d.6
This commit is contained in:
@@ -7,6 +7,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { AuditLogTab } from '@/features/crm/components/audit-log-tab';
|
||||
import { formatDateTime } from '@/lib/date-format';
|
||||
import { formatNumber } from '@/lib/number-format';
|
||||
import { leadByIdOptions } from '../api/queries';
|
||||
@@ -111,7 +112,7 @@ export function LeadDetail({
|
||||
<TabsList>
|
||||
<TabsTrigger value='followups'>Follow-ups</TabsTrigger>
|
||||
<TabsTrigger value='linked-opportunities'>Linked Opportunities</TabsTrigger>
|
||||
<TabsTrigger value='activity'>Activity</TabsTrigger>
|
||||
<TabsTrigger value='activity'>Audit Log</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value='followups'>
|
||||
@@ -152,26 +153,11 @@ export function LeadDetail({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='activity'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Activity</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3'>
|
||||
{lead.activity.length === 0 ? (
|
||||
<div className='text-muted-foreground text-sm'>No activity yet.</div>
|
||||
) : (
|
||||
lead.activity.map((activity) => (
|
||||
<div key={activity.id} className='border-border rounded-md border p-3'>
|
||||
<div className='font-medium'>{activity.action}</div>
|
||||
<div className='text-muted-foreground mt-1 text-xs'>
|
||||
{activity.actorName ?? activity.userId} -{' '}
|
||||
{formatDateTime(activity.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AuditLogTab
|
||||
items={lead.activity}
|
||||
emptyMessage='No audit log recorded yet.'
|
||||
description='System-generated immutable history for this lead.'
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,15 @@ export function LeadFollowupPanel({
|
||||
leadId: string;
|
||||
detail: LeadDetailResponse;
|
||||
}) {
|
||||
const { FormTextField, FormSelectField, FormTextareaField } = useFormFields<FollowupFormValues>();
|
||||
const { FormDatePickerField, FormSelectField, FormTextareaField } =
|
||||
useFormFields<FollowupFormValues>();
|
||||
|
||||
const mutation = useMutation({
|
||||
...createLeadFollowupMutation,
|
||||
onError: (error) => {
|
||||
toast.error(error instanceof Error ? error.message : 'Unable to save lead follow-up');
|
||||
}
|
||||
});
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
@@ -42,80 +50,78 @@ export function LeadFollowupPanel({
|
||||
nextFollowupDate: value.nextFollowupDate || null
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
...createLeadFollowupMutation,
|
||||
onSuccess: () => {
|
||||
toast.success('บันทึก follow-up สำเร็จ');
|
||||
toast.success('Saved lead follow-up successfully');
|
||||
form.reset();
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error instanceof Error ? error.message : 'ไม่สามารถบันทึก follow-up ได้');
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>New Follow-up</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form.AppForm>
|
||||
<form.Form id='lead-followup-form' className='grid gap-4 md:grid-cols-2'>
|
||||
<FormTextField name='followupDate' label='Follow-up Date' placeholder='YYYY-MM-DD' />
|
||||
<FormTextField
|
||||
name='nextFollowupDate'
|
||||
label='Next Follow-up Date'
|
||||
placeholder='YYYY-MM-DD'
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Follow-ups</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='flex flex-col gap-6'>
|
||||
<form.AppForm>
|
||||
<form.Form id='lead-followup-form' className='grid gap-4 md:grid-cols-2'>
|
||||
<FormDatePickerField
|
||||
name='followupDate'
|
||||
label='Follow-up Date'
|
||||
required
|
||||
placeholder='YYYY-MM-DD'
|
||||
/>
|
||||
<FormDatePickerField
|
||||
name='nextFollowupDate'
|
||||
label='Next Follow-up Date'
|
||||
placeholder='YYYY-MM-DD'
|
||||
/>
|
||||
<FormSelectField
|
||||
name='followupStatus'
|
||||
label='Follow-up Status'
|
||||
options={detail.referenceData.followupStatuses.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))}
|
||||
/>
|
||||
<div className='md:col-span-2'>
|
||||
<FormTextareaField
|
||||
name='note'
|
||||
label='Note'
|
||||
size='md'
|
||||
placeholder='Summarize the latest customer conversation and next step.'
|
||||
/>
|
||||
<FormSelectField
|
||||
name='followupStatus'
|
||||
label='Follow-up Status'
|
||||
options={detail.referenceData.followupStatuses.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))}
|
||||
/>
|
||||
<div />
|
||||
<FormTextareaField name='note' label='Note' className='md:col-span-2' />
|
||||
<div className='md:col-span-2'>
|
||||
<Button type='submit' form='lead-followup-form' isLoading={mutation.isPending}>
|
||||
Save Follow-up
|
||||
</Button>
|
||||
</div>
|
||||
</form.Form>
|
||||
</form.AppForm>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</form.Form>
|
||||
</form.AppForm>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>History</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3'>
|
||||
<div className='flex justify-end'>
|
||||
<Button type='submit' form='lead-followup-form' isLoading={mutation.isPending}>
|
||||
Save Follow-up
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-3'>
|
||||
{detail.followups.length === 0 ? (
|
||||
<div className='text-muted-foreground text-sm'>No follow-up history yet.</div>
|
||||
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm'>
|
||||
No follow-ups yet.
|
||||
</div>
|
||||
) : (
|
||||
detail.followups.map((followup) => (
|
||||
<div key={followup.id} className='border-border rounded-md border p-3'>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<div className='font-medium'>
|
||||
{followup.followupStatusLabel ?? followup.followupStatus}
|
||||
<div key={followup.id} className='rounded-lg border p-4'>
|
||||
<div className='font-medium'>{followup.followupStatusLabel ?? followup.followupStatus}</div>
|
||||
<div className='text-muted-foreground mt-1 text-xs'>
|
||||
{formatDate(followup.followupDate)}
|
||||
</div>
|
||||
<div className='mt-2 text-sm'>{followup.note || '-'}</div>
|
||||
{followup.nextFollowupDate ? (
|
||||
<div className='text-muted-foreground mt-2 text-xs'>
|
||||
Next: {formatDate(followup.nextFollowupDate)}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-xs'>{formatDate(followup.followupDate)}</div>
|
||||
</div>
|
||||
{followup.note ? <div className='mt-2 text-sm'>{followup.note}</div> : null}
|
||||
<div className='text-muted-foreground mt-2 text-xs'>
|
||||
{followup.createdByName ?? followup.createdBy}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,7 +258,10 @@ async function listLeadActivity(
|
||||
entityId: log.entityId,
|
||||
createdAt: log.createdAt,
|
||||
userId: log.userId,
|
||||
actorName: actorMap.get(log.userId) ?? null
|
||||
actorName: actorMap.get(log.userId) ?? null,
|
||||
beforeData: log.beforeData,
|
||||
afterData: log.afterData,
|
||||
requestId: log.requestId
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1077,4 +1080,3 @@ export function buildLeadAccessContext(input: {
|
||||
}) {
|
||||
return buildCrmSecurityContext(input);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,15 +47,7 @@ export interface LeadLinkedOpportunitySummary {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface LeadActivityRecord {
|
||||
id: string;
|
||||
action: string;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
createdAt: string;
|
||||
userId: string;
|
||||
actorName: string | null;
|
||||
}
|
||||
export type LeadActivityRecord = CrmAuditLogRecord;
|
||||
|
||||
export interface LeadReferenceData {
|
||||
awarenesses: LeadOption[];
|
||||
@@ -225,4 +217,5 @@ export interface LeadMutationSuccessResponse {
|
||||
lead?: LeadDetail;
|
||||
followup?: LeadFollowupSummary;
|
||||
}
|
||||
import type { CrmAuditLogRecord } from '@/features/crm/audit-log/types';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user