Merge pull request #1412 from Tria-plc/freight_feature/usermanagement

feat: enhance booking and audit log functionalities
This commit is contained in:
marshal
2026-08-25 02:50:34 +03:00
committed by GitHub
28 changed files with 1356 additions and 86 deletions

View File

@@ -115,6 +115,7 @@ export function LogPassYardWorkModal({
const { toast } = useToast();
const { user } = useAuth();
const canLoad = hasFreightPermission(user, FREIGHT_PERMS.trainScheduling.load);
const canLeave = hasFreightPermission(user, FREIGHT_PERMS.trainScheduling.update);
const [justLogged, setJustLogged] = useState(false);
// When the train was here — defaults to now, past allowed (recorded after the fact).
const [passAt, setPassAt] = useState<Date | null>(null);
@@ -134,6 +135,10 @@ export function LogPassYardWorkModal({
api.trainScheduling.recordCheckpoint.mutationOptions(),
);
const load = useMutation(api.trainScheduling.loadScheduleBooking.mutationOptions());
// "Leave behind": the cargo is not on the train — unassign frees its wagons
// and returns the booking to the pool for a later schedule. Reversible (the
// booking can be re-assigned), so no extra confirm step.
const leave = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
const yard = yardWorkQuery.data?.yards.find((y) => y.yardId === station?.yardId);
const boarders: YardWorkBookingRow[] = yard?.toLoad ?? [];
@@ -196,6 +201,28 @@ export function LogPassYardWorkModal({
);
};
const doLeave = (row: YardWorkBookingRow) => {
leave.mutate(
{ id: scheduleId, bookingId: row.id },
{
onSuccess: () => {
toast({
title: `${row.reference ?? "Booking"} left behind`,
description:
"Removed from this train — wagons freed, booking returned to the pool for a later schedule.",
});
void yardWorkQuery.refetch();
},
onError: (err) =>
toast({
title: "Could not leave booking behind",
description: parseError(err, "Please try again"),
variant: "destructive",
}),
},
);
};
const hasWork = boarders.length > 0 || arrivals.length > 0;
return (
@@ -355,30 +382,54 @@ export function LogPassYardWorkModal({
</Table.Td>
<Table.Td>
{!row.loadedAt ? (
<Tooltip
label={
!canLoad
? "You don't have permission to load cargo"
: !logged
? "Log the pass first — the train must be at this yard"
: !row.canLoad
? "Booking is not ready to load (payment pending)"
: "Confirm cargo loaded onto the train"
}
>
<Button
size="compact-xs"
variant="light"
leftSection={<PackageCheck size={13} />}
disabled={!canLoad || !logged || !row.canLoad}
loading={
load.isPending && load.variables?.bookingId === row.id
<Group gap={6} wrap="nowrap">
<Tooltip
label={
!canLoad
? "You don't have permission to load cargo"
: !logged
? "Log the pass first — the train must be at this yard"
: !row.canLoad
? "Booking is not ready to load (payment pending)"
: "Confirm cargo loaded onto the train"
}
onClick={() => doLoad(row)}
>
Load
</Button>
</Tooltip>
<Button
size="compact-xs"
variant="light"
leftSection={<PackageCheck size={13} />}
disabled={!canLoad || !logged || !row.canLoad}
loading={
load.isPending && load.variables?.bookingId === row.id
}
onClick={() => doLoad(row)}
>
Load
</Button>
</Tooltip>
<Tooltip
label={
row.isGovernment
? "Government bookings cannot be removed from a train"
: !canLeave
? "You don't have permission to remove bookings"
: "Cargo is not on the train — free its wagons and return the booking to the pool"
}
>
<Button
size="compact-xs"
variant="light"
color="red"
disabled={!canLeave || row.isGovernment}
loading={
leave.isPending && leave.variables?.bookingId === row.id
}
onClick={() => doLeave(row)}
>
Leave
</Button>
</Tooltip>
</Group>
) : null}
</Table.Td>
</Table.Tr>