task-j1
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import type { CrmDashboardSummary } from '../api/types';
|
||||
|
||||
function formatNumber(value: number) {
|
||||
return new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format(value);
|
||||
}
|
||||
|
||||
function SummaryCard({
|
||||
title,
|
||||
description,
|
||||
items
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
items: Array<{ label: string; value: number; suffix?: string }>;
|
||||
}) {
|
||||
return (
|
||||
<Card className='h-full'>
|
||||
<CardHeader>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4 sm:grid-cols-2'>
|
||||
{items.map((item) => (
|
||||
<div key={item.label} className='rounded-lg border bg-muted/20 p-4'>
|
||||
<div className='text-muted-foreground text-xs'>{item.label}</div>
|
||||
<div className='mt-2 text-2xl font-semibold'>
|
||||
{formatNumber(item.value)}
|
||||
{item.suffix ? <span className='ml-1 text-sm font-normal'>{item.suffix}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardSummaryCards({ summary }: { summary: CrmDashboardSummary }) {
|
||||
return (
|
||||
<div className='grid gap-4 xl:grid-cols-2 2xl:grid-cols-4'>
|
||||
<SummaryCard
|
||||
title='Lead'
|
||||
description='Lead-stage enquiries based on the frozen KPI rules.'
|
||||
items={[
|
||||
{ label: 'Lead Count', value: summary.lead.leadCount },
|
||||
{ label: 'New Leads', value: summary.lead.newLeads },
|
||||
{ label: 'Unassigned Leads', value: summary.lead.unassignedLeads },
|
||||
{ label: 'Lead Aging Avg', value: summary.lead.averageLeadAgingDays, suffix: 'days' }
|
||||
]}
|
||||
/>
|
||||
<SummaryCard
|
||||
title='Opportunity'
|
||||
description='Assigned enquiries that remain active in the pipeline.'
|
||||
items={[
|
||||
{ label: 'Opportunity Count', value: summary.opportunity.opportunityCount },
|
||||
{ label: 'Open Opportunities', value: summary.opportunity.openOpportunities },
|
||||
{ label: 'Hot Opportunities', value: summary.opportunity.hotOpportunities },
|
||||
{ label: 'Opportunity Value', value: summary.opportunity.opportunityValue }
|
||||
]}
|
||||
/>
|
||||
<SummaryCard
|
||||
title='Quotation'
|
||||
description='Live quotation status counts from production data.'
|
||||
items={[
|
||||
{ label: 'Draft Quotations', value: summary.quotation.draftQuotations },
|
||||
{ label: 'Pending Approval', value: summary.quotation.pendingApproval },
|
||||
{ label: 'Approved Quotations', value: summary.quotation.approvedQuotations },
|
||||
{ label: 'Sent Quotations', value: summary.quotation.sentQuotations }
|
||||
]}
|
||||
/>
|
||||
<SummaryCard
|
||||
title='Revenue'
|
||||
description='Quotation, won, and lost values using frozen dashboard rules.'
|
||||
items={[
|
||||
{ label: 'Quotation Value', value: summary.revenue.quotationValue },
|
||||
{ label: 'Won Value', value: summary.revenue.wonValue },
|
||||
{ label: 'Lost Value', value: summary.revenue.lostValue }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user