mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
feat: add warehouse gate times editor modal for import trucks
Enable per-truck editing of warehouse gate arrival/departure times (arrivedAt/departedAt) via modal on Import Trucks page. Accessible via row action menu for EDR-haulage trucks. Includes backend endpoint POST /last-mile/:id/warehouse-gate-times and frontend modal with DateTimePicker inputs. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Group,
|
||||
Modal,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { DateTimePicker } from '@mantine/dates';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { lastMileService, type LastMileRecord } from '@/services/last-mile.service';
|
||||
|
||||
interface WarehouseGateTimesModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
record: LastMileRecord | null;
|
||||
}
|
||||
|
||||
const plateOf = (a: NonNullable<LastMileRecord['vehicleAssignments']>[number]) =>
|
||||
[a.vehicle?.code, a.vehicle?.plateNumber].filter(Boolean).join(' · ') || a.vehicleId;
|
||||
|
||||
export function WarehouseGateTimesModal({ opened, onClose, record }: WarehouseGateTimesModalProps) {
|
||||
const { toast } = useToast();
|
||||
const qc = useQueryClient();
|
||||
const id = record?.id ?? null;
|
||||
const assignments = record?.vehicleAssignments ?? [];
|
||||
|
||||
interface TruckRow {
|
||||
vehicleId: string;
|
||||
label: string;
|
||||
arrivedAt: Date | null;
|
||||
departedAt: Date | null;
|
||||
}
|
||||
|
||||
const [rows, setRows] = useState<Array<TruckRow>>([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (assignments.length > 0) {
|
||||
setRows(
|
||||
assignments.map((a) => ({
|
||||
vehicleId: a.vehicleId,
|
||||
label: plateOf(a),
|
||||
arrivedAt: a.arrivedAt ? new Date(a.arrivedAt) : null,
|
||||
departedAt: a.departedAt ? new Date(a.departedAt) : null,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, [assignments, opened]);
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
if (!id) return Promise.resolve(null);
|
||||
return lastMileService.setWarehouseGateTimes(id, rows.map(r => ({
|
||||
vehicleId: r.vehicleId,
|
||||
arrivedAt: r.arrivedAt?.toISOString() ?? null,
|
||||
departedAt: r.departedAt?.toISOString() ?? null,
|
||||
})));
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Warehouse gate times updated',
|
||||
});
|
||||
qc.invalidateQueries({ queryKey: ['last-mile-record-import-trucks', id] });
|
||||
onClose();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Failed to update warehouse gate times',
|
||||
description: error?.response?.data?.message || error?.message,
|
||||
});
|
||||
},
|
||||
onSettled: () => {
|
||||
setSaving(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
await updateMutation.mutateAsync();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal opened={opened} onClose={onClose} title="Warehouse Gate Times" size="lg">
|
||||
<Stack gap="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
Set arrival (gate-in) and departure (gate-out) times for each truck.
|
||||
</Text>
|
||||
|
||||
{/* @ts-ignore - DateTimePicker type inference issue with row state */}
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Plate</Table.Th>
|
||||
<Table.Th>Arrived At (Gate-In)</Table.Th>
|
||||
<Table.Th>Departed At (Gate-Out)</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{rows.map((row: TruckRow, idx: number) => (
|
||||
<Table.Tr key={row.vehicleId}>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={600}>
|
||||
{row.label}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<DateTimePicker
|
||||
placeholder="Select arrival time"
|
||||
value={(row.arrivedAt as unknown) as Date | null}
|
||||
onChange={(date) => {
|
||||
const newRows = [...rows];
|
||||
newRows[idx] = { ...row, arrivedAt: date };
|
||||
setRows(newRows);
|
||||
}}
|
||||
clearable
|
||||
size="sm"
|
||||
/>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<DateTimePicker
|
||||
placeholder="Select departure time"
|
||||
value={(row.departedAt as unknown) as Date | null}
|
||||
onChange={(date) => {
|
||||
const newRows = [...rows];
|
||||
newRows[idx] = { ...row, departedAt: date };
|
||||
setRows(newRows);
|
||||
}}
|
||||
clearable
|
||||
size="sm"
|
||||
/>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={onClose} disabled={saving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave} loading={saving}>
|
||||
Save Times
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user