task-d.5.4
This commit is contained in:
190
src/features/crm/leads/components/lead-detail.tsx
Normal file
190
src/features/crm/leads/components/lead-detail.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user