mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(filter-bar): migrate BookingRequestsPage (richest Family A page)
13 useState fields -> 11 FilterDef entries + useFilters. Notable pieces: - the deep-link-follow useEffect is gone entirely — controls.values reads live off the URL every render, so a link opened while the page is already mounted (the header document-review alarm) just works, and every filter auto-pins its own pill once it has a value (FilterBar's secondary/pinned split), so a deep link can never land behind "More filters" hidden. Confirmed the encoding stays back-compatible with the existing ?statuses=A,B&tradeDirection=IMPORT links via url.test.ts. - "Paid, not allocated" (a checkbox that overrides the payment-status filter) became a boolean FilterDef with a custom toParams — ordered after paymentStatus in the defs array so its toParams wins on merge, matching the original override semantics exactly. - booking kind and status stay pinned pills (always visible, matching the old primary row); direction/freight/payment/ownership/yards/dates are secondary (behind "More filters", matching the old advanced collapse).
This commit is contained in:
@@ -1,44 +1,32 @@
|
|||||||
import { useMyTradeAccess } from "@/hooks/useMyTradeAccess";
|
import { useMyTradeAccess } from "@/hooks/useMyTradeAccess";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Checkbox,
|
|
||||||
Collapse,
|
|
||||||
Group,
|
Group,
|
||||||
Modal,
|
Modal,
|
||||||
MultiSelect,
|
|
||||||
Select,
|
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { DatePickerInput } from "@mantine/dates";
|
|
||||||
import { getDateRangePresets } from "@/components/common/dateRangePresets";
|
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Calendar,
|
Calendar,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock,
|
Clock,
|
||||||
FilterX,
|
|
||||||
LayoutList,
|
LayoutList,
|
||||||
Package,
|
Package,
|
||||||
Plus,
|
Plus,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Search,
|
|
||||||
User,
|
User,
|
||||||
X,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu";
|
import { BookingActionsMenu } from "@/components/bookings/BookingActionsMenu";
|
||||||
import { FilterToggle } from "@/components/common/FilterToggle";
|
|
||||||
import { formatDate, humanize } from "@/lib/format";
|
import { formatDate, humanize } from "@/lib/format";
|
||||||
|
import { FilterBar, useFilters, type FilterDef } from "@/components/filters";
|
||||||
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
|
import { BookingPriorityBadge } from "@/components/bookings/BookingPriorityBadge";
|
||||||
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
||||||
// BookingStatusTabs / Operations* queues removed — replaced by booking-kind tabs.
|
// BookingStatusTabs / Operations* queues removed — replaced by booking-kind tabs.
|
||||||
@@ -63,7 +51,6 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
DataTable,
|
DataTable,
|
||||||
DataTableFooter,
|
DataTableFooter,
|
||||||
usePagination,
|
|
||||||
type ColumnDef,
|
type ColumnDef,
|
||||||
} from "@edr/ui-common";
|
} from "@edr/ui-common";
|
||||||
|
|
||||||
@@ -104,61 +91,11 @@ const OWNERSHIP_OPTIONS = [
|
|||||||
{ value: "false", label: "Private" },
|
{ 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BookingRequestsPage() {
|
export default function BookingRequestsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// Deep links land here pre-filtered (?statuses=A,B&tradeDirection=IMPORT) —
|
|
||||||
// the header's document-review alarm opens exactly the undecided requests it
|
|
||||||
// is counting down for. Read once as the initial state so staff can then
|
|
||||||
// change the filters like any other visit.
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
const [debouncedQuery] = useDebouncedValue(query, 300);
|
|
||||||
// Booking kind is a filter now — one list holds both kinds (null = "all").
|
|
||||||
const [kindFilter, setKindFilter] = useState<BookingKind | null>(null);
|
|
||||||
// Filter controls (empty/null = "all").
|
|
||||||
const paramStatuses = searchParams.get("statuses") ?? "";
|
|
||||||
const paramDirection = searchParams.get("tradeDirection");
|
|
||||||
const [statusFilter, setStatusFilter] = useState<string[]>(() =>
|
|
||||||
paramStatuses.split(",").filter(Boolean),
|
|
||||||
);
|
|
||||||
const { filterOptions } = useMyTradeAccess();
|
const { filterOptions } = useMyTradeAccess();
|
||||||
const [directionFilter, setDirectionFilter] = useState<string | null>(
|
|
||||||
paramDirection,
|
|
||||||
);
|
|
||||||
const [freightTypeFilter, setFreightTypeFilter] = useState<string | null>(null);
|
|
||||||
const [paymentStatusFilter, setPaymentStatusFilter] = useState<string | null>(null);
|
|
||||||
const [ownershipFilter, setOwnershipFilter] = useState<string | null>(null);
|
|
||||||
const [originYardFilter, setOriginYardFilter] = useState<string | null>(null);
|
|
||||||
const [destinationYardFilter, setDestinationYardFilter] = useState<string | null>(null);
|
|
||||||
const [createdFrom, setCreatedFrom] = useState<Date | null>(null);
|
|
||||||
const [createdTo, setCreatedTo] = useState<Date | null>(null);
|
|
||||||
const [scheduledFrom, setScheduledFrom] = useState<Date | null>(null);
|
|
||||||
const [scheduledTo, setScheduledTo] = useState<Date | null>(null);
|
|
||||||
// Direction is the only deep-linkable advanced filter — open the panel so a
|
|
||||||
// deep link never hides its own filter.
|
|
||||||
const [showAdvanced, setShowAdvanced] = useState(() =>
|
|
||||||
Boolean(paramDirection),
|
|
||||||
);
|
|
||||||
const [allocateOpen, setAllocateOpen] = useState(false);
|
const [allocateOpen, setAllocateOpen] = useState(false);
|
||||||
const [allocateIds, setAllocateIds] = useState<string[]>([]);
|
const [allocateIds, setAllocateIds] = useState<string[]>([]);
|
||||||
// Paid bookings with no train attached (staff removed them or a sweep
|
|
||||||
// detached them) — the queue the per-row Allocate action works through.
|
|
||||||
const [paidUnallocated, setPaidUnallocated] = useState(false);
|
|
||||||
const [allocatingId, setAllocatingId] = useState<string | null>(null);
|
const [allocatingId, setAllocatingId] = useState<string | null>(null);
|
||||||
const [otherDayModal, setOtherDayModal] = useState<{
|
const [otherDayModal, setOtherDayModal] = useState<{
|
||||||
booking: BookingListRow;
|
booking: BookingListRow;
|
||||||
@@ -173,77 +110,6 @@ export default function BookingRequestsPage() {
|
|||||||
}, 400);
|
}, 400);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Follow the URL when a deep link arrives while the page is already open
|
|
||||||
// (clicking the header alarm from this very list). Same-value writes are
|
|
||||||
// dropped so a manual filter change is never undone.
|
|
||||||
useEffect(() => {
|
|
||||||
const next = paramStatuses.split(",").filter(Boolean);
|
|
||||||
setStatusFilter((prev) => (prev.join(",") === next.join(",") ? prev : next));
|
|
||||||
setDirectionFilter(paramDirection);
|
|
||||||
if (paramDirection) setShowAdvanced(true);
|
|
||||||
}, [paramStatuses, paramDirection]);
|
|
||||||
|
|
||||||
const filter: BookingListFilter = useMemo(() => {
|
|
||||||
return {
|
|
||||||
page: pagination.pageIndex + 1,
|
|
||||||
pageSize: pagination.pageSize,
|
|
||||||
sortBy: "createdAt",
|
|
||||||
sortOrder: "DESC",
|
|
||||||
// React Query cache key per kind selection ("ALL" when unfiltered).
|
|
||||||
tab: kindFilter ?? "ALL",
|
|
||||||
...(kindFilter ? { bookingType: kindFilter } : {}),
|
|
||||||
// Server-side free-text search (booking ref, customer, contract ref).
|
|
||||||
...(debouncedQuery.trim() ? { search: debouncedQuery.trim() } : {}),
|
|
||||||
...(statusFilter.length ? { statuses: statusFilter.join(",") } : {}),
|
|
||||||
...(directionFilter ? { tradeDirection: directionFilter } : {}),
|
|
||||||
...(freightTypeFilter ? { freightType: freightTypeFilter } : {}),
|
|
||||||
...(paymentStatusFilter ? { paymentStatus: paymentStatusFilter } : {}),
|
|
||||||
// Wins over the payment-status select — the queue is by definition PAID.
|
|
||||||
...(paidUnallocated
|
|
||||||
? { paymentStatus: "PAID", assignedToSchedule: "false" as const }
|
|
||||||
: {}),
|
|
||||||
...(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,
|
|
||||||
kindFilter,
|
|
||||||
debouncedQuery,
|
|
||||||
statusFilter,
|
|
||||||
directionFilter,
|
|
||||||
freightTypeFilter,
|
|
||||||
paymentStatusFilter,
|
|
||||||
paidUnallocated,
|
|
||||||
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).
|
// Yard options for the origin/destination filters (shared routes reference list).
|
||||||
const { data: yardRefs } = useQuery(
|
const { data: yardRefs } = useQuery(
|
||||||
api.routes.yards.queryOptions({ staleTime: 5 * 60_000 }),
|
api.routes.yards.queryOptions({ staleTime: 5 * 60_000 }),
|
||||||
@@ -257,43 +123,62 @@ export default function BookingRequestsPage() {
|
|||||||
[yardRefs],
|
[yardRefs],
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetPage = useCallback(() => {
|
// Deep links land here pre-filtered (?statuses=A,B&tradeDirection=IMPORT) —
|
||||||
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
|
// the header's document-review alarm opens exactly the undecided requests
|
||||||
}, [setPagination, pagination.pageSize]);
|
// it is counting down for. No sync effect needed any more: controls.values
|
||||||
|
// reads live off the URL every render, so a link opened while this page is
|
||||||
|
// already mounted just works, and every filter — direction included —
|
||||||
|
// auto-pins its own pill the moment it has a value (FilterBar's `secondary`
|
||||||
|
// split), so a deep link can never land behind "More filters" unseen.
|
||||||
|
const bookingFilterDefs: FilterDef[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: "bookingType", label: "Kind", type: "enum", multiple: false, options: BOOKING_KIND_OPTIONS },
|
||||||
|
{ key: "statuses", label: "Status", type: "enum", options: STATUS_OPTIONS },
|
||||||
|
{
|
||||||
|
key: "tradeDirection", label: "Direction", type: "enum", multiple: false,
|
||||||
|
options: filterOptions(TRADE_DIRECTION_OPTIONS), secondary: true,
|
||||||
|
},
|
||||||
|
{ key: "freightType", label: "Freight", type: "enum", multiple: false, options: FREIGHT_TYPE_OPTIONS, secondary: true },
|
||||||
|
{ key: "paymentStatus", label: "Payment", type: "enum", multiple: false, options: PAYMENT_STATUS_OPTIONS, secondary: true },
|
||||||
|
{
|
||||||
|
// Wins over the `paymentStatus` filter above — the queue is by
|
||||||
|
// definition PAID — because it's later in this array: toApiParams
|
||||||
|
// merges defs in order, so a later toParams overwrites an earlier one.
|
||||||
|
key: "paidUnallocated", label: "Allocation", type: "boolean", secondary: true,
|
||||||
|
trueLabel: "Paid, not allocated",
|
||||||
|
toParams: (v) => (v.v[0] === "true" ? { paymentStatus: "PAID", assignedToSchedule: "false" } : {}),
|
||||||
|
},
|
||||||
|
{ key: "isGovernment", label: "Ownership", type: "enum", multiple: false, options: OWNERSHIP_OPTIONS, secondary: true },
|
||||||
|
{ key: "originYardId", label: "Origin", type: "enum", multiple: false, options: yardOptions, secondary: true },
|
||||||
|
{ key: "destinationYardId", label: "Destination", type: "enum", multiple: false, options: yardOptions, secondary: true },
|
||||||
|
{ key: "created", label: "Created", type: "date", secondary: true, toParams: ({ v }) => ({ createdFrom: v[0], createdTo: v[1] }) },
|
||||||
|
{ key: "scheduled", label: "Scheduled", type: "date", secondary: true, toParams: ({ v }) => ({ scheduledFrom: v[0], scheduledTo: v[1] }) },
|
||||||
|
],
|
||||||
|
[filterOptions, yardOptions],
|
||||||
|
);
|
||||||
|
|
||||||
const activeFilterCount =
|
const controls = useFilters(bookingFilterDefs, { defaultSort: "createdAt:DESC", pageSize: 10 });
|
||||||
(kindFilter ? 1 : 0) +
|
|
||||||
(statusFilter.length ? 1 : 0) +
|
|
||||||
(directionFilter ? 1 : 0) +
|
|
||||||
(freightTypeFilter ? 1 : 0) +
|
|
||||||
(paymentStatusFilter ? 1 : 0) +
|
|
||||||
(paidUnallocated ? 1 : 0) +
|
|
||||||
(ownershipFilter ? 1 : 0) +
|
|
||||||
(originYardFilter ? 1 : 0) +
|
|
||||||
(destinationYardFilter ? 1 : 0) +
|
|
||||||
(createdFrom || createdTo ? 1 : 0) +
|
|
||||||
(scheduledFrom || scheduledTo ? 1 : 0);
|
|
||||||
|
|
||||||
// Badge on the advanced-filters toggle — active filters hidden behind it.
|
const filter: BookingListFilter = useMemo(
|
||||||
const advancedFilterCount =
|
() => ({
|
||||||
activeFilterCount - (kindFilter ? 1 : 0) - (statusFilter.length ? 1 : 0);
|
...(controls.params as unknown as BookingListFilter),
|
||||||
|
// React Query cache key per kind selection ("ALL" when unfiltered) —
|
||||||
|
// kept as a param the API ignores, matching the pre-migration cache key.
|
||||||
|
tab: (controls.values.bookingType?.v[0] as BookingKind | undefined) ?? "ALL",
|
||||||
|
}),
|
||||||
|
[controls.params, controls.values.bookingType],
|
||||||
|
);
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
const { data, isLoading, isError, refetch, isFetching } = useBookingList(filter);
|
||||||
setKindFilter(null);
|
const primaryAllocateId = allocateIds[0];
|
||||||
setStatusFilter([]);
|
const { data: allocateBooking } = useBookingDetail(
|
||||||
setDirectionFilter(null);
|
allocateOpen ? primaryAllocateId : undefined,
|
||||||
setFreightTypeFilter(null);
|
);
|
||||||
setPaymentStatusFilter(null);
|
const {
|
||||||
setPaidUnallocated(false);
|
data: summary,
|
||||||
setOwnershipFilter(null);
|
isLoading: summaryLoading,
|
||||||
setOriginYardFilter(null);
|
refetch: refetchSummary,
|
||||||
setDestinationYardFilter(null);
|
} = useBookingListSummary(filter);
|
||||||
setCreatedFrom(null);
|
|
||||||
setCreatedTo(null);
|
|
||||||
setScheduledFrom(null);
|
|
||||||
setScheduledTo(null);
|
|
||||||
resetPage();
|
|
||||||
}, [resetPage]);
|
|
||||||
|
|
||||||
// Search is applied server-side (via the `search` filter param) — no
|
// Search is applied server-side (via the `search` filter param) — no
|
||||||
// client-side filtering here.
|
// client-side filtering here.
|
||||||
@@ -303,8 +188,7 @@ export default function BookingRequestsPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const total = data?.total ?? 0;
|
const total = data?.total ?? 0;
|
||||||
const pageCount = Math.max(1, Math.ceil(total / pagination.pageSize));
|
const hasSearch = controls.searchText.trim().length > 0;
|
||||||
const hasSearch = query.trim().length > 0;
|
|
||||||
const showEmpty = !isLoading && !isError && rows.length === 0;
|
const showEmpty = !isLoading && !isError && rows.length === 0;
|
||||||
|
|
||||||
const metrics = summary?.metrics;
|
const metrics = summary?.metrics;
|
||||||
@@ -585,195 +469,13 @@ export default function BookingRequestsPage() {
|
|||||||
|
|
||||||
<Card p={0}>
|
<Card p={0}>
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Box px="md" pt="md" pb="sm" w="100%">
|
<Box px="md" pt="sm" pb="xs" w="100%">
|
||||||
<Stack gap="sm">
|
<FilterBar
|
||||||
<Group gap="sm" wrap="wrap">
|
defs={bookingFilterDefs}
|
||||||
<TextInput
|
controls={controls}
|
||||||
placeholder="Search booking, contract or customer…"
|
searchPlaceholder="Search booking, contract or customer…"
|
||||||
leftSection={<Search size={18} />}
|
viewId="booking-requests"
|
||||||
value={query}
|
/>
|
||||||
onChange={(e) => {
|
|
||||||
setQuery(e.target.value);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
rightSection={
|
|
||||||
query && (
|
|
||||||
<ActionIcon
|
|
||||||
size="sm"
|
|
||||||
color="gray"
|
|
||||||
radius="md"
|
|
||||||
variant="transparent"
|
|
||||||
onClick={() => {
|
|
||||||
setQuery("");
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<X size={16} />
|
|
||||||
</ActionIcon>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
style={{ flex: 1, minWidth: "200px" }}
|
|
||||||
radius="lg"
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="All booking types"
|
|
||||||
data={BOOKING_KIND_OPTIONS}
|
|
||||||
value={kindFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setKindFilter((v as BookingKind | null) ?? null);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 190 }}
|
|
||||||
/>
|
|
||||||
<MultiSelect
|
|
||||||
placeholder={statusFilter.length ? undefined : "All statuses"}
|
|
||||||
data={STATUS_OPTIONS}
|
|
||||||
value={statusFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setStatusFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
searchable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 220 }}
|
|
||||||
/>
|
|
||||||
<FilterToggle
|
|
||||||
count={advancedFilterCount}
|
|
||||||
expanded={showAdvanced}
|
|
||||||
onClick={() => setShowAdvanced((v) => !v)}
|
|
||||||
/>
|
|
||||||
{activeFilterCount > 0 ? (
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
color="gray"
|
|
||||||
radius="lg"
|
|
||||||
leftSection={<FilterX size={16} />}
|
|
||||||
onClick={clearFilters}
|
|
||||||
>
|
|
||||||
Clear filters ({activeFilterCount})
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</Group>
|
|
||||||
<Collapse expanded={showAdvanced}>
|
|
||||||
<Group gap="sm" wrap="wrap">
|
|
||||||
<Select
|
|
||||||
placeholder="All origins"
|
|
||||||
data={yardOptions}
|
|
||||||
value={originYardFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setOriginYardFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
searchable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 180 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="All destinations"
|
|
||||||
data={yardOptions}
|
|
||||||
value={destinationYardFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setDestinationYardFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
searchable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 180 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="All directions"
|
|
||||||
data={filterOptions(TRADE_DIRECTION_OPTIONS)}
|
|
||||||
value={directionFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setDirectionFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 150 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="All freight types"
|
|
||||||
data={FREIGHT_TYPE_OPTIONS}
|
|
||||||
value={freightTypeFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setFreightTypeFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 150 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="All payment statuses"
|
|
||||||
data={PAYMENT_STATUS_OPTIONS}
|
|
||||||
value={paymentStatusFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setPaymentStatusFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 180 }}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label="Paid, not allocated"
|
|
||||||
checked={paidUnallocated}
|
|
||||||
onChange={(e) => {
|
|
||||||
setPaidUnallocated(e.currentTarget.checked);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
radius="sm"
|
|
||||||
style={{ alignSelf: "center" }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
placeholder="Gov / Private"
|
|
||||||
data={OWNERSHIP_OPTIONS}
|
|
||||||
value={ownershipFilter}
|
|
||||||
onChange={(v) => {
|
|
||||||
setOwnershipFilter(v);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 140 }}
|
|
||||||
/>
|
|
||||||
<DatePickerInput
|
|
||||||
type="range"
|
|
||||||
placeholder="Created date range"
|
|
||||||
value={[createdFrom, createdTo]}
|
|
||||||
onChange={([from, to]) => {
|
|
||||||
setCreatedFrom(from ? new Date(from) : null);
|
|
||||||
setCreatedTo(to ? new Date(to) : null);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
presets={getDateRangePresets()}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 220 }}
|
|
||||||
/>
|
|
||||||
<DatePickerInput
|
|
||||||
type="range"
|
|
||||||
placeholder="Scheduled date range"
|
|
||||||
value={[scheduledFrom, scheduledTo]}
|
|
||||||
onChange={([from, to]) => {
|
|
||||||
setScheduledFrom(from ? new Date(from) : null);
|
|
||||||
setScheduledTo(to ? new Date(to) : null);
|
|
||||||
resetPage();
|
|
||||||
}}
|
|
||||||
presets={getDateRangePresets()}
|
|
||||||
clearable
|
|
||||||
radius="lg"
|
|
||||||
style={{ minWidth: 230 }}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
</Collapse>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{showEmpty ? (
|
{showEmpty ? (
|
||||||
@@ -791,18 +493,7 @@ export default function BookingRequestsPage() {
|
|||||||
data={rows}
|
data={rows}
|
||||||
status={isLoading ? "loading" : isError ? "error" : "success"}
|
status={isLoading ? "loading" : isError ? "error" : "success"}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
pagination={{
|
{...controls.tableProps(total)}
|
||||||
pageIndex: pagination.pageIndex,
|
|
||||||
pageSize: pagination.pageSize,
|
|
||||||
pageCount,
|
|
||||||
totalCount: total,
|
|
||||||
}}
|
|
||||||
tableOptions={{
|
|
||||||
state: { pagination },
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
manualPagination: true,
|
|
||||||
pageCount,
|
|
||||||
}}
|
|
||||||
containerClassName="border-0 shadow-none bg-transparent"
|
containerClassName="border-0 shadow-none bg-transparent"
|
||||||
footer={DataTableFooter}
|
footer={DataTableFooter}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user