import { ActionIcon, Box, Button, Card, Group, Select, Stack, Tabs, Text, TextInput, } from "@mantine/core"; import { AlertTriangle, ArrowRight, Calendar, CheckCircle2, Clock, LayoutList, Package, Plus, RefreshCw, Search, User, X, } from "lucide-react"; import { useCallback, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; 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 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" }, ]; 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 selects (each nullable = "all"). const [statusFilter, setStatusFilter] = useState(null); const [directionFilter, setDirectionFilter] = useState(null); const [freightTypeFilter, setFreightTypeFilter] = 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 ? { statuses: statusFilter } : {}), ...(directionFilter ? { tradeDirection: directionFilter } : {}), ...(freightTypeFilter ? { freightType: freightTypeFilter } : {}), }; }, [ pagination.pageIndex, pagination.pageSize, kindTab, statusFilter, directionFilter, freightTypeFilter, ]); 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); 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), ); }, [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: "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" : ""} { setDirectionFilter(v); setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); }} clearable radius="lg" style={{ minWidth: 170 }} />