thai-uxui

This commit is contained in:
phaichayon
2026-06-19 11:11:17 +07:00
parent c0cc880be2
commit 721653f692
47 changed files with 1135 additions and 538 deletions

View File

@@ -24,42 +24,42 @@ export function DashboardApprovalMetrics({
return (
<Card>
<CardHeader>
<CardTitle>Approval Analytics</CardTitle>
<CardDescription>Live workflow counts plus the current approver queue.</CardDescription>
<CardTitle></CardTitle>
<CardDescription> workflow </CardDescription>
</CardHeader>
<CardContent className='space-y-6'>
<div className='grid gap-4 sm:grid-cols-2 xl:grid-cols-5'>
<Stat label='Pending Approvals' value={approvals.summary.pendingApprovals} />
<Stat label='Approved Today' value={approvals.summary.approvedToday} />
<Stat label='Rejected Today' value={approvals.summary.rejectedToday} />
<Stat label='Returned Today' value={approvals.summary.returnedToday} />
<Stat label='รออนุมัติ' value={approvals.summary.pendingApprovals} />
<Stat label='อนุมัติวันนี้' value={approvals.summary.approvedToday} />
<Stat label='ไม่อนุมัติวันนี้' value={approvals.summary.rejectedToday} />
<Stat label='ตีกลับวันนี้' value={approvals.summary.returnedToday} />
<Stat
label='Average Approval Time'
label='เวลาอนุมัติเฉลี่ย'
value={approvals.summary.averageApprovalTimeHours}
suffix='hrs'
suffix='ชม.'
/>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead>Document</TableHead>
<TableHead></TableHead>
<TableHead>Workflow</TableHead>
<TableHead>Current Step</TableHead>
<TableHead>Requested At</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{approvals.myPendingApprovals.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className='text-muted-foreground text-center'>
No pending approvals are assigned to your current approver scope.
</TableCell>
</TableRow>
) : (
approvals.myPendingApprovals.map((row) => (
<TableRow key={row.id}>
<TableCell>
<div className='font-medium'>{row.entityCode ?? 'Approval'}</div>
<div className='font-medium'>{row.entityCode ?? 'อนุมัติเอกสาร'}</div>
<div className='text-muted-foreground text-xs'>{row.entityTitle ?? '-'}</div>
</TableCell>
<TableCell>{row.workflowName}</TableCell>

View File

@@ -61,11 +61,11 @@ export function DashboardFilters({
value={params.branch ?? '__all__'}
onValueChange={(value) => void setParams({ branch: value === '__all__' ? null : value })}
>
<SelectTrigger>
<SelectValue placeholder='Branch' />
<SelectTrigger>
<SelectValue placeholder='สาขา' />
</SelectTrigger>
<SelectContent>
<SelectItem value='__all__'>All Branches</SelectItem>
<SelectItem value='__all__'></SelectItem>
{referenceData.branches.map((item) => (
<SelectItem key={item.id} value={item.id}>
{item.label}
@@ -77,11 +77,11 @@ export function DashboardFilters({
value={params.salesman ?? '__all__'}
onValueChange={(value) => void setParams({ salesman: value === '__all__' ? null : value })}
>
<SelectTrigger>
<SelectValue placeholder='Sales Person' />
<SelectTrigger>
<SelectValue placeholder='ฝ่ายขาย' />
</SelectTrigger>
<SelectContent>
<SelectItem value='__all__'>All Sales</SelectItem>
<SelectItem value='__all__'></SelectItem>
{referenceData.salesmen.map((item) => (
<SelectItem key={item.id} value={item.id}>
{item.name}
@@ -95,11 +95,11 @@ export function DashboardFilters({
void setParams({ projectPartyRole: value === '__all__' ? null : value })
}
>
<SelectTrigger>
<SelectValue placeholder='Project Party Role' />
<SelectTrigger>
<SelectValue placeholder='บทบาทผู้เกี่ยวข้องในโครงการ' />
</SelectTrigger>
<SelectContent>
<SelectItem value='__all__'>All Party Roles</SelectItem>
<SelectItem value='__all__'></SelectItem>
{referenceData.projectPartyRoles.map((item) => (
<SelectItem key={item.id} value={item.id}>
{item.label}
@@ -111,11 +111,11 @@ export function DashboardFilters({
value={params.productType ?? '__all__'}
onValueChange={(value) => void setParams({ productType: value === '__all__' ? null : value })}
>
<SelectTrigger>
<SelectValue placeholder='Product Type' />
<SelectTrigger>
<SelectValue placeholder='ประเภทสินค้า' />
</SelectTrigger>
<SelectContent>
<SelectItem value='__all__'>All Product Types</SelectItem>
<SelectItem value='__all__'></SelectItem>
{referenceData.productTypes.map((item) => (
<SelectItem key={item.id} value={item.id}>
{item.label}
@@ -138,7 +138,7 @@ export function DashboardFilters({
})
}
>
Reset Filters
</Button>
{canExport ? (
<>
@@ -148,7 +148,7 @@ export function DashboardFilters({
exportQuery ? `&${exportQuery}` : ''
}`}
>
<Icons.download className='mr-2 h-4 w-4' /> Export Sales CSV
<Icons.download className='mr-2 h-4 w-4' /> CSV
</Link>
</Button>
<Button asChild variant='outline'>
@@ -157,7 +157,7 @@ export function DashboardFilters({
exportQuery ? `&${exportQuery}` : ''
}`}
>
<Icons.download className='mr-2 h-4 w-4' /> Export Revenue Excel
<Icons.download className='mr-2 h-4 w-4' /> Excel
</Link>
</Button>
<Button asChild variant='outline'>
@@ -166,7 +166,7 @@ export function DashboardFilters({
exportQuery ? `&${exportQuery}` : ''
}`}
>
<Icons.download className='mr-2 h-4 w-4' /> Export Funnel CSV
<Icons.download className='mr-2 h-4 w-4' /> CSV Funnel
</Link>
</Button>
</>

View File

@@ -21,31 +21,31 @@ export function DashboardFollowups({
return (
<Card>
<CardHeader>
<CardTitle>Follow-up Dashboard</CardTitle>
<CardDescription>Upcoming and completed CRM follow-ups across enquiries and quotations.</CardDescription>
<CardTitle>Dashboard </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='space-y-6'>
<div className='grid gap-4 sm:grid-cols-2 xl:grid-cols-4'>
<Stat label='Due Today' value={followups.summary.dueToday} />
<Stat label='Due This Week' value={followups.summary.dueThisWeek} />
<Stat label='Overdue' value={followups.summary.overdue} />
<Stat label='Completed' value={followups.summary.completed} />
<Stat label='ครบกำหนดวันนี้' value={followups.summary.dueToday} />
<Stat label='ครบกำหนดสัปดาห์นี้' value={followups.summary.dueThisWeek} />
<Stat label='เกินกำหนด' value={followups.summary.overdue} />
<Stat label='เสร็จแล้ว' value={followups.summary.completed} />
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead>Date</TableHead>
<TableHead>Customer</TableHead>
<TableHead>Enquiry</TableHead>
<TableHead>Enquiry Owner</TableHead>
<TableHead>Source</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{followups.upcoming.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className='text-muted-foreground text-center'>
No upcoming follow-ups match the current filters.
</TableCell>
</TableRow>
) : (

View File

@@ -13,8 +13,8 @@ export function DashboardFunnel({ steps }: { steps: CrmDashboardFunnelStep[] })
return (
<Card>
<CardHeader>
<CardTitle>Sales Pipeline Funnel</CardTitle>
<CardDescription>Lead, enquiry, quotation, approval, and won stages from the current pipeline model.</CardDescription>
<CardTitle>Funnel </CardTitle>
<CardDescription> </CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{steps.map((step) => (
@@ -23,12 +23,12 @@ export function DashboardFunnel({ steps }: { steps: CrmDashboardFunnelStep[] })
<div>
<div className='font-medium'>{step.label}</div>
<div className='text-muted-foreground text-xs'>
{formatNumber(step.count)} items, value {formatNumber(step.value)}
{formatNumber(step.count)} , {formatNumber(step.value)}
</div>
</div>
<div className='text-right text-sm'>
<div>{step.conversionRate === null ? '-' : `${formatNumber(step.conversionRate)}%`}</div>
<div className='text-muted-foreground text-xs'>conversion</div>
<div className='text-muted-foreground text-xs'></div>
</div>
</div>
<div className='bg-muted h-2 overflow-hidden rounded-full'>

View File

@@ -12,25 +12,25 @@ export function DashboardHotProjects({ rows }: { rows: CrmDashboardHotProjectRow
return (
<Card>
<CardHeader>
<CardTitle>Top Active Enquiries</CardTitle>
<CardDescription>Sales-owned hot enquiries ranked by estimated commercial value.</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Customer</TableHead>
<TableHead className='text-right'>Value</TableHead>
<TableHead>Enquiry Owner</TableHead>
<TableHead className='text-right'>Probability</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead></TableHead>
<TableHead className='text-right'></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className='text-muted-foreground text-center'>
No hot opportunities match the current filters.
</TableCell>
</TableRow>
) : (

View File

@@ -28,16 +28,16 @@ function RevenueTable({
<TableHeader>
<TableRow>
<TableHead>Customer</TableHead>
<TableHead className='text-right'>Revenue</TableHead>
<TableHead className='text-right'>Quotations</TableHead>
<TableHead className='text-right'>Won Revenue</TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className='text-muted-foreground text-center'>
No revenue rows match the current filters.
</TableCell>
</TableRow>
) : (
@@ -68,23 +68,23 @@ export function DashboardRevenueCards({
return (
<div className='grid gap-4 xl:grid-cols-2'>
<RevenueTable
title='Top End Customers'
description='Revenue owner attribution with billing-customer fallback when end-customer is missing.'
title='เจ้าของโครงการมูลค่าสูงสุด'
description='จัดอันดับตามเจ้าของโครงการ โดยใช้ลูกค้าผู้รับใบเสนอราคาเป็น fallback เมื่อไม่มี end customer'
rows={revenueAnalytics.topEndCustomers}
/>
<RevenueTable
title='Top Billing Customers'
description='Commercial relationship view grouped by billing customer.'
title='ลูกค้าผู้รับใบเสนอราคามูลค่าสูงสุด'
description='มุมมองเชิงพาณิชย์แบบจัดกลุ่มตามลูกค้าผู้รับใบเสนอราคา'
rows={revenueAnalytics.topBillingCustomers}
/>
<RevenueTable
title='Top Contractors'
description='Relationship analytics with full attribution for each contractor role.'
title='ผู้รับเหมามูลค่าสูงสุด'
description='วิเคราะห์มูลค่าตามบทบาทผู้รับเหมาในโครงการ'
rows={revenueAnalytics.topContractors}
/>
<RevenueTable
title='Top Consultants'
description='Consultant-side revenue relationships from project parties.'
title='ที่ปรึกษามูลค่าสูงสุด'
description='วิเคราะห์มูลค่าตามบทบาทที่ปรึกษาในโครงการ'
rows={revenueAnalytics.topConsultants}
/>
</div>

View File

@@ -12,27 +12,27 @@ export function DashboardSalesRanking({ rows }: { rows: CrmDashboardSalesRanking
return (
<Card>
<CardHeader>
<CardTitle>Sales Ranking</CardTitle>
<CardDescription>Lead and enquiry ownership follow assignment rules. Quotation ownership uses salesman.</CardDescription>
<CardTitle></CardTitle>
<CardDescription> </CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Sales Person</TableHead>
<TableHead className='text-right'>Lead</TableHead>
<TableHead className='text-right'>Enquiry</TableHead>
<TableHead className='text-right'>Quotation</TableHead>
<TableHead className='text-right'>Approved</TableHead>
<TableHead className='text-right'>Won Revenue</TableHead>
<TableHead className='text-right'>Conversion</TableHead>
<TableHead></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
<TableHead className='text-right'></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className='text-muted-foreground text-center'>
No sales ranking rows match the current filters.
</TableCell>
</TableRow>
) : (

View File

@@ -52,47 +52,47 @@ export function DashboardSummaryCards({
return (
<div className={`grid gap-4 ${canViewCommercialData ? 'xl:grid-cols-2 2xl:grid-cols-4' : 'xl:grid-cols-2'}`}>
<SummaryCard
title='Lead'
description='Marketing-owned lead-stage records based on pipeline stage.'
title='ลีด'
description='รายการที่ยังอยู่ในขั้นลีดและอยู่ในความรับผิดชอบของการตลาด'
href='/dashboard/crm/leads'
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' }
{ label: 'จำนวนลีด', value: summary.lead.leadCount },
{ label: 'ลีดใหม่', value: summary.lead.newLeads },
{ label: 'ลีดยังไม่มอบหมาย', value: summary.lead.unassignedLeads },
{ label: 'อายุลีดเฉลี่ย', value: summary.lead.averageLeadAgingDays, suffix: 'วัน' }
]}
/>
<SummaryCard
title='Enquiry'
description='Sales-owned enquiry-stage records plus closed outcomes.'
title='โอกาสขาย'
description='รายการที่อยู่ในขั้นโอกาสขาย รวมถึงผลลัพธ์ชนะและแพ้'
href='/dashboard/crm/enquiries'
items={[
{ label: 'Enquiry Count', value: summary.enquiry.enquiryCount },
{ label: 'Active Enquiries', value: summary.enquiry.activeEnquiries },
{ label: 'Closed Won', value: summary.enquiry.wonCount },
{ label: 'Closed Lost', value: summary.enquiry.lostCount }
{ label: 'จำนวนโอกาสขาย', value: summary.enquiry.enquiryCount },
{ label: 'โอกาสขายที่กำลังดำเนินการ', value: summary.enquiry.activeEnquiries },
{ label: 'จำนวนงานที่ชนะ', value: summary.enquiry.wonCount },
{ label: 'จำนวนงานที่แพ้', value: summary.enquiry.lostCount }
]}
/>
{canViewCommercialData ? (
<SummaryCard
title='Quotation'
description='Live quotation status counts from production data.'
title='ใบเสนอราคา'
description='สถานะของใบเสนอราคาจากข้อมูลจริงในระบบ'
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 }
{ label: 'ฉบับร่าง', value: summary.quotation.draftQuotations },
{ label: 'รออนุมัติ', value: summary.quotation.pendingApproval },
{ label: 'อนุมัติแล้ว', value: summary.quotation.approvedQuotations },
{ label: 'ส่งแล้ว', value: summary.quotation.sentQuotations }
]}
/>
) : null}
{canViewCommercialData ? (
<SummaryCard
title='Revenue'
description='Quotation, won, and lost values using governed revenue rules.'
title='มูลค่าใบเสนอราคา'
description='มูลค่าใบเสนอราคา งานที่ชนะ และงานที่แพ้ตามกติกา revenue attribution'
items={[
{ label: 'Quotation Value', value: summary.revenue.quotationValue },
{ label: 'Won Value', value: summary.revenue.wonValue },
{ label: 'Lost Value', value: summary.revenue.lostValue }
{ label: 'มูลค่าใบเสนอราคา', value: summary.revenue.quotationValue },
{ label: 'มูลค่างานที่ชนะ', value: summary.revenue.wonValue },
{ label: 'มูลค่างานที่แพ้', value: summary.revenue.lostValue }
]}
/>
) : null}