From 3dfb8d99ba3ea86a18f8a5e8ef65aa916690616a Mon Sep 17 00:00:00 2001 From: Nathnael Date: Sat, 20 Jun 2026 10:45:22 +0000 Subject: [PATCH] style: finish revamp of the warehouse page --- .../warehouses/WarehouseInquiryTable.tsx | 118 +++++----- .../warehouses/WarehouseInventoryTable.tsx | 222 +++++++++--------- .../components/warehouses/WarehouseTable.tsx | 153 ++++++------ .../src/pages/warehouses/ArrivalQueuePage.tsx | 206 ++++++++-------- .../pages/warehouses/LoadedInventoryPage.tsx | 113 +++++---- .../src/pages/warehouses/LoadingQueuePage.tsx | 121 +++++----- .../warehouses/WarehouseInvoicesPage.tsx | 93 +++++--- .../pages/warehouses/WarehouseRulesPage.tsx | 157 +++++++------ 8 files changed, 631 insertions(+), 552 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInquiryTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInquiryTable.tsx index 8a282957a..366a3950c 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInquiryTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInquiryTable.tsx @@ -1,4 +1,5 @@ -import { Stack, Table, Text } from '@mantine/core'; +import { Stack, Text } from '@mantine/core'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import type { InventoryInquiryResult } from '@/types/warehouse'; import { InventoryStatusBadge } from './badges'; @@ -16,70 +17,61 @@ const itemDescriptor = (result: InventoryInquiryResult) => { return '—'; }; -export function WarehouseInquiryTable({ results }: WarehouseInquiryTableProps) { - if (results.length === 0) { - return ( - - No matching items. Adjust your search to locate cargo, containers or goods. +const columns: ColumnDef[] = [ + { + id: 'booking', + header: 'Booking', + cell: ({ row }) => ( + + {row.original.bookingNumber ?? row.original.bookingId.slice(0, 8)} - ); - } + ), + }, + { id: 'customer', header: 'Customer', cell: ({ row }) => row.original.customerName ?? '—' }, + { id: 'item', header: 'Item', cell: ({ row }) => itemDescriptor(row.original) }, + { + id: 'warehouse', + header: 'Warehouse', + cell: ({ row }) => ( + + {row.original.warehouse?.name ?? '—'} + {row.original.warehouse?.code && ( + + {row.original.warehouse.code} + + )} + + ), + }, + { id: 'yard', header: 'Yard', cell: ({ row }) => row.original.yard?.name ?? '—' }, + { id: 'zone', header: 'Zone', cell: ({ row }) => row.original.zone?.name ?? '—' }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => , + }, + { id: 'qty', header: 'Qty', cell: ({ row }) => formatNumber(row.original.quantity) }, + { id: 'weight', header: 'Weight', cell: ({ row }) => formatNumber(row.original.weight) }, + { + id: 'arrived', + header: 'Arrived', + cell: ({ row }) => {formatDate(row.original.arrivedAt)}, + }, + { + id: 'ready', + header: 'Ready', + cell: ({ row }) => {formatDate(row.original.readyForLoadingAt)}, + }, +]; +export function WarehouseInquiryTable({ results }: WarehouseInquiryTableProps) { return ( - - - - - Booking - Customer - Item - Warehouse - Yard - Zone - Status - Qty - Weight - Arrived - Ready - - - - {results.map((result) => ( - - - - {result.bookingNumber ?? result.bookingId.slice(0, 8)} - - - {result.customerName ?? '—'} - {itemDescriptor(result)} - - - {result.warehouse?.name ?? '—'} - {result.warehouse?.code && ( - - {result.warehouse.code} - - )} - - - {result.yard?.name ?? '—'} - {result.zone?.name ?? '—'} - - - - {formatNumber(result.quantity)} - {formatNumber(result.weight)} - - {formatDate(result.arrivedAt)} - - - {formatDate(result.readyForLoadingAt)} - - - ))} - -
-
+ ); } diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx index 11d9d323f..0fc8669bf 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx @@ -1,5 +1,7 @@ -import { ActionIcon, Badge, Button, Group, Table, Text, Tooltip } from '@mantine/core'; +import { useMemo } from 'react'; +import { ActionIcon, Badge, Button, Group, Text, Tooltip } from '@mantine/core'; import { ArrowRightLeft, ClipboardList, Coins, History } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import type { InventoryAction, WarehouseInventoryItem } from '@/types/warehouse'; import { INVENTORY_NEXT_ACTION } from '@/types/warehouse'; @@ -40,115 +42,117 @@ export function WarehouseInventoryTable({ onInspect, onFeePreview, }: WarehouseInventoryTableProps) { - if (items.length === 0) { - return ( - - No inventory items found. - - ); - } + const columns = useMemo[]>( + () => [ + { + id: 'booking', + header: 'Booking', + cell: ({ row }) => + row.original.bookingId ? ( + + + {row.original.bookingId.slice(0, 8)}… + + + ) : ( + + — + + ), + }, + { + id: 'facility', + header: 'Facility', + cell: ({ row }) => row.original.warehouse?.facility?.name ?? '—', + }, + { id: 'warehouse', header: 'Warehouse', cell: ({ row }) => row.original.warehouse?.code ?? '—' }, + { id: 'yard', header: 'Yard', cell: ({ row }) => row.original.yard?.code ?? '—' }, + { id: 'zone', header: 'Zone', cell: ({ row }) => row.original.zone?.code ?? '—' }, + { + id: 'item', + header: 'Item', + cell: ({ row }) => { + const kind = itemKind(row.original); + return ( + + {kind.label} + + ); + }, + }, + { id: 'qty', header: 'Qty', cell: ({ row }) => formatNumber(row.original.quantity) }, + { id: 'weight', header: 'Weight', cell: ({ row }) => formatNumber(row.original.weight) }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => , + }, + { + id: 'arrived', + header: 'Arrived', + cell: ({ row }) => {formatDate(row.original.arrivedAt)}, + }, + { + id: 'actions', + header: '', + cell: ({ row }) => { + const item = row.original; + const busy = busyId === item.id; + const nextAction = INVENTORY_NEXT_ACTION[item.status]; + return ( + e.stopPropagation()}> + {nextAction && ( + + )} + {item.status !== 'DISPATCHED' && ( + + onMove(item)}> + + + + )} + {onInspect && ( + + onInspect(item)}> + + + + )} + {onFeePreview && ( + + onFeePreview(item)}> + + + + )} + + onHistory(item)}> + + + + + ); + }, + }, + ], + [busyId, onAdvance, onMove, onHistory, onInspect, onFeePreview], + ); return ( - - - - - Booking - Facility - Warehouse - Yard - Zone - Item - Qty - Weight - Status - Arrived - Actions - - - - {items.map((item) => { - const kind = itemKind(item); - const busy = busyId === item.id; - const nextAction = INVENTORY_NEXT_ACTION[item.status]; - return ( - - - {item.bookingId ? ( - - - {item.bookingId.slice(0, 8)}… - - - ) : ( - - — - - )} - - {item.warehouse?.facility?.name ?? '—'} - {item.warehouse?.code ?? '—'} - {item.yard?.code ?? '—'} - {item.zone?.code ?? '—'} - - - {kind.label} - - - {formatNumber(item.quantity)} - {formatNumber(item.weight)} - - - - - {formatDate(item.arrivedAt)} - - - - {nextAction && ( - - )} - {item.status !== 'DISPATCHED' && ( - - onMove(item)}> - - - - )} - {onInspect && ( - - onInspect(item)}> - - - - )} - {onFeePreview && ( - - onFeePreview(item)}> - - - - )} - - onHistory(item)}> - - - - - - - ); - })} - -
-
+ ); } diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseTable.tsx index fbd3a94c1..80b85f693 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseTable.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react'; -import { ActionIcon, Anchor, Group, Table, Text } from '@mantine/core'; +import { ActionIcon, Group, Text } from '@mantine/core'; import { Eye, Pencil } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import { useStations } from '@/hooks/useStations'; import type { Warehouse } from '@/types/warehouse'; @@ -20,73 +21,91 @@ export function WarehouseTable({ warehouses, onView, onEdit }: WarehouseTablePro [stations], ); - if (warehouses.length === 0) { - return ( - - No warehouses found. - - ); - } + const columns: ColumnDef[] = [ + { + id: 'code', + header: 'Code', + cell: ({ row }) => ( + onView(row.original)} + > + {row.original.code} + + ), + }, + { id: 'name', header: 'Name', cell: ({ row }) => row.original.name }, + { + id: 'facility', + header: 'Facility', + cell: ({ row }) => { + const name = row.original.stationId + ? stationNameById.get(row.original.stationId) + : undefined; + return name ? ( + + {name} + + ) : ( + + — + + ); + }, + }, + { + id: 'type', + header: 'Type', + cell: ({ row }) => , + }, + { + id: 'location', + header: 'Location', + cell: ({ row }) => row.original.locationName ?? '—', + }, + { + id: 'weight', + header: 'Weight (cur / cap)', + cell: ({ row }) => formatCapacity(row.original.currentWeight, row.original.capacityWeight), + }, + { + id: 'containers', + header: 'Containers (cur / cap)', + cell: ({ row }) => + formatCapacity(row.original.currentContainers, row.original.capacityContainers), + }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => , + }, + { + id: 'actions', + header: '', + cell: ({ row }) => ( + e.stopPropagation()}> + onView(row.original)} title="View"> + + + onEdit(row.original)} title="Edit"> + + + + ), + }, + ]; return ( - - - - - Code - Name - Facility - Type - Location - Weight (cur / cap) - Containers (cur / cap) - Status - Actions - - - - {warehouses.map((warehouse) => ( - - - onView(warehouse)}> - {warehouse.code} - - - {warehouse.name} - - {warehouse.stationId && stationNameById.get(warehouse.stationId) ? ( - - {stationNameById.get(warehouse.stationId)} - - ) : ( - - — - - )} - - - - - {warehouse.locationName ?? '—'} - {formatCapacity(warehouse.currentWeight, warehouse.capacityWeight)} - {formatCapacity(warehouse.currentContainers, warehouse.capacityContainers)} - - - - - - onView(warehouse)} title="View"> - - - onEdit(warehouse)} title="Edit"> - - - - - - ))} - -
-
+ onView(warehouse)} + emptyMessage="No warehouses found." + containerClassName="border-0 shadow-none" + /> ); } diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/ArrivalQueuePage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/ArrivalQueuePage.tsx index 55eed2699..92165f408 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/ArrivalQueuePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/ArrivalQueuePage.tsx @@ -5,11 +5,10 @@ import { Button, Card, Group, - Loader, - Table, Text, } from '@mantine/core'; import { ClipboardList, Eye, PackageOpen, Truck } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import { PageContainer, PageHeader } from '@/components/page'; import { @@ -60,6 +59,96 @@ export default function ArrivalQueuePage() { } }; + const columns: ColumnDef[] = [ + { + id: 'booking', + header: 'Booking', + cell: ({ row }) => ( + + {row.original.bookingReference} + + ), + }, + { id: 'customer', header: 'Customer', cell: ({ row }) => row.original.customer ?? '—' }, + { + id: 'cargo', + header: 'Cargo / Container', + cell: ({ row }) => row.original.container ?? row.original.cargo ?? '—', + }, + { + id: 'arrival', + header: 'Arrival', + cell: ({ row }) => {formatDate(row.original.arrivalDate)}, + }, + { id: 'facility', header: 'Facility', cell: ({ row }) => row.original.facility ?? '—' }, + { id: 'warehouse', header: 'Warehouse', cell: ({ row }) => row.original.warehouse ?? '—' }, + { id: 'yard', header: 'Yard', cell: ({ row }) => row.original.yard ?? '—' }, + { id: 'zone', header: 'Zone', cell: ({ row }) => row.original.zone ?? '—' }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => + row.original.unloaded ? ( + + {row.original.currentStatus ?? 'RECEIVED'} + + ) : ( + + Not unloaded + + ), + }, + { + id: 'inspection', + header: 'Inspection', + cell: ({ row }) => inspectionBadge(row.original.inspectionStatus), + }, + { + id: 'actions', + header: '', + cell: ({ row }) => { + const item = row.original; + return ( + e.stopPropagation()}> + {!item.unloaded && ( + + )} + {item.inventoryId && ( + + )} + {item.inventoryId && ( + + )} + + ); + }, + }, + ]; + return ( - {isLoading ? ( - - - - ) : items.length === 0 ? ( - - ) : ( - - - - - Booking - Customer - Cargo / Container - Arrival - Facility - Warehouse - Yard - Zone - Status - Inspection - Actions - - - - {items.map((item) => ( - - - {item.bookingReference} - - {item.customer ?? '—'} - {item.container ?? item.cargo ?? '—'} - - {formatDate(item.arrivalDate)} - - {item.facility ?? '—'} - {item.warehouse ?? '—'} - {item.yard ?? '—'} - {item.zone ?? '—'} - - {item.unloaded ? ( - - {item.currentStatus ?? 'RECEIVED'} - - ) : ( - - Not unloaded - - )} - - {inspectionBadge(item.inspectionStatus)} - - - {!item.unloaded && ( - - )} - {item.inventoryId && ( - - )} - {item.inventoryId && ( - - )} - - - - ))} - -
-
- )} + {!isLoading && items.length === 0 ? ( + + ) : ( + + )} ['data']>[number]; + +const columns: ColumnDef[] = [ + { + id: 'wagon', + header: 'Wagon', + cell: ({ row }) => ( + + + + {row.original.wagonNumber ?? row.original.wagonId.slice(0, 8)} + + + ), + }, + { + id: 'warehouse', + header: 'Warehouse', + cell: ({ row }) => + row.original.inventory?.warehouse + ? `${row.original.inventory.warehouse.name} (${row.original.inventory.warehouse.code})` + : '—', + }, + { + id: 'zone', + header: 'Zone', + cell: ({ row }) => (row.original.inventory?.zone ? row.original.inventory.zone.name : '—'), + }, + { + id: 'weight', + header: 'Loaded Weight (kg)', + cell: ({ row }) => formatNumber(row.original.loadedWeight), + }, + { + id: 'loadedAt', + header: 'Loaded At', + cell: ({ row }) => formatDate(row.original.loadedAt), + }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => ( + + {row.original.inventory?.status ?? 'LOADED'} + + ), + }, +]; + /** Record of every inventory item loaded onto a wagon. */ export default function LoadedInventoryPage() { const { data, isLoading } = useWarehouseLoadings(); @@ -17,62 +71,19 @@ export default function LoadedInventoryPage() { /> - {isLoading ? ( - - - - ) : loadings.length === 0 ? ( + {!isLoading && loadings.length === 0 ? ( ) : ( - - - - - Wagon - Warehouse - Zone - Loaded Weight (kg) - Loaded At - Status - - - - {loadings.map((l) => ( - - - - - - {l.wagonNumber ?? l.wagonId.slice(0, 8)} - - - - - {l.inventory?.warehouse - ? `${l.inventory.warehouse.name} (${l.inventory.warehouse.code})` - : '—'} - - {l.inventory?.zone ? l.inventory.zone.name : '—'} - {formatNumber(l.loadedWeight)} - {formatDate(l.loadedAt)} - - - {l.inventory?.status ?? 'LOADED'} - - - - ))} - -
-
+ )}
diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/LoadingQueuePage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/LoadingQueuePage.tsx index 9f8e6a64d..f7a83969e 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/LoadingQueuePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/LoadingQueuePage.tsx @@ -1,7 +1,8 @@ import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Badge, Button, Card, Group, Table, Tabs, Text } from '@mantine/core'; +import { Badge, Button, Card, Group, Tabs, Text } from '@mantine/core'; import { CreditCard, Eye, Truck } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import { PageContainer, PageHeader } from '@/components/page'; import { @@ -176,63 +177,67 @@ interface PendingPaymentTableProps { /** Read-only view of unpaid ready-for-loading items. No Mark-as-Loaded action. */ function PendingPaymentTable({ items, onNavigate }: PendingPaymentTableProps) { + const columns: ColumnDef[] = [ + { + id: 'booking', + header: 'Booking', + cell: ({ row }) => ( + + {row.original.booking?.reference ?? row.original.bookingId?.slice(0, 8) ?? '—'} + + ), + }, + { id: 'warehouse', header: 'Warehouse', cell: ({ row }) => row.original.warehouse?.code ?? '—' }, + { id: 'zone', header: 'Zone', cell: ({ row }) => row.original.zone?.code ?? '—' }, + { id: 'weight', header: 'Weight (kg)', cell: ({ row }) => formatNumber(row.original.weight) }, + { + id: 'payment', + header: 'Payment', + cell: ({ row }) => ( + + {row.original.booking?.status ?? row.original.booking?.paymentStatus ?? 'UNPAID'} + + ), + }, + { + id: 'actions', + header: '', + cell: ({ row }) => { + const item = row.original; + return ( + e.stopPropagation()}> + + + + ); + }, + }, + ]; + return ( - - - - - Booking - Warehouse - Zone - Weight (kg) - Payment - Actions - - - - {items.map((item) => ( - - - - {item.booking?.reference ?? item.bookingId?.slice(0, 8) ?? '—'} - - - {item.warehouse?.code ?? '—'} - {item.zone?.code ?? '—'} - {formatNumber(item.weight)} - - - {item.booking?.status ?? item.booking?.paymentStatus ?? 'UNPAID'} - - - - - - - - - - ))} - -
-
+ ); } diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseInvoicesPage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseInvoicesPage.tsx index ee1303a47..7a4ca90f2 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseInvoicesPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseInvoicesPage.tsx @@ -16,6 +16,7 @@ import { TextInput, } from '@mantine/core'; import { Ban, CreditCard, Eye, Search } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import { PageContainer, PageHeader } from '@/components/page'; import { useToast } from '@/hooks/use-toast'; @@ -56,6 +57,56 @@ export default function WarehouseInvoicesPage() { return invoices.filter((i) => [i.invoiceNumber, i.bookingId, i.customerId].join(' ').toLowerCase().includes(q)); }, [invoices, search]); + const invoiceColumns: ColumnDef[] = [ + { + id: 'invoiceNumber', + header: 'Invoice No', + cell: ({ row }) => ( + + {row.original.invoiceNumber} + + ), + }, + { id: 'type', header: 'Type', cell: ({ row }) => row.original.invoiceType.replace(/_/g, ' ') }, + { id: 'total', header: 'Total', cell: ({ row }) => fmt(row.original.totalAmount, row.original.currency) }, + { id: 'paid', header: 'Paid', cell: ({ row }) => fmt(row.original.paidAmount, row.original.currency) }, + { + id: 'balance', + header: 'Balance', + cell: ({ row }) => fmt(row.original.balanceAmount, row.original.currency), + }, + { + id: 'status', + header: 'Status', + cell: ({ row }) => ( + + {row.original.status.replace(/_/g, ' ')} + + ), + }, + { + id: 'issued', + header: 'Issued', + cell: ({ row }) => {fmtDate(row.original.issuedAt)}, + }, + { + id: 'actions', + header: '', + cell: ({ row }) => ( + e.stopPropagation()}> + setDetailId(row.original.id)} + title="View" + > + + + + ), + }, + ]; + return ( - {isLoading ? ( - - ) : filtered.length === 0 ? ( - No invoices found. - ) : ( - - - - - Invoice NoTypeTotal - PaidBalanceStatus - IssuedActions - - - - {filtered.map((inv) => ( - - {inv.invoiceNumber} - {inv.invoiceType.replace(/_/g, ' ')} - {fmt(inv.totalAmount, inv.currency)} - {fmt(inv.paidAmount, inv.currency)} - {fmt(inv.balanceAmount, inv.currency)} - {inv.status.replace(/_/g, ' ')} - {fmtDate(inv.issuedAt)} - - setDetailId(inv.id)} title="View"> - - - - - ))} - -
-
- )} + setDetailId(null)} /> diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseRulesPage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseRulesPage.tsx index 9e9e5b522..ed858fdcd 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseRulesPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseRulesPage.tsx @@ -5,17 +5,16 @@ import { Button, Card, Group, - Loader, Modal, NumberInput, Select, Stack, - Table, Tabs, Text, TextInput, } from '@mantine/core'; import { Plus, Trash2 } from 'lucide-react'; +import { DataTable, type ColumnDef } from '@edr/ui-common'; import { PageContainer, PageHeader } from '@/components/page'; import { useToast } from '@/hooks/use-toast'; @@ -105,45 +104,52 @@ function AllocationRules() { setForm({ name: '', priority: 100, freightType: '', tradeDirection: '', cargoTypeCode: '', containerStatus: '', targetYardCode: '', storageType: '' }); }; + const columns: ColumnDef<(typeof rules)[number]>[] = [ + { id: 'priority', header: 'Priority', cell: ({ row }) => row.original.priority }, + { id: 'name', header: 'Name', cell: ({ row }) => row.original.name }, + { id: 'freight', header: 'Freight', cell: ({ row }) => row.original.freightType ?? '—' }, + { id: 'trade', header: 'Trade', cell: ({ row }) => row.original.tradeDirection ?? '—' }, + { id: 'cargo', header: 'Cargo code', cell: ({ row }) => row.original.cargoTypeCode ?? '—' }, + { + id: 'targetYard', + header: 'Target yard', + cell: ({ row }) => {row.original.targetYardCode}, + }, + { + id: 'active', + header: 'Active', + cell: ({ row }) => ( + + {row.original.isActive ? 'Yes' : 'No'} + + ), + }, + { + id: 'actions', + header: '', + cell: ({ row }) => ( + e.stopPropagation()}> + remove.mutate(row.original.id)} title="Delete"> + + + + ), + }, + ]; + return ( <> {rules.length} rule(s) — matched by ascending priority - {isLoading ? ( - - ) : ( - - - - - PriorityNameFreight - TradeCargo codeTarget yard - ActiveActions - - - - {rules.map((r) => ( - - {r.priority} - {r.name} - {r.freightType ?? '—'} - {r.tradeDirection ?? '—'} - {r.cargoTypeCode ?? '—'} - {r.targetYardCode} - {r.isActive ? 'Yes' : 'No'} - - remove.mutate(r.id)} title="Delete"> - - - - - ))} - -
-
- )} + setOpen(false)} title="New allocation rule" centered size="lg"> @@ -211,45 +217,60 @@ function FeeRules() { setOpen(false); }; + const columns: ColumnDef<(typeof rules)[number]>[] = [ + { + id: 'type', + header: 'Type', + cell: ({ row }) => ( + + {row.original.ruleType === 'DEMURRAGE_FEE' ? 'Demurrage' : 'Storage'} + + ), + }, + { id: 'name', header: 'Name', cell: ({ row }) => row.original.name }, + { id: 'freight', header: 'Freight', cell: ({ row }) => row.original.freightType ?? '—' }, + { id: 'trade', header: 'Trade', cell: ({ row }) => row.original.tradeDirection ?? '—' }, + { id: 'freeDays', header: 'Free days', cell: ({ row }) => row.original.freeDays }, + { + id: 'rate', + header: 'Rate / day', + cell: ({ row }) => `${Number(row.original.ratePerDay).toLocaleString()} ${row.original.currency}`, + }, + { + id: 'active', + header: 'Active', + cell: ({ row }) => ( + + {row.original.isActive ? 'Yes' : 'No'} + + ), + }, + { + id: 'actions', + header: '', + cell: ({ row }) => ( + e.stopPropagation()}> + remove.mutate(row.original.id)} title="Delete"> + + + + ), + }, + ]; + return ( <> {rules.length} rule(s) — most specific match applies - {isLoading ? ( - - ) : ( - - - - - TypeNameFreight - TradeFree daysRate / day - ActiveActions - - - - {rules.map((r) => ( - - {r.ruleType === 'DEMURRAGE_FEE' ? 'Demurrage' : 'Storage'} - {r.name} - {r.freightType ?? '—'} - {r.tradeDirection ?? '—'} - {r.freeDays} - {Number(r.ratePerDay).toLocaleString()} {r.currency} - {r.isActive ? 'Yes' : 'No'} - - remove.mutate(r.id)} title="Delete"> - - - - - ))} - -
-
- )} + setOpen(false)} title="New fee rule" centered size="lg">