mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #762 from Tria-plc/freight/feature/user_management_UI
Freight/feature/user management UI
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Freight } from "@edr/types";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Group,
|
||||
@@ -24,11 +25,19 @@ export default function AvailableWagonsPanel({
|
||||
yardLabel,
|
||||
onAssign,
|
||||
assigning,
|
||||
exportTrainNumber,
|
||||
importTrainNumber,
|
||||
}: AvailableWagonsPanelProps) {
|
||||
const [search, setSearch] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState<string>("ALL");
|
||||
const [runOnly, setRunOnly] = useState(false);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
// The train's own run, e.g. "8001-8002" — only offered when the train has one.
|
||||
const runLabel = exportTrainNumber
|
||||
? `${exportTrainNumber}${importTrainNumber ? `-${importTrainNumber}` : ""}`
|
||||
: null;
|
||||
|
||||
const wagonsQuery = useQuery(
|
||||
api.wagons.list.queryOptions({
|
||||
input: {
|
||||
@@ -42,10 +51,23 @@ export default function AvailableWagonsPanel({
|
||||
const q = search.trim().toLowerCase();
|
||||
return (wagonsQuery.data ?? []).filter((wagon) => {
|
||||
if (typeFilter !== "ALL" && wagon.wagonTypeId !== typeFilter) return false;
|
||||
// Rostered to this train's run — match on the export run, which fixes the
|
||||
// import run anyway.
|
||||
if (runOnly && wagon.exportTrainNumber !== exportTrainNumber) return false;
|
||||
if (q && !wagon.wagonNumber.toLowerCase().includes(q)) return false;
|
||||
return true;
|
||||
});
|
||||
}, [wagonsQuery.data, search, typeFilter]);
|
||||
}, [wagonsQuery.data, search, typeFilter, runOnly, exportTrainNumber]);
|
||||
|
||||
const runMatchCount = useMemo(
|
||||
() =>
|
||||
exportTrainNumber
|
||||
? (wagonsQuery.data ?? []).filter(
|
||||
(w) => w.exportTrainNumber === exportTrainNumber,
|
||||
).length
|
||||
: 0,
|
||||
[wagonsQuery.data, exportTrainNumber],
|
||||
);
|
||||
|
||||
const typeOptions = useMemo(() => {
|
||||
const byId = new Map<string, string>();
|
||||
@@ -112,6 +134,15 @@ export default function AvailableWagonsPanel({
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{runLabel ? (
|
||||
<Checkbox
|
||||
size="sm"
|
||||
label={`Only wagons on this train's run (${runLabel}) — ${runMatchCount} here`}
|
||||
checked={runOnly}
|
||||
onChange={(e) => setRunOnly(e.currentTarget.checked)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{wagons.length ? (
|
||||
<Checkbox
|
||||
size="sm"
|
||||
@@ -151,9 +182,24 @@ export default function AvailableWagonsPanel({
|
||||
aria-label={`Select wagon ${wagon.wagonNumber}`}
|
||||
/>
|
||||
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} ff="monospace" truncate>
|
||||
{wagon.wagonNumber}
|
||||
</Text>
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<Text size="sm" fw={600} ff="monospace" truncate>
|
||||
{wagon.wagonNumber}
|
||||
</Text>
|
||||
{wagon.exportTrainNumber ? (
|
||||
<Badge
|
||||
size="xs"
|
||||
radius="sm"
|
||||
variant="light"
|
||||
color={
|
||||
wagon.exportTrainNumber === exportTrainNumber ? "edr-green" : "gray"
|
||||
}
|
||||
>
|
||||
{wagon.exportTrainNumber}
|
||||
{wagon.importTrainNumber ? `-${wagon.importTrainNumber}` : ""}
|
||||
</Badge>
|
||||
) : null}
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{wagon.wagonType
|
||||
? `${wagon.wagonType.name} · ${wagon.wagonType.capacityTons ?? "—"}T cap`
|
||||
@@ -183,4 +229,8 @@ export interface AvailableWagonsPanelProps {
|
||||
yardLabel?: string | null;
|
||||
onAssign: (wagonIds: string[]) => void;
|
||||
assigning: boolean;
|
||||
/** This train's odd EXPORT run — drives the "only this run" filter. */
|
||||
exportTrainNumber?: string | null;
|
||||
/** This train's even IMPORT run — label only; the export run does the matching. */
|
||||
importTrainNumber?: string | null;
|
||||
}
|
||||
|
||||
@@ -255,6 +255,8 @@ export default function TrainBuilderDetailPage() {
|
||||
<AvailableWagonsPanel
|
||||
yardId={yard?.id ?? ""}
|
||||
yardLabel={yard?.label}
|
||||
exportTrainNumber={composition.exportTrainNumber}
|
||||
importTrainNumber={composition.importTrainNumber}
|
||||
assigning={assignWagons.isPending}
|
||||
onAssign={(wagonIds) =>
|
||||
void withToast(
|
||||
|
||||
@@ -28,6 +28,10 @@ export interface Wagon {
|
||||
status: Freight.WagonStatus;
|
||||
currentYardId: string | null;
|
||||
currentYard?: { id: string; label?: string; code?: string } | null;
|
||||
/** Odd EXPORT run (Ethiopia → Djibouti); null when the wagon is not on a run. */
|
||||
exportTrainNumber?: string | null;
|
||||
/** Even IMPORT run (Djibouti → Ethiopia); always the export run + 1. */
|
||||
importTrainNumber?: string | null;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user