import { ActionIcon, Box, Button, Card, Group, MultiSelect, Select, Stack, Tabs, Text, TextInput, } from "@mantine/core"; import { DateInput } from "@mantine/dates"; import { AlertTriangle, ArrowRight, Calendar, CheckCircle2, Clock, FilterX, LayoutList, Package, Plus, RefreshCw, Search, User, X, } from "lucide-react"; import { useCallback, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu"; import { BookingApprovalProgressCell } from "@/components/bookings/BookingApprovalProgressCell"; import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge"; import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge"; // BookingStatusTabs / Operations* queues removed — replaced by booking-kind tabs. import { BookingTableEmpty } from "@/components/bookings/BookingTableEmpty"; import { bookingTable } from "@/components/bookings/booking-ui.styles"; import { KpiStrip, PageContainer, PageHeader } from "@/components/page"; import { AllocateBookingWizard } from "@/components/trainScheduling/AllocateBookingWizard"; import { BOOKING_STATUS_STYLES } from "@/features/bookings/booking-status.config"; import { toBookingListRow } from "@/features/bookings/mapBookingListRow"; import { useBookingDetail, useBookingList, useBookingListSummary, } from "@/hooks/bookings/useBookings"; import { api } from "@/services/api"; import type { BookingListFilter } from "@/services/bookings.service"; import type { BookingListRow } from "@/types/booking"; import { Badge, DataTable, DataTableFooter, usePagination, type ColumnDef, } from "@edr/ui-common"; /** The two booking-kind tabs: one-time vs general-contract bookings. */ type BookingKindTab = "ONE_TIME" | "GENERAL_CONTRACT"; const BOOKING_KIND_TABS: { value: BookingKindTab; label: string }[] = [ { value: "ONE_TIME", label: "One-time booking" }, { value: "GENERAL_CONTRACT", label: "General booking" }, ]; /** Status options for the filter select — built from the shared status styles. */ const STATUS_OPTIONS = Object.entries(BOOKING_STATUS_STYLES).map( ([value, { label }]) => ({ value, label }), ); const TRADE_DIRECTION_OPTIONS = [ { value: "IMPORT", label: "Import" }, { value: "EXPORT", label: "Export" }, { value: "DOMESTIC", label: "Domestic" }, ]; const FREIGHT_TYPE_OPTIONS = [ { value: "CONTAINER", label: "Container" }, { value: "BULK", label: "Bulk" }, ]; const PAYMENT_STATUS_OPTIONS = [ { value: "PENDING", label: "Payment pending" }, { value: "PNR_GENERATED", label: "PNR generated" }, { value: "VERIFICATION_IN_PROGRESS", label: "Verification in progress" }, { value: "PAID", label: "Paid" }, { value: "FAILED", label: "Payment failed" }, ]; const OWNERSHIP_OPTIONS = [ { value: "true", label: "Government" }, { value: "false", label: "Private" }, ]; /** Local start-of-day → ISO, for inclusive "from" date filters. */ function startOfDayIso(d: Date): string { const x = new Date(d); x.setHours(0, 0, 0, 0); return x.toISOString(); } /** Local end-of-day → ISO, for inclusive "to" date filters. */ function endOfDayIso(d: Date): string { const x = new Date(d); x.setHours(23, 59, 59, 999); return x.toISOString(); } function formatDate(value: string | null | undefined): string { if (!value) return "—"; const d = new Date(value); return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } export default function BookingRequestsPage() { const navigate = useNavigate(); const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [query, setQuery] = useState(""); // Booking-kind tabs (one-time vs general contract) replace the old status tabs. const [kindTab, setKindTab] = useState("ONE_TIME"); // Per-tab filter controls (empty/null = "all"). const [statusFilter, setStatusFilter] = useState([]); const [directionFilter, setDirectionFilter] = useState(null); const [freightTypeFilter, setFreightTypeFilter] = useState(null); const [paymentStatusFilter, setPaymentStatusFilter] = useState(null); const [ownershipFilter, setOwnershipFilter] = useState(null); const [originYardFilter, setOriginYardFilter] = useState(null); const [destinationYardFilter, setDestinationYardFilter] = useState(null); const [createdFrom, setCreatedFrom] = useState(null); const [createdTo, setCreatedTo] = useState(null); const [scheduledFrom, setScheduledFrom] = useState(null); const [scheduledTo, setScheduledTo] = useState(null); const [allocateOpen, setAllocateOpen] = useState(false); const [allocateIds, setAllocateIds] = useState([]); const suppressRowClickRef = useRef(false); const suppressRowClick = useCallback(() => { suppressRowClickRef.current = true; window.setTimeout(() => { suppressRowClickRef.current = false; }, 400); }, []); const filter: BookingListFilter = useMemo(() => { return { page: pagination.pageIndex + 1, pageSize: pagination.pageSize, sortBy: "createdAt", sortOrder: "DESC", // React Query cache key per kind tab. tab: kindTab, bookingType: kindTab, ...(statusFilter.length ? { statuses: statusFilter.join(",") } : {}), ...(directionFilter ? { tradeDirection: directionFilter } : {}), ...(freightTypeFilter ? { freightType: freightTypeFilter } : {}), ...(paymentStatusFilter ? { paymentStatus: paymentStatusFilter } : {}), ...(ownershipFilter ? { isGovernment: ownershipFilter as "true" | "false" } : {}), ...(originYardFilter ? { originYardId: originYardFilter } : {}), ...(destinationYardFilter ? { destinationYardId: destinationYardFilter } : {}), ...(createdFrom ? { createdFrom: startOfDayIso(createdFrom) } : {}), ...(createdTo ? { createdTo: endOfDayIso(createdTo) } : {}), ...(scheduledFrom ? { scheduledFrom: startOfDayIso(scheduledFrom) } : {}), ...(scheduledTo ? { scheduledTo: endOfDayIso(scheduledTo) } : {}), }; }, [ pagination.pageIndex, pagination.pageSize, kindTab, statusFilter, directionFilter, freightTypeFilter, paymentStatusFilter, ownershipFilter, originYardFilter, destinationYardFilter, createdFrom, createdTo, scheduledFrom, scheduledTo, ]); const { data, isLoading, isError, refetch, isFetching } = useBookingList(filter); const primaryAllocateId = allocateIds[0]; const { data: allocateBooking } = useBookingDetail( allocateOpen ? primaryAllocateId : undefined, ); const { data: summary, isLoading: summaryLoading, refetch: refetchSummary, } = useBookingListSummary(filter); // Yard options for the origin/destination filters (shared routes reference list). const { data: yardRefs } = useQuery( api.routes.yards.queryOptions({ staleTime: 5 * 60_000 }), ); const yardOptions = useMemo( () => (yardRefs ?? []).map((y) => ({ value: y.id, label: y.label ?? y.code, })), [yardRefs], ); const resetPage = useCallback(() => { setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }, [setPagination, pagination.pageSize]); const activeFilterCount = (statusFilter.length ? 1 : 0) + (directionFilter ? 1 : 0) + (freightTypeFilter ? 1 : 0) + (paymentStatusFilter ? 1 : 0) + (ownershipFilter ? 1 : 0) + (originYardFilter ? 1 : 0) + (destinationYardFilter ? 1 : 0) + (createdFrom || createdTo ? 1 : 0) + (scheduledFrom || scheduledTo ? 1 : 0); const clearFilters = useCallback(() => { setStatusFilter([]); setDirectionFilter(null); setFreightTypeFilter(null); setPaymentStatusFilter(null); setOwnershipFilter(null); setOriginYardFilter(null); setDestinationYardFilter(null); setCreatedFrom(null); setCreatedTo(null); setScheduledFrom(null); setScheduledTo(null); resetPage(); }, [resetPage]); const rows = useMemo(() => { const items = (data?.items ?? []).map(toBookingListRow); const q = query.trim().toLowerCase(); if (!q) return items; return items.filter( (b) => b.reference.toLowerCase().includes(q) || b.customerLabel.toLowerCase().includes(q) || (b.contractReference?.toLowerCase().includes(q) ?? false), ); }, [data?.items, query]); const total = data?.total ?? 0; const pageCount = Math.max(1, Math.ceil(total / pagination.pageSize)); const hasSearch = query.trim().length > 0; const showEmpty = !isLoading && !isError && rows.length === 0; const metrics = summary?.metrics; const tabCounts = summary?.tabs; const handleRefresh = useCallback(() => { void refetch(); void refetchSummary(); }, [refetch, refetchSummary]); const handleRowClick = useCallback( (row: BookingListRow) => { if (suppressRowClickRef.current) return; navigate(`/dashboard/booking-requests/${row.id}`); }, [navigate], ); const columns: ColumnDef[] = [ { id: "booking", header: () => Booking, cell: ({ row }) => { const b = row.original; return (

{b.reference}

{b.customerLabel}

); }, }, { id: "contract", header: () => Contract, cell: ({ row }) => { const ref = row.original.contractReference; return (
{ref ? ( {ref} ) : ( )}
); }, }, { id: "route", header: () => Route, cell: ({ row }) => { const b = row.original; return (
{b.originLabel} {b.destinationLabel}
{b.tradeDirection} {b.freightType}
); }, }, { id: "status", size: 200, minSize: 180, header: () => Status, cell: ({ row }) => (
), meta: { headerClassName: "min-w-[11rem]", cellClassName: "min-w-[11rem]", }, }, { id: "approval", header: () => ( Approval ), cell: ({ row }) => , }, { id: "scheduled", header: () => Scheduled, cell: ({ row }) => ( {formatDate(row.original.scheduledDate)} ), }, { id: "priority", header: () => Priority, cell: ({ row }) => ( ), }, { id: "actions", size: 140, cell: ({ row }) => ( ), }, ]; return ( } /> {/* Status tabs replaced by booking-kind tabs (one-time / general). The old BookingStatusTabs is commented out — status is now a filter select. { setActiveTab(tab); setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }} counts={tabCounts} /> */} { setKindTab((value as BookingKindTab) ?? "ONE_TIME"); setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }} > {BOOKING_KIND_TABS.map((t) => ( {t.label} ))} } value={query} onChange={(e) => setQuery(e.target.value)} rightSection={ query && ( setQuery("")} > ) } style={{ flex: 1, minWidth: "200px" }} radius="lg" /> {total} record{total !== 1 ? "s" : ""} { setStatusFilter(v); resetPage(); }} clearable searchable radius="lg" style={{ minWidth: 220 }} /> { setDestinationYardFilter(v); resetPage(); }} clearable searchable radius="lg" style={{ minWidth: 180 }} /> { setFreightTypeFilter(v); resetPage(); }} clearable radius="lg" style={{ minWidth: 150 }} /> { setOwnershipFilter(v); resetPage(); }} clearable radius="lg" style={{ minWidth: 140 }} /> { setCreatedFrom(v ? new Date(v) : null); resetPage(); }} maxDate={createdTo ?? undefined} clearable radius="lg" style={{ minWidth: 140 }} /> { setCreatedTo(v ? new Date(v) : null); resetPage(); }} minDate={createdFrom ?? undefined} clearable radius="lg" style={{ minWidth: 140 }} /> { setScheduledFrom(v ? new Date(v) : null); resetPage(); }} maxDate={scheduledTo ?? undefined} clearable radius="lg" style={{ minWidth: 150 }} /> { setScheduledTo(v ? new Date(v) : null); resetPage(); }} minDate={scheduledFrom ?? undefined} clearable radius="lg" style={{ minWidth: 150 }} /> {activeFilterCount > 0 ? ( ) : null} {showEmpty ? ( ) : ( )} {allocateBooking ? ( { setAllocateOpen(false); setAllocateIds([]); void refetch(); }} initialBookingIds={allocateIds} /> ) : null} ); }