102 lines
4.3 KiB
TypeScript
102 lines
4.3 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
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,
|
|
href
|
|
}: {
|
|
title: string;
|
|
description: string;
|
|
items: Array<{ label: string; value: number; suffix?: string }>;
|
|
href?: string;
|
|
}) {
|
|
const content = (
|
|
<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>
|
|
);
|
|
|
|
return href ? <Link href={href} className='block'>{content}</Link> : content;
|
|
}
|
|
|
|
export function DashboardSummaryCards({
|
|
summary,
|
|
canViewCommercialData
|
|
}: {
|
|
summary: CrmDashboardSummary;
|
|
canViewCommercialData: boolean;
|
|
}) {
|
|
return (
|
|
<div className={`grid gap-4 ${canViewCommercialData ? 'xl:grid-cols-2 2xl:grid-cols-4' : 'xl:grid-cols-2'}`}>
|
|
<SummaryCard
|
|
title='ลีด'
|
|
description='รายการที่ยังอยู่ในขั้นลีดและอยู่ในความรับผิดชอบของการตลาด'
|
|
href='/dashboard/crm/leads'
|
|
items={[
|
|
{ label: 'จำนวนลีด', value: summary.lead.leadCount },
|
|
{ label: 'ลีดใหม่', value: summary.lead.newLeads },
|
|
{ label: 'ลีดยังไม่มอบหมาย', value: summary.lead.unassignedLeads },
|
|
{ label: 'อายุลีดเฉลี่ย', value: summary.lead.averageLeadAgingDays, suffix: 'วัน' }
|
|
]}
|
|
/>
|
|
<SummaryCard
|
|
title='โอกาสขาย'
|
|
description='รายการที่อยู่ในขั้นโอกาสขาย รวมถึงผลลัพธ์ชนะและแพ้'
|
|
href='/dashboard/crm/enquiries'
|
|
items={[
|
|
{ label: 'จำนวนโอกาสขาย', value: summary.enquiry.enquiryCount },
|
|
{ label: 'โอกาสขายที่กำลังดำเนินการ', value: summary.enquiry.activeEnquiries },
|
|
{ label: 'จำนวนงานที่ชนะ', value: summary.enquiry.wonCount },
|
|
{ label: 'จำนวนงานที่แพ้', value: summary.enquiry.lostCount }
|
|
]}
|
|
/>
|
|
{canViewCommercialData ? (
|
|
<SummaryCard
|
|
title='ใบเสนอราคา'
|
|
description='สถานะของใบเสนอราคาจากข้อมูลจริงในระบบ'
|
|
items={[
|
|
{ label: 'ฉบับร่าง', value: summary.quotation.draftQuotations },
|
|
{ label: 'รออนุมัติ', value: summary.quotation.pendingApproval },
|
|
{ label: 'อนุมัติแล้ว', value: summary.quotation.approvedQuotations },
|
|
{ label: 'ส่งแล้ว', value: summary.quotation.sentQuotations }
|
|
]}
|
|
/>
|
|
) : null}
|
|
{canViewCommercialData ? (
|
|
<SummaryCard
|
|
title='มูลค่าใบเสนอราคา'
|
|
description='มูลค่าใบเสนอราคา งานที่ชนะ และงานที่แพ้ตามกติกา revenue attribution'
|
|
items={[
|
|
{ label: 'มูลค่าใบเสนอราคา', value: summary.revenue.quotationValue },
|
|
{ label: 'มูลค่างานที่ชนะ', value: summary.revenue.wonValue },
|
|
{ label: 'มูลค่างานที่แพ้', value: summary.revenue.lostValue }
|
|
]}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|