task-d.5.4

This commit is contained in:
phaichayon
2026-06-25 08:06:53 +07:00
parent 0c28735c90
commit 6093003b70
22 changed files with 1930 additions and 207 deletions

View File

@@ -0,0 +1,190 @@
'use client';
import Link from 'next/link';
import { useState } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
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 { leadByIdOptions } from '../api/queries';
import type { LeadReferenceData } from '../api/types';
import { LeadAssignmentDialog } from './lead-assignment-dialog';
import { LeadFollowupPanel } from './lead-followup-panel';
import { LeadForm } from './lead-form';
function FieldItem({ label, value }: { label: string; value: string | null | undefined }) {
return (
<div className='space-y-1'>
<div className='text-muted-foreground text-xs'>{label}</div>
<div className='text-sm'>{value || '-'}</div>
</div>
);
}
export function LeadDetail({
leadId,
referenceData,
canUpdate,
canDelete,
canAssign
}: {
leadId: string;
referenceData: LeadReferenceData;
canUpdate: boolean;
canDelete: boolean;
canAssign: boolean;
}) {
const { data } = useSuspenseQuery(leadByIdOptions(leadId));
const lead = data.lead;
const [editOpen, setEditOpen] = useState(false);
const [assignOpen, setAssignOpen] = useState(false);
return (
<div className='space-y-6'>
<LeadForm open={editOpen} onOpenChange={setEditOpen} referenceData={referenceData} lead={lead} />
<LeadAssignmentDialog
lead={lead}
referenceData={referenceData}
open={assignOpen}
onOpenChange={setAssignOpen}
/>
<div className='flex items-start justify-between gap-4'>
<div>
<div className='flex items-center gap-3'>
<h2 className='text-2xl font-semibold'>{lead.code}</h2>
<Badge variant='outline'>{lead.statusLabel ?? lead.status}</Badge>
</div>
<p className='text-muted-foreground mt-2 text-sm'>
Lead workspace for marketing qualification before sales enquiry handoff
</p>
</div>
<div className='flex flex-wrap gap-2'>
{canAssign ? (
<Button variant='outline' onClick={() => setAssignOpen(true)}>
{lead.assignedSalesOwnerId ? 'Reassign to Sales' : 'Assign to Sales'}
</Button>
) : null}
{canUpdate ? (
<Button variant='outline' onClick={() => setEditOpen(true)}>
Edit Lead
</Button>
) : null}
</div>
</div>
<div className='grid gap-6 lg:grid-cols-3'>
<div className='space-y-6 lg:col-span-2'>
<Card>
<CardHeader>
<CardTitle>Lead Summary</CardTitle>
</CardHeader>
<CardContent className='grid gap-4 md:grid-cols-2 xl:grid-cols-3'>
<FieldItem label='Customer' value={lead.customerName} />
<FieldItem label='Contact' value={lead.contactName} />
<FieldItem label='Project Name' value={lead.projectName} />
<FieldItem label='Project Location' value={lead.projectLocation} />
<FieldItem label='Awareness' value={lead.awarenessLabel} />
<FieldItem label='Priority' value={lead.priority} />
<FieldItem label='Marketing Owner' value={lead.ownerMarketingName} />
<FieldItem label='Suggested Sales Owner' value={lead.suggestedSalesOwnerName} />
<FieldItem label='Assigned Sales Owner' value={lead.assignedSalesOwnerName} />
<FieldItem
label='Estimated Value'
value={lead.estimatedValue !== null ? lead.estimatedValue.toLocaleString() : null}
/>
<FieldItem label='Assignment Remark' value={lead.assignmentRemark} />
<FieldItem label='Description' value={lead.description} />
</CardContent>
</Card>
<Tabs defaultValue='followups' className='space-y-4'>
<TabsList>
<TabsTrigger value='followups'>Follow-ups</TabsTrigger>
<TabsTrigger value='linked-enquiries'>Linked Enquiries</TabsTrigger>
<TabsTrigger value='activity'>Activity</TabsTrigger>
</TabsList>
<TabsContent value='followups'>
<LeadFollowupPanel leadId={leadId} detail={data} />
</TabsContent>
<TabsContent value='linked-enquiries'>
<Card>
<CardHeader>
<CardTitle>Linked Enquiries</CardTitle>
</CardHeader>
<CardContent className='space-y-3'>
{lead.linkedEnquiries.length === 0 ? (
<div className='text-muted-foreground text-sm'>No linked enquiries yet.</div>
) : (
lead.linkedEnquiries.map((enquiry) => (
<div key={enquiry.id} className='border-border rounded-md border p-3'>
<div className='flex items-center justify-between gap-3'>
<Link
href={`/dashboard/crm/enquiries/${enquiry.id}`}
className='font-medium hover:underline'
>
{enquiry.code}
</Link>
<Badge variant='outline'>{enquiry.status}</Badge>
</div>
<div className='mt-1 text-sm'>{enquiry.title}</div>
<div className='text-muted-foreground mt-2 text-xs'>
Owner: {enquiry.assignedToName ?? '-'}
</div>
</div>
))
)}
</CardContent>
</Card>
</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} -{' '}
{new Date(activity.createdAt).toLocaleString()}
</div>
</div>
))
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
<div className='space-y-6'>
<Card>
<CardHeader>
<CardTitle>Workspace Status</CardTitle>
</CardHeader>
<CardContent className='space-y-3'>
<FieldItem label='Current Workspace' value='Lead' />
<FieldItem label='Related Enquiries' value={String(lead.relatedEnquiryCount)} />
<FieldItem
label='Assigned At'
value={lead.assignedAt ? new Date(lead.assignedAt).toLocaleString() : null}
/>
<FieldItem label='Assigned By' value={lead.assignedByName} />
<FieldItem label='Deleted' value={canDelete ? 'Allowed' : 'Not allowed'} />
</CardContent>
</Card>
</div>
</div>
</div>
);
}