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)}
/>
>
)}