diff --git a/apps/edr-freight-web/backoffice/src/components/filters/index.ts b/apps/edr-freight-web/backoffice/src/components/filters/index.ts index 5fe7702f3..ea2c28fb2 100644 --- a/apps/edr-freight-web/backoffice/src/components/filters/index.ts +++ b/apps/edr-freight-web/backoffice/src/components/filters/index.ts @@ -3,6 +3,7 @@ export * from "./url"; export * from "./dates"; export * from "./format"; export * from "./clientFilter"; +export * from "./ruleEngineFooterProps"; export * from "./useFilters"; export * from "./useSavedViews"; export { FilterBar } from "./FilterBar"; diff --git a/apps/edr-freight-web/backoffice/src/components/filters/ruleEngineFooterProps.ts b/apps/edr-freight-web/backoffice/src/components/filters/ruleEngineFooterProps.ts new file mode 100644 index 000000000..e8cd32dab --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/filters/ruleEngineFooterProps.ts @@ -0,0 +1,32 @@ +import type { OnChangeFn, PaginationState } from "@edr/ui-common"; +import type { UseFilters } from "./useFilters"; + +/** + * Adapts `useFilters`'s URL-backed page/pageSize to `RuleEngineListFooter`'s + * prop shape, for the client-bridge pages that render a plain `` + + * that footer instead of `` (which has `tableProps()` for this). + * Routes page-index vs page-size changes to the right setter — the same + * pageSize-gets-silently-dropped bug `tableProps()` had before it was fixed. + */ +export function toRuleEngineFooterProps( + controls: Pick, + totalCount: number, +): { + pagination: PaginationState; + pageCount: number; + totalCount: number; + onPaginationChange: OnChangeFn; +} { + const { page, pageSize, setPage, setPageSize } = controls; + return { + pagination: { pageIndex: page - 1, pageSize }, + pageCount: Math.max(1, Math.ceil(totalCount / pageSize)), + totalCount, + onPaginationChange: (updater) => { + const current = { pageIndex: page - 1, pageSize }; + const next = typeof updater === "function" ? updater(current) : updater; + if (next.pageSize !== pageSize) setPageSize(next.pageSize); + else if (next.pageIndex !== current.pageIndex) setPage(next.pageIndex + 1); + }, + }; +} diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/CompliancePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/CompliancePage.tsx index f31bf5e76..9cb305bbb 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/CompliancePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/CompliancePage.tsx @@ -18,11 +18,16 @@ import { } from "@mantine/core"; import { Plus, AlertTriangle } from "lucide-react"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; -import ListControls from "@/components/common/ListControls"; // Generic list footer — already shared by the fleet and train-scheduling lists // despite the ruleEngine path. import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter"; -import { useListControls } from "@/hooks/useListControls"; +import { + applyClientFilters, + FilterBar, + toRuleEngineFooterProps, + useFilters, + type FilterDef, +} from "@/components/filters"; import { useToast } from "@/hooks/use-toast"; import { complianceService, @@ -52,6 +57,8 @@ const statusColor = (status: ComplianceRecord["status"]) => { const formatDate = (value?: string | null) => value ? new Date(value).toLocaleDateString() : "—"; +const COMPLIANCE_FILTER_DEFS: FilterDef[] = [{ key: "expiryDate", label: "Expiry", type: "date" }]; + const emptyForm = { vehicleId: "", type: "INSPECTION" as ComplianceType, @@ -91,10 +98,18 @@ export default function CompliancePage() { }, }); - const controls = useListControls(records as ComplianceRecord[], { - searchKeys: ["type", "status", "documentNumber"], - dateKey: "expiryDate", - }); + const controls = useFilters(COMPLIANCE_FILTER_DEFS, { pageSize: 10 }); + const filteredRecords = applyClientFilters( + records as ComplianceRecord[], + COMPLIANCE_FILTER_DEFS, + controls.values, + controls.searchText, + { searchKeys: ["type", "status", "documentNumber"] }, + ); + const pagedRecords = filteredRecords.slice( + (controls.page - 1) * controls.pageSize, + controls.page * controls.pageSize, + ); const createMutation = useMutation({ mutationFn: async (data: typeof formData) => { @@ -220,17 +235,11 @@ export default function CompliancePage() { Compliance Records -
@@ -261,7 +270,7 @@ export default function CompliancePage() { ) : null} - {controls.pagedRows.map((record) => ( + {pagedRecords.map((record) => ( {vehicleLabel(record)} @@ -282,11 +291,8 @@ export default function CompliancePage() {
diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx index 8ecedd712..ec9c93482 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/FuelPurchasePage.tsx @@ -19,11 +19,16 @@ import { } from "@mantine/core"; import { Plus } from "lucide-react"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; -import ListControls from "@/components/common/ListControls"; // Generic list footer — already shared by the fleet and train-scheduling lists // despite the ruleEngine path. import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter"; -import { useListControls } from "@/hooks/useListControls"; +import { + applyClientFilters, + FilterBar, + toRuleEngineFooterProps, + useFilters, + type FilterDef, +} from "@/components/filters"; import { useToast } from "@/hooks/use-toast"; import { api } from "@/auth/http"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; @@ -45,6 +50,8 @@ interface FuelPurchase { } +const FUEL_FILTER_DEFS: FilterDef[] = [{ key: "purchaseDate", label: "Purchased", type: "date" }]; + export default function FuelPurchasePage() { const { toast } = useToast(); const qc = useQueryClient(); @@ -123,10 +130,18 @@ export default function FuelPurchasePage() { const totalCost = formData.liters * formData.costPerLiter; // Aggregate stats (guarded against divide-by-zero when there are no purchases) - const controls = useListControls(purchasesData as FuelPurchase[], { - searchKeys: ["fuelStation", "paymentMethod"], - dateKey: "purchaseDate", - }); + const controls = useFilters(FUEL_FILTER_DEFS, { pageSize: 10 }); + const filteredPurchases = applyClientFilters( + purchasesData as FuelPurchase[], + FUEL_FILTER_DEFS, + controls.values, + controls.searchText, + { searchKeys: ["fuelStation", "paymentMethod"] }, + ); + const pagedPurchases = filteredPurchases.slice( + (controls.page - 1) * controls.pageSize, + controls.page * controls.pageSize, + ); const totalLiters = (purchasesData as FuelPurchase[]).reduce( (sum, p) => sum + Number(p.liters), @@ -195,17 +210,11 @@ export default function FuelPurchasePage() { {/* Purchases Table */} - @@ -237,7 +246,7 @@ export default function FuelPurchasePage() { ) : null} - {controls.pagedRows.map((purchase) => ( + {pagedPurchases.map((purchase) => ( {(purchase as any).vehicle?.registrationNumber || (purchase as any).vehicle?.plateNumber || purchase.vehicleId} {new Date(purchase.purchaseDate).toLocaleDateString()} @@ -253,11 +262,8 @@ export default function FuelPurchasePage() {
diff --git a/apps/edr-freight-web/backoffice/src/pages/fleet/IncidentsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/fleet/IncidentsPage.tsx index 6276091b8..62c3d9e01 100644 --- a/apps/edr-freight-web/backoffice/src/pages/fleet/IncidentsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/fleet/IncidentsPage.tsx @@ -20,11 +20,16 @@ import { } from "@mantine/core"; import { Plus } from "lucide-react"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; -import ListControls from "@/components/common/ListControls"; // Generic list footer — already shared by the fleet and train-scheduling lists // despite the ruleEngine path. import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter"; -import { useListControls } from "@/hooks/useListControls"; +import { + applyClientFilters, + FilterBar, + toRuleEngineFooterProps, + useFilters, + type FilterDef, +} from "@/components/filters"; import { useToast } from "@/hooks/use-toast"; import { incidentsService, @@ -89,6 +94,8 @@ const initialForm = { reportedBy: "", }; +const INCIDENT_FILTER_DEFS: FilterDef[] = [{ key: "occurredAt", label: "Occurred", type: "date" }]; + export default function IncidentsPage() { const { toast } = useToast(); const qc = useQueryClient(); @@ -166,10 +173,18 @@ export default function IncidentsPage() { })) || []; const incidents = incidentsData as Incident[]; - const controls = useListControls(incidents, { - searchKeys: ["type", "severity", "status"], - dateKey: "occurredAt", - }); + const controls = useFilters(INCIDENT_FILTER_DEFS, { pageSize: 10 }); + const filteredIncidents = applyClientFilters( + incidents, + INCIDENT_FILTER_DEFS, + controls.values, + controls.searchText, + { searchKeys: ["type", "severity", "status"] }, + ); + const pagedIncidents = filteredIncidents.slice( + (controls.page - 1) * controls.pageSize, + controls.page * controls.pageSize, + ); const totalCount = incidents.length; const openCount = incidents.filter((i) => OPEN_STATUSES.includes(i.status)).length; const underReviewCount = incidents.filter((i) => i.status === "UNDER_REVIEW").length; @@ -246,17 +261,11 @@ export default function IncidentsPage() { {/* Incidents Table */} - @@ -288,7 +297,7 @@ export default function IncidentsPage() { ) : null} - {controls.pagedRows.map((incident) => ( + {pagedIncidents.map((incident) => ( {new Date(incident.occurredAt).toLocaleDateString()} @@ -316,11 +325,8 @@ export default function IncidentsPage() {
diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx index 39f7daac4..7ddcef866 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx @@ -14,7 +14,13 @@ import { PageContainer, PageHeader } from "@/components/page"; // Generic list footer — already shared by the fleet and train-scheduling lists // despite the ruleEngine path; reused here rather than adding a second one. import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter"; -import { applyClientFilters, FilterBar, useFilters, type FilterDef } from "@/components/filters"; +import { + applyClientFilters, + FilterBar, + toRuleEngineFooterProps, + useFilters, + type FilterDef, +} from "@/components/filters"; import { useTrucksOnSite } from "@/hooks/useWarehouses"; import type { TruckOnSite } from "@/types/warehouse"; import { formatDateTime } from "@/lib/format"; @@ -231,16 +237,8 @@ export default function TrucksOnSitePage() { <> { - const current = { pageIndex: controls.page - 1, pageSize: controls.pageSize }; - const next = typeof updater === "function" ? updater(current) : updater; - if (next.pageSize !== controls.pageSize) controls.setPageSize(next.pageSize); - else controls.setPage(next.pageIndex + 1); - }} + {...toRuleEngineFooterProps(controls, filteredTrucks.length)} /> )}