mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 07:08:18 +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 { Freight } from "@edr/types";
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Group,
|
Group,
|
||||||
@@ -24,11 +25,19 @@ export default function AvailableWagonsPanel({
|
|||||||
yardLabel,
|
yardLabel,
|
||||||
onAssign,
|
onAssign,
|
||||||
assigning,
|
assigning,
|
||||||
|
exportTrainNumber,
|
||||||
|
importTrainNumber,
|
||||||
}: AvailableWagonsPanelProps) {
|
}: AvailableWagonsPanelProps) {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [typeFilter, setTypeFilter] = useState<string>("ALL");
|
const [typeFilter, setTypeFilter] = useState<string>("ALL");
|
||||||
|
const [runOnly, setRunOnly] = useState(false);
|
||||||
const [selected, setSelected] = useState<string[]>([]);
|
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(
|
const wagonsQuery = useQuery(
|
||||||
api.wagons.list.queryOptions({
|
api.wagons.list.queryOptions({
|
||||||
input: {
|
input: {
|
||||||
@@ -42,10 +51,23 @@ export default function AvailableWagonsPanel({
|
|||||||
const q = search.trim().toLowerCase();
|
const q = search.trim().toLowerCase();
|
||||||
return (wagonsQuery.data ?? []).filter((wagon) => {
|
return (wagonsQuery.data ?? []).filter((wagon) => {
|
||||||
if (typeFilter !== "ALL" && wagon.wagonTypeId !== typeFilter) return false;
|
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;
|
if (q && !wagon.wagonNumber.toLowerCase().includes(q)) return false;
|
||||||
return true;
|
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 typeOptions = useMemo(() => {
|
||||||
const byId = new Map<string, string>();
|
const byId = new Map<string, string>();
|
||||||
@@ -112,6 +134,15 @@ export default function AvailableWagonsPanel({
|
|||||||
/>
|
/>
|
||||||
</Group>
|
</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 ? (
|
{wagons.length ? (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -151,9 +182,24 @@ export default function AvailableWagonsPanel({
|
|||||||
aria-label={`Select wagon ${wagon.wagonNumber}`}
|
aria-label={`Select wagon ${wagon.wagonNumber}`}
|
||||||
/>
|
/>
|
||||||
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text size="sm" fw={600} ff="monospace" truncate>
|
<Group gap={6} wrap="nowrap">
|
||||||
{wagon.wagonNumber}
|
<Text size="sm" fw={600} ff="monospace" truncate>
|
||||||
</Text>
|
{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>
|
<Text size="xs" c="dimmed" truncate>
|
||||||
{wagon.wagonType
|
{wagon.wagonType
|
||||||
? `${wagon.wagonType.name} · ${wagon.wagonType.capacityTons ?? "—"}T cap`
|
? `${wagon.wagonType.name} · ${wagon.wagonType.capacityTons ?? "—"}T cap`
|
||||||
@@ -183,4 +229,8 @@ export interface AvailableWagonsPanelProps {
|
|||||||
yardLabel?: string | null;
|
yardLabel?: string | null;
|
||||||
onAssign: (wagonIds: string[]) => void;
|
onAssign: (wagonIds: string[]) => void;
|
||||||
assigning: boolean;
|
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
|
<AvailableWagonsPanel
|
||||||
yardId={yard?.id ?? ""}
|
yardId={yard?.id ?? ""}
|
||||||
yardLabel={yard?.label}
|
yardLabel={yard?.label}
|
||||||
|
exportTrainNumber={composition.exportTrainNumber}
|
||||||
|
importTrainNumber={composition.importTrainNumber}
|
||||||
assigning={assignWagons.isPending}
|
assigning={assignWagons.isPending}
|
||||||
onAssign={(wagonIds) =>
|
onAssign={(wagonIds) =>
|
||||||
void withToast(
|
void withToast(
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export interface Wagon {
|
|||||||
status: Freight.WagonStatus;
|
status: Freight.WagonStatus;
|
||||||
currentYardId: string | null;
|
currentYardId: string | null;
|
||||||
currentYard?: { id: string; label?: string; code?: 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;
|
notes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user