lead menu
nquiry menu
This commit is contained in:
@@ -9,52 +9,116 @@ import type { EnquiryListItem, EnquiryReferenceData } from '../api/types';
|
||||
import { EnquiryCellAction } from './enquiry-cell-action';
|
||||
import { EnquiryStatusBadge } from './enquiry-status-badge';
|
||||
|
||||
export function getEnquiryColumns({
|
||||
function getLeadColumns({
|
||||
referenceData,
|
||||
canUpdate,
|
||||
canDelete,
|
||||
canAssign,
|
||||
canReassign
|
||||
}: {
|
||||
referenceData: EnquiryReferenceData;
|
||||
canUpdate: boolean;
|
||||
canDelete: boolean;
|
||||
canAssign: boolean;
|
||||
canReassign: boolean;
|
||||
}): ColumnDef<EnquiryListItem>[] {
|
||||
}: 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]));
|
||||
const priorityMap = new Map(referenceData.priorities.map((item) => [item.id, item]));
|
||||
const branchMap = new Map(referenceData.branches.map((item) => [item.id, item]));
|
||||
const leadChannelMap = new Map(referenceData.leadChannels.map((item) => [item.id, item.label]));
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'title',
|
||||
accessorKey: 'title',
|
||||
id: 'code',
|
||||
accessorKey: 'code',
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Enquiry' />
|
||||
<DataTableColumnHeader column={column} title='Code' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex flex-col'>
|
||||
<Link
|
||||
href={`/dashboard/crm/enquiries/${row.original.id}`}
|
||||
href={`/dashboard/crm/leads/${row.original.id}`}
|
||||
className='font-medium hover:underline'
|
||||
>
|
||||
{row.original.title}
|
||||
{row.original.code}
|
||||
</Link>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{row.original.code} • {row.original.customerName}
|
||||
</span>
|
||||
<span className='text-muted-foreground text-xs'>{row.original.title}</span>
|
||||
</div>
|
||||
),
|
||||
meta: {
|
||||
label: 'Enquiry',
|
||||
placeholder: 'Search enquiries...',
|
||||
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='Company' />
|
||||
),
|
||||
cell: ({ row }) => <span>{row.original.customerName}</span>,
|
||||
meta: {
|
||||
label: 'Company',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.customers.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name
|
||||
}))
|
||||
},
|
||||
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',
|
||||
@@ -76,79 +140,74 @@ export function getEnquiryColumns({
|
||||
enableColumnFilter: true
|
||||
},
|
||||
{
|
||||
id: 'productType',
|
||||
accessorKey: 'productType',
|
||||
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]));
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'code',
|
||||
accessorKey: 'code',
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Product Type' />
|
||||
<DataTableColumnHeader column={column} title='Code' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Badge variant='outline'>
|
||||
{productTypeMap.get(row.original.productType)?.label ?? 'Unknown'}
|
||||
</Badge>
|
||||
<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: 'Product Type',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.productTypes.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
label: 'Code',
|
||||
placeholder: 'Search enquiries...',
|
||||
variant: 'text' as const,
|
||||
icon: Icons.search
|
||||
},
|
||||
enableColumnFilter: true
|
||||
},
|
||||
{
|
||||
id: 'priority',
|
||||
accessorKey: 'priority',
|
||||
id: 'projectName',
|
||||
accessorFn: (row) => row.projectName ?? row.title,
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Priority' />
|
||||
<DataTableColumnHeader column={column} title='Project' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Badge variant='secondary'>
|
||||
{priorityMap.get(row.original.priority)?.label ?? 'Unknown'}
|
||||
</Badge>
|
||||
),
|
||||
meta: {
|
||||
label: 'Priority',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.priorities.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
},
|
||||
enableColumnFilter: true
|
||||
},
|
||||
{
|
||||
id: 'branch',
|
||||
accessorFn: (row) => row.branchId ?? '',
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Branch' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span>
|
||||
{row.original.branchId
|
||||
? (branchMap.get(row.original.branchId)?.name ?? 'Unknown')
|
||||
: 'Unassigned'}
|
||||
</span>
|
||||
),
|
||||
meta: {
|
||||
label: 'Branch',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.branches.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.name
|
||||
}))
|
||||
},
|
||||
enableColumnFilter: true
|
||||
cell: ({ row }) => <span>{row.original.projectName ?? row.original.title}</span>
|
||||
},
|
||||
{
|
||||
id: 'customer',
|
||||
accessorFn: (row) => row.customerId,
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Customer' />
|
||||
<DataTableColumnHeader column={column} title='Billing Customer' />
|
||||
),
|
||||
cell: ({ row }) => <span>{row.original.customerName}</span>,
|
||||
meta: {
|
||||
label: 'Customer',
|
||||
label: 'Billing Customer',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.customers.map((item) => ({
|
||||
value: item.id,
|
||||
@@ -159,25 +218,77 @@ export function getEnquiryColumns({
|
||||
},
|
||||
{
|
||||
id: 'assignedSales',
|
||||
header: 'Assigned Sales',
|
||||
accessorFn: (row) => row.assignedToName ?? '',
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Enquiry 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 }) => (
|
||||
<div className='flex flex-col'>
|
||||
<span>{row.original.assignedToName ?? 'Unassigned'}</span>
|
||||
{row.original.assignedAt ? (
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{new Date(row.original.assignedAt).toLocaleDateString()}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span>{row.original.chancePercent !== null ? `${row.original.chancePercent}%` : '-'}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'followupCount',
|
||||
accessorKey: 'followupCount',
|
||||
id: 'estimatedValue',
|
||||
accessorKey: 'estimatedValue',
|
||||
header: ({ column }: { column: Column<EnquiryListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Follow-ups' />
|
||||
<DataTableColumnHeader column={column} title='Estimated Value' />
|
||||
),
|
||||
cell: ({ row }) => <span>{row.original.followupCount}</span>
|
||||
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',
|
||||
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',
|
||||
@@ -185,6 +296,7 @@ export function getEnquiryColumns({
|
||||
<EnquiryCellAction
|
||||
data={row.original}
|
||||
referenceData={referenceData}
|
||||
workspace='enquiry'
|
||||
canUpdate={canUpdate}
|
||||
canDelete={canDelete}
|
||||
canAssign={canAssign}
|
||||
@@ -194,3 +306,34 @@ export function getEnquiryColumns({
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
type SharedColumnsConfig = {
|
||||
referenceData: EnquiryReferenceData;
|
||||
canUpdate: boolean;
|
||||
canDelete: boolean;
|
||||
canAssign: boolean;
|
||||
canReassign: boolean;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user