mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 15:18:11 +00:00
Merge branch 'dev' of github.com:Tria-plc/edr-platform into freight_feature/usermanagement
This commit is contained in:
@@ -69,6 +69,11 @@ export interface FleetFormFieldDef extends FormFieldDef {
|
||||
* (e.g. a license expiry); "past" (default) = cannot be in the future.
|
||||
*/
|
||||
dateBound?: "past" | "future";
|
||||
/**
|
||||
* Format the value must match, checked on submit. The value is upper-cased and
|
||||
* trimmed before the test, matching the server. Empty optional fields skip it.
|
||||
*/
|
||||
pattern?: { regex: RegExp; message: string; uppercase?: boolean };
|
||||
}
|
||||
|
||||
export interface FleetListFilterDef {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import type { FleetResourceConfig } from "./resources";
|
||||
|
||||
/**
|
||||
* A plate is two or three letters, a hyphen, then two to six digits — ET-9875,
|
||||
* AA-8642. Mirrors VEHICLE_PLATE_REGEX on the API so the form and the server
|
||||
* agree on what a plate looks like.
|
||||
*/
|
||||
const PLATE_PATTERN = {
|
||||
regex: /^[A-Z]{2,3}-\d{2,6}$/,
|
||||
message: "Use letters and numbers like ET-9875 or AA-8642",
|
||||
};
|
||||
|
||||
const VEHICLE_TYPE_OPTIONS = [
|
||||
{ label: "Truck", value: "TRUCK" },
|
||||
{ label: "Van", value: "VAN" },
|
||||
@@ -77,9 +87,9 @@ export const vehiclesConfig: FleetResourceConfig = {
|
||||
],
|
||||
formFields: [
|
||||
{ name: "code", label: "Code", type: "text" },
|
||||
{ name: "plateNumber", label: "Power Plate No", type: "text", required: true },
|
||||
{ name: "plateNumber", label: "Power Plate No", type: "text", required: true, pattern: PLATE_PATTERN },
|
||||
// { name: "powerPlateNo", label: "Power Plate No", type: "text" },
|
||||
{ name: "trailerPlateNo", label: "Trailer Plate No", type: "text" },
|
||||
{ name: "trailerPlateNo", label: "Trailer Plate No", type: "text", pattern: PLATE_PATTERN },
|
||||
{ name: "vehicleType", label: "Vehicle Type", type: "select", required: true, options: VEHICLE_TYPE_OPTIONS },
|
||||
{ name: "manufacturer", label: "Manufacturer", type: "text", required: true },
|
||||
{ name: "model", label: "Model", type: "text", required: true },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
@@ -139,7 +140,13 @@ function Rows({ rows }: { rows: TruckOnSite[] }) {
|
||||
|
||||
export default function TrucksOnSitePage() {
|
||||
const { data: trucks = [], isLoading } = useTrucksOnSite();
|
||||
const [scope, setScope] = useState<"ALL" | "ON_SITE" | "INBOUND">("ALL");
|
||||
// The dashboard's "Trucks on-site" card counts only arrived trucks, so it
|
||||
// deep-links here with ?scope=ON_SITE to land on the matching tab.
|
||||
const [searchParams] = useSearchParams();
|
||||
const scopeParam = searchParams.get("scope");
|
||||
const [scope, setScope] = useState<"ALL" | "ON_SITE" | "INBOUND">(
|
||||
scopeParam === "ON_SITE" || scopeParam === "INBOUND" ? scopeParam : "ALL",
|
||||
);
|
||||
const [source, setSource] = useState<"ALL" | "CUSTOMER" | "EDR">("ALL");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Button, Card, Group, Select, Stack, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { PackageOpen, Search, Truck } from 'lucide-react';
|
||||
import { PackageOpen, Search, Truck, X } from 'lucide-react';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
@@ -25,14 +25,36 @@ export default function WarehouseInventoryPage() {
|
||||
const [filter, setFilter] = useState<InventoryFilter>(
|
||||
initialStatus ? { status: initialStatus } : {},
|
||||
);
|
||||
// KPI drill-downs arriving from the ops dashboard cards; each shows as a
|
||||
// dismissible chip so the list can be widened back out in place.
|
||||
const [quick, setQuick] = useState<
|
||||
Pick<InventoryFilter, 'receivedToday' | 'pendingInspection' | 'agingOverDays'>
|
||||
>(() => {
|
||||
const aging = Number(searchParams.get('agingOverDays'));
|
||||
return {
|
||||
receivedToday: searchParams.get('receivedToday') ? true : undefined,
|
||||
pendingInspection: searchParams.get('pendingInspection') ? true : undefined,
|
||||
agingOverDays: Number.isFinite(aging) && aging > 0 ? aging : undefined,
|
||||
};
|
||||
});
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 300);
|
||||
const queryFilter = useMemo<InventoryFilter>(
|
||||
() => ({ ...filter, direction, search: debouncedSearch || undefined }),
|
||||
[filter, direction, debouncedSearch],
|
||||
() => ({ ...filter, ...quick, direction, search: debouncedSearch || undefined }),
|
||||
[filter, quick, direction, debouncedSearch],
|
||||
);
|
||||
|
||||
const quickChips: Array<{ key: keyof typeof quick; label: string }> = [
|
||||
...(quick.receivedToday ? [{ key: 'receivedToday' as const, label: 'Received today' }] : []),
|
||||
...(quick.pendingInspection
|
||||
? [{ key: 'pendingInspection' as const, label: 'Pending inspection' }]
|
||||
: []),
|
||||
...(quick.agingOverDays
|
||||
? [{ key: 'agingOverDays' as const, label: `In warehouse >${quick.agingOverDays}d` }]
|
||||
: []),
|
||||
];
|
||||
|
||||
const warehousesQuery = useWarehouses();
|
||||
const yardsQuery = useWarehouseYards(filter.warehouseId);
|
||||
const zonesQuery = useWarehouseZones(filter.yardId);
|
||||
@@ -136,6 +158,17 @@ export default function WarehouseInventoryPage() {
|
||||
}
|
||||
w={200}
|
||||
/>
|
||||
{quickChips.map((chip) => (
|
||||
<Button
|
||||
key={chip.key}
|
||||
size="compact-xs"
|
||||
variant="light"
|
||||
rightSection={<X size={12} />}
|
||||
onClick={() => setQuick((q) => ({ ...q, [chip.key]: undefined }))}
|
||||
>
|
||||
{chip.label}
|
||||
</Button>
|
||||
))}
|
||||
</Group>
|
||||
|
||||
<InventoryWorkbench items={inventoryQuery.data ?? []} isLoading={inventoryQuery.isLoading} />
|
||||
|
||||
Reference in New Issue
Block a user