task-d.5.5

This commit is contained in:
phaichayon
2026-06-25 08:27:20 +07:00
parent 6093003b70
commit f2c7156851
11 changed files with 1049 additions and 530 deletions

View File

@@ -2,13 +2,21 @@
import Link from 'next/link';
import type { Column, ColumnDef } from '@tanstack/react-table';
import { Icons } from '@/components/icons';
import { Badge } from '@/components/ui/badge';
import { DataTableColumnHeader } from '@/components/ui/table/data-table-column-header';
import { Icons } from '@/components/icons';
import type { EnquiryListItem, EnquiryReferenceData } from '../api/types';
import { EnquiryCellAction } from './enquiry-cell-action';
import { EnquiryStatusBadge } from './enquiry-status-badge';
type SharedColumnsConfig = {
referenceData: EnquiryReferenceData;
canUpdate: boolean;
canDelete: boolean;
canAssign: boolean;
canReassign: boolean;
};
function getLeadColumns({
referenceData,
canUpdate,
@@ -28,10 +36,7 @@ function getLeadColumns({
),
cell: ({ row }) => (
<div className='flex flex-col'>
<Link
href={`/dashboard/crm/leads/${row.original.id}`}
className='font-medium hover:underline'
>
<Link href={`/dashboard/crm/leads/${row.original.id}`} className='font-medium hover:underline'>
{row.original.code}
</Link>
<span className='text-muted-foreground text-xs'>{row.original.title}</span>
@@ -57,68 +62,19 @@ function getLeadColumns({
id: 'customer',
accessorFn: (row) => row.customerId,
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Company' />
<DataTableColumnHeader column={column} title='Customer' />
),
cell: ({ row }) => <span>{row.original.customerName}</span>,
meta: {
label: 'Company',
variant: 'multiSelect' as const,
label: 'Customer',
variant: 'select' as const,
options: referenceData.customers.map((item) => ({
value: item.id,
label: item.name
label: `${item.name} (${item.code})`
}))
},
enableColumnFilter: true
},
{
id: 'contact',
accessorFn: (row) => row.contactId ?? '',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Contact' />
),
cell: ({ row }) => <span>{row.original.contactName ?? '-'}</span>
},
{
id: 'assignedSales',
accessorFn: (row) => row.assignedToName ?? '',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Assigned Sales' />
),
cell: ({ row }) => <span>{row.original.assignedToName ?? 'Unassigned'}</span>
},
{
id: 'createdBy',
accessorFn: (row) => row.createdByName ?? row.createdBy,
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Created By' />
),
cell: ({ row }) => <span>{row.original.createdByName ?? row.original.createdBy}</span>
},
{
id: 'createdAt',
accessorKey: 'createdAt',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Created At' />
),
cell: ({ row }) => <span>{new Date(row.original.createdAt).toLocaleDateString()}</span>
},
{
id: 'age',
accessorFn: (row) => row.createdAt,
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Age' />
),
cell: ({ row }) => {
const ageInDays = Math.max(
0,
Math.floor(
(Date.now() - new Date(row.original.createdAt).getTime()) / (1000 * 60 * 60 * 24)
)
);
return <span>{ageInDays} days</span>;
}
},
{
id: 'status',
accessorKey: 'status',
@@ -164,13 +120,14 @@ function getEnquiryWorkspaceColumns({
canReassign
}: SharedColumnsConfig): ColumnDef<EnquiryListItem>[] {
const statusMap = new Map(referenceData.statuses.map((item) => [item.id, item]));
const productTypeMap = new Map(referenceData.productTypes.map((item) => [item.id, item.label]));
return [
{
id: 'code',
accessorKey: 'code',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Code' />
<DataTableColumnHeader column={column} title='Opportunity Code' />
),
cell: ({ row }) => (
<div className='flex flex-col'>
@@ -184,92 +141,76 @@ function getEnquiryWorkspaceColumns({
</div>
),
meta: {
label: 'Code',
placeholder: 'Search enquiries...',
label: 'Opportunity',
placeholder: 'Search opportunities...',
variant: 'text' as const,
icon: Icons.search
},
enableColumnFilter: true
},
{
id: 'projectName',
accessorFn: (row) => row.projectName ?? row.title,
id: 'sourceLead',
accessorFn: (row) => (row.leadId ? 'linked' : 'direct'),
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Project' />
<DataTableColumnHeader column={column} title='Source Lead' />
),
cell: ({ row }) => <span>{row.original.projectName ?? row.original.title}</span>
cell: ({ row }) =>
row.original.leadId ? (
<Badge variant='outline'>{row.original.source ?? row.original.sourceLeadCode ?? 'Lead linked'}</Badge>
) : (
<span className='text-muted-foreground text-sm'>Direct sales</span>
)
},
{
id: 'customer',
accessorFn: (row) => row.customerId,
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='ลูกค้าผู้รับใบเสนอราคา' />
<DataTableColumnHeader column={column} title='Customer' />
),
cell: ({ row }) => <span>{row.original.customerName}</span>,
meta: {
label: 'ลูกค้าผู้รับใบเสนอราคา',
variant: 'multiSelect' as const,
label: 'Customer',
variant: 'select' as const,
options: referenceData.customers.map((item) => ({
value: item.id,
label: item.name
label: `${item.name} (${item.code})`
}))
},
enableColumnFilter: true
},
{
id: 'assignedSales',
id: 'projectName',
accessorFn: (row) => row.projectName ?? '',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Project Name' />
),
cell: ({ row }) => <span>{row.original.projectName ?? '-'}</span>
},
{
id: 'productType',
accessorFn: (row) => row.productType,
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Product Type' />
),
cell: ({ row }) => <span>{productTypeMap.get(row.original.productType) ?? row.original.productType}</span>,
meta: {
label: 'Product Type',
variant: 'multiSelect' as const,
options: referenceData.productTypes.map((item) => ({
value: item.id,
label: item.label
}))
},
enableColumnFilter: true
},
{
id: 'assignedToName',
accessorFn: (row) => row.assignedToName ?? '',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Enquiry Owner' />
<DataTableColumnHeader column={column} title='Sales Owner' />
),
cell: ({ row }) => <span>{row.original.assignedToName ?? '-'}</span>
},
{
id: 'chancePercent',
accessorKey: 'chancePercent',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Chance %' />
),
cell: ({ row }) => (
<span>{row.original.chancePercent !== null ? `${row.original.chancePercent}%` : '-'}</span>
)
},
{
id: 'estimatedValue',
accessorKey: 'estimatedValue',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Estimated Value' />
),
cell: ({ row }) => (
<span>
{row.original.estimatedValue !== null
? row.original.estimatedValue.toLocaleString()
: '-'}
</span>
)
},
{
id: 'quotationCount',
accessorKey: 'quotationCount',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Quotation Count' />
),
cell: ({ row }) => <span>{row.original.quotationCount}</span>
},
{
id: 'nextFollowupDate',
accessorKey: 'nextFollowupDate',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Next Follow-up' />
),
cell: ({ row }) => (
<span>
{row.original.nextFollowupDate
? new Date(row.original.nextFollowupDate).toLocaleDateString()
: '-'}
</span>
)
},
{
id: 'status',
accessorKey: 'status',
@@ -290,6 +231,60 @@ function getEnquiryWorkspaceColumns({
},
enableColumnFilter: true
},
{
id: 'estimatedValue',
accessorKey: 'estimatedValue',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Estimated Value' />
),
cell: ({ row }) => (
<span>
{row.original.estimatedValue !== null ? row.original.estimatedValue.toLocaleString() : '-'}
</span>
)
},
{
id: 'chancePercent',
accessorKey: 'chancePercent',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Chance %' />
),
cell: ({ row }) => <span>{row.original.chancePercent !== null ? `${row.original.chancePercent}%` : '-'}</span>
},
{
id: 'expectedCloseDate',
accessorKey: 'expectedCloseDate',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Expected Close' />
),
cell: ({ row }) => (
<span>
{row.original.expectedCloseDate
? new Date(row.original.expectedCloseDate).toLocaleDateString()
: '-'}
</span>
)
},
{
id: 'nextFollowupDate',
accessorKey: 'nextFollowupDate',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Next Follow-up' />
),
cell: ({ row }) => (
<span>
{row.original.nextFollowupDate ? new Date(row.original.nextFollowupDate).toLocaleDateString() : '-'}
</span>
)
},
{
id: 'createdAt',
accessorKey: 'createdAt',
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Created Date' />
),
cell: ({ row }) => <span>{new Date(row.original.createdAt).toLocaleDateString()}</span>
},
{
id: 'actions',
cell: ({ row }) => (
@@ -307,14 +302,6 @@ function getEnquiryWorkspaceColumns({
];
}
type SharedColumnsConfig = {
referenceData: EnquiryReferenceData;
canUpdate: boolean;
canDelete: boolean;
canAssign: boolean;
canReassign: boolean;
};
export function getEnquiryColumns({
referenceData,
workspace,
@@ -322,18 +309,7 @@ export function getEnquiryColumns({
canDelete,
canAssign,
canReassign
}: SharedColumnsConfig & {
workspace: 'lead' | 'enquiry';
}): ColumnDef<EnquiryListItem>[] {
const sharedConfig = {
referenceData,
canUpdate,
canDelete,
canAssign,
canReassign
};
return workspace === 'lead'
? getLeadColumns(sharedConfig)
: getEnquiryWorkspaceColumns(sharedConfig);
}: SharedColumnsConfig & { workspace: 'lead' | 'enquiry' }): ColumnDef<EnquiryListItem>[] {
const sharedConfig = { referenceData, canUpdate, canDelete, canAssign, canReassign };
return workspace === 'lead' ? getLeadColumns(sharedConfig) : getEnquiryWorkspaceColumns(sharedConfig);
}