192 lines
7.6 KiB
TypeScript
192 lines
7.6 KiB
TypeScript
'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 opportunity 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-opportunities'>Linked Opportunities</TabsTrigger>
|
|
<TabsTrigger value='activity'>Activity</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value='followups'>
|
|
<LeadFollowupPanel leadId={leadId} detail={data} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value='linked-opportunities'>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Linked Opportunities</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className='space-y-3'>
|
|
{lead.linkedOpportunities.length === 0 ? (
|
|
<div className='text-muted-foreground text-sm'>No linked opportunities yet.</div>
|
|
) : (
|
|
lead.linkedOpportunities.map((opportunity) => (
|
|
<div key={opportunity.id} className='border-border rounded-md border p-3'>
|
|
<div className='flex items-center justify-between gap-3'>
|
|
<Link
|
|
href={`/dashboard/crm/opportunities/${opportunity.id}`}
|
|
className='font-medium hover:underline'
|
|
>
|
|
{opportunity.code}
|
|
</Link>
|
|
<Badge variant='outline'>{opportunity.status}</Badge>
|
|
</div>
|
|
<div className='mt-1 text-sm'>{opportunity.title}</div>
|
|
<div className='text-muted-foreground mt-2 text-xs'>
|
|
Owner: {opportunity.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 Opportunities' value={String(lead.relatedOpportunityCount)} />
|
|
<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>
|
|
);
|
|
}
|
|
|