mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
165 lines
5.7 KiB
TypeScript
165 lines
5.7 KiB
TypeScript
import { useCallback, useRef } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { ArrowRight, Calendar, Package, User } from "lucide-react";
|
|
import { Group } from "@mantine/core";
|
|
|
|
import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu";
|
|
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
|
|
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
|
import { SchedulingStatusBadge } from "@/components/trainScheduling/ScheduleStatusBadge";
|
|
import { bookingTable } from "@/components/bookings/booking-ui.styles";
|
|
import type { BookingListRow } from "@/types/booking";
|
|
import { cn } from "@/lib/utils";
|
|
import { Badge, DataTable, type ColumnDef } from "@edr/ui-common";
|
|
|
|
export function OperationsBookingQueue({
|
|
bookings,
|
|
isLoading,
|
|
onAllocate,
|
|
}: {
|
|
bookings: BookingListRow[];
|
|
isLoading?: boolean;
|
|
onAllocate: (bookingIds: string[]) => void;
|
|
}) {
|
|
const navigate = useNavigate();
|
|
const suppressRowClickRef = useRef(false);
|
|
|
|
const suppressRowClick = useCallback(() => {
|
|
suppressRowClickRef.current = true;
|
|
window.setTimeout(() => {
|
|
suppressRowClickRef.current = false;
|
|
}, 400);
|
|
}, []);
|
|
|
|
const handleRowClick = useCallback(
|
|
(row: BookingListRow) => {
|
|
if (suppressRowClickRef.current) return;
|
|
navigate(`/dashboard/booking-requests/${row.id}`);
|
|
},
|
|
[navigate],
|
|
);
|
|
|
|
const columns: ColumnDef<BookingListRow>[] = [
|
|
{
|
|
id: "booking",
|
|
header: () => <span className={bookingTable.headerCell}>Booking</span>,
|
|
cell: ({ row }) => {
|
|
const booking = row.original;
|
|
return (
|
|
<div className="flex items-center gap-3 py-1.5">
|
|
<div className={bookingTable.rowIcon}>
|
|
<Package className="size-4" strokeWidth={1.75} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<Group gap={6} wrap="nowrap">
|
|
<p className="truncate font-medium text-foreground">{booking.reference}</p>
|
|
{booking.isGovernment ? (
|
|
<Badge variant="secondary" className="h-5 px-1.5 text-[10px]">
|
|
Government
|
|
</Badge>
|
|
) : null}
|
|
</Group>
|
|
<p className="mt-0.5 flex items-center gap-1 truncate text-xs text-muted-foreground">
|
|
<User className="size-3 shrink-0 opacity-70" />
|
|
{booking.customerLabel}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "route",
|
|
header: () => <span className={bookingTable.headerCell}>Route</span>,
|
|
cell: ({ row }) => {
|
|
const booking = row.original;
|
|
return (
|
|
<div className="space-y-1 py-1">
|
|
<div className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
|
<span className="max-w-[8rem] truncate">{booking.originLabel}</span>
|
|
<ArrowRight className="size-3.5 shrink-0 text-muted-foreground" />
|
|
<span className="max-w-[8rem] truncate">{booking.destinationLabel}</span>
|
|
</div>
|
|
<div className="flex gap-1.5">
|
|
<Badge variant="outline" className="h-5 px-1.5 text-[10px] uppercase">
|
|
{booking.tradeDirection}
|
|
</Badge>
|
|
<Badge variant="secondary" className="h-5 px-1.5 text-[10px]">
|
|
{booking.freightType}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: "status",
|
|
header: () => <span className={bookingTable.headerCell}>Status</span>,
|
|
cell: ({ row }) => (
|
|
<div className="space-y-1 py-1">
|
|
<BookingStatusBadge status={row.original.status} />
|
|
{row.original.schedulingStatus ? (
|
|
<SchedulingStatusBadge status={row.original.schedulingStatus} />
|
|
) : null}
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
id: "scheduled",
|
|
header: () => <span className={bookingTable.headerCell}>Scheduled</span>,
|
|
cell: ({ row }) => (
|
|
<span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
<Calendar className="size-3.5" />
|
|
{row.original.scheduledDate}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: "priority",
|
|
header: () => <span className={bookingTable.headerCell}>Priority</span>,
|
|
cell: ({ row }) => <BookingPriorityBadge score={row.original.priorityScore} />,
|
|
},
|
|
{
|
|
id: "amount",
|
|
header: () => <span className={bookingTable.headerCell}>Amount</span>,
|
|
cell: ({ row }) => (
|
|
<span className="font-mono text-sm font-semibold tabular-nums text-foreground">
|
|
{row.original.paymentCurrency}{" "}
|
|
{row.original.totalAmount.toLocaleString(undefined, {
|
|
minimumFractionDigits: 2,
|
|
})}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
id: "actions",
|
|
header: () => <span className={bookingTable.headerCell}>Actions</span>,
|
|
cell: ({ row }) => (
|
|
<BookingActionsMenu
|
|
row={row.original}
|
|
variant="table"
|
|
onSuppressRowClick={suppressRowClick}
|
|
onAllocateBooking={() => onAllocate([row.original.id])}
|
|
/>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<DataTable
|
|
columns={columns}
|
|
data={bookings}
|
|
status={isLoading ? "loading" : "success"}
|
|
emptyMessage="No PAID bookings ready to load."
|
|
onRowClick={handleRowClick}
|
|
containerClassName={cn(
|
|
"border-0 shadow-none",
|
|
"[&_thead_tr]:border-b [&_thead_tr]:border-border/50",
|
|
"[&_thead_th]:bg-muted/20 [&_thead_th]:backdrop-blur-sm",
|
|
"[&_tbody_tr]:group/tr [&_tbody_tr]:cursor-pointer [&_tbody_tr]:border-b [&_tbody_tr]:border-border/30",
|
|
"[&_tbody_tr]:transition-colors [&_tbody_tr:hover]:bg-muted/20",
|
|
)}
|
|
/>
|
|
);
|
|
}
|