316 lines
10 KiB
TypeScript
316 lines
10 KiB
TypeScript
'use client';
|
|
|
|
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 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,
|
|
canDelete,
|
|
canAssign,
|
|
canReassign
|
|
}: SharedColumnsConfig): ColumnDef<EnquiryListItem>[] {
|
|
const statusMap = new Map(referenceData.statuses.map((item) => [item.id, item]));
|
|
const leadChannelMap = new Map(referenceData.leadChannels.map((item) => [item.id, item.label]));
|
|
|
|
return [
|
|
{
|
|
id: 'code',
|
|
accessorKey: 'code',
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Code' />
|
|
),
|
|
cell: ({ row }) => (
|
|
<div className='flex flex-col'>
|
|
<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>
|
|
</div>
|
|
),
|
|
meta: {
|
|
label: 'Code',
|
|
placeholder: 'Search leads...',
|
|
variant: 'text' as const,
|
|
icon: Icons.search
|
|
},
|
|
enableColumnFilter: true
|
|
},
|
|
{
|
|
id: 'leadChannel',
|
|
accessorFn: (row) => row.leadChannel ?? '',
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Lead Source' />
|
|
),
|
|
cell: ({ row }) => <span>{leadChannelMap.get(row.original.leadChannel ?? '') ?? '-'}</span>
|
|
},
|
|
{
|
|
id: 'customer',
|
|
accessorFn: (row) => row.customerId,
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Customer' />
|
|
),
|
|
cell: ({ row }) => <span>{row.original.customerName}</span>,
|
|
meta: {
|
|
label: 'Customer',
|
|
variant: 'select' as const,
|
|
options: referenceData.customers.map((item) => ({
|
|
value: item.id,
|
|
label: `${item.name} (${item.code})`
|
|
}))
|
|
},
|
|
enableColumnFilter: true
|
|
},
|
|
{
|
|
id: 'status',
|
|
accessorKey: 'status',
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Status' />
|
|
),
|
|
cell: ({ row }) => {
|
|
const status = statusMap.get(row.original.status);
|
|
return <EnquiryStatusBadge code={status?.code} label={status?.label ?? 'Unknown'} />;
|
|
},
|
|
meta: {
|
|
label: 'Status',
|
|
variant: 'multiSelect' as const,
|
|
options: referenceData.statuses.map((item) => ({
|
|
value: item.id,
|
|
label: item.label
|
|
}))
|
|
},
|
|
enableColumnFilter: true
|
|
},
|
|
{
|
|
id: 'actions',
|
|
cell: ({ row }) => (
|
|
<EnquiryCellAction
|
|
data={row.original}
|
|
referenceData={referenceData}
|
|
workspace='lead'
|
|
canUpdate={canUpdate}
|
|
canDelete={canDelete}
|
|
canAssign={canAssign}
|
|
canReassign={canReassign}
|
|
/>
|
|
)
|
|
}
|
|
];
|
|
}
|
|
|
|
function getEnquiryWorkspaceColumns({
|
|
referenceData,
|
|
canUpdate,
|
|
canDelete,
|
|
canAssign,
|
|
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='Opportunity Code' />
|
|
),
|
|
cell: ({ row }) => (
|
|
<div className='flex flex-col'>
|
|
<Link
|
|
href={`/dashboard/crm/enquiries/${row.original.id}`}
|
|
className='font-medium hover:underline'
|
|
>
|
|
{row.original.code}
|
|
</Link>
|
|
<span className='text-muted-foreground text-xs'>{row.original.title}</span>
|
|
</div>
|
|
),
|
|
meta: {
|
|
label: 'Opportunity',
|
|
placeholder: 'Search opportunities...',
|
|
variant: 'text' as const,
|
|
icon: Icons.search
|
|
},
|
|
enableColumnFilter: true
|
|
},
|
|
{
|
|
id: 'sourceLead',
|
|
accessorFn: (row) => (row.leadId ? 'linked' : 'direct'),
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Source Lead' />
|
|
),
|
|
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='Customer' />
|
|
),
|
|
cell: ({ row }) => <span>{row.original.customerName}</span>,
|
|
meta: {
|
|
label: 'Customer',
|
|
variant: 'select' as const,
|
|
options: referenceData.customers.map((item) => ({
|
|
value: item.id,
|
|
label: `${item.name} (${item.code})`
|
|
}))
|
|
},
|
|
enableColumnFilter: true
|
|
},
|
|
{
|
|
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='Sales Owner' />
|
|
),
|
|
cell: ({ row }) => <span>{row.original.assignedToName ?? '-'}</span>
|
|
},
|
|
{
|
|
id: 'status',
|
|
accessorKey: 'status',
|
|
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
|
<DataTableColumnHeader column={column} title='Status' />
|
|
),
|
|
cell: ({ row }) => {
|
|
const status = statusMap.get(row.original.status);
|
|
return <EnquiryStatusBadge code={status?.code} label={status?.label ?? 'Unknown'} />;
|
|
},
|
|
meta: {
|
|
label: 'Status',
|
|
variant: 'multiSelect' as const,
|
|
options: referenceData.statuses.map((item) => ({
|
|
value: item.id,
|
|
label: item.label
|
|
}))
|
|
},
|
|
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 }) => (
|
|
<EnquiryCellAction
|
|
data={row.original}
|
|
referenceData={referenceData}
|
|
workspace='enquiry'
|
|
canUpdate={canUpdate}
|
|
canDelete={canDelete}
|
|
canAssign={canAssign}
|
|
canReassign={canReassign}
|
|
/>
|
|
)
|
|
}
|
|
];
|
|
}
|
|
|
|
export function getEnquiryColumns({
|
|
referenceData,
|
|
workspace,
|
|
canUpdate,
|
|
canDelete,
|
|
canAssign,
|
|
canReassign
|
|
}: SharedColumnsConfig & { workspace: 'lead' | 'enquiry' }): ColumnDef<EnquiryListItem>[] {
|
|
const sharedConfig = { referenceData, canUpdate, canDelete, canAssign, canReassign };
|
|
return workspace === 'lead' ? getLeadColumns(sharedConfig) : getEnquiryWorkspaceColumns(sharedConfig);
|
|
}
|