mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
feat: enhance locomotive creation and management with optional code generation and default max pull weight
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { Download, Zap, FileText, Clock } from "lucide-react";
|
||||
import { Stack, Text, Button } from "@mantine/core";
|
||||
|
||||
import { AllocateBookingWizard } from "@/components/trainScheduling/AllocateBookingWizard";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
import { BookingActionsMenu } from "./BookingActionsMenu";
|
||||
import { SectionCard } from "./detail/SectionCard";
|
||||
import { toBookingListRow } from "@/features/bookings/mapBookingListRow";
|
||||
import { canAllocateBooking } from "@/features/bookings/booking-actions.config";
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { canManageScheduling } from "@/lib/permissions";
|
||||
import type { useBookingMutations } from "@/hooks/bookings/useBookings";
|
||||
|
||||
type Mutations = ReturnType<typeof useBookingMutations>;
|
||||
@@ -21,11 +16,8 @@ interface BookingActionsToolbarProps {
|
||||
|
||||
/** Detail-page actions: primary toolbar + downloads. */
|
||||
export function BookingActionsToolbar({ booking, mutations }: BookingActionsToolbarProps) {
|
||||
const { user } = useAuth();
|
||||
const row = toBookingListRow(booking);
|
||||
const { status } = booking;
|
||||
const [allocateOpen, setAllocateOpen] = useState(false);
|
||||
const canAllocate = canManageScheduling(user);
|
||||
|
||||
const downloadBlob = async (fn: () => Promise<Blob>, filename: string) => {
|
||||
const blob = await fn();
|
||||
@@ -106,11 +98,7 @@ export function BookingActionsToolbar({ booking, mutations }: BookingActionsTool
|
||||
<Text size="xs" c="dimmed">
|
||||
Confirm each step before it is applied.
|
||||
</Text>
|
||||
<BookingActionsMenu
|
||||
row={row}
|
||||
variant="toolbar"
|
||||
onAllocateBooking={() => setAllocateOpen(true)}
|
||||
/>
|
||||
<BookingActionsMenu row={row} variant="toolbar" />
|
||||
</Stack>
|
||||
</SectionCard>
|
||||
|
||||
@@ -130,14 +118,6 @@ export function BookingActionsToolbar({ booking, mutations }: BookingActionsTool
|
||||
</Button>
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{canAllocate && canAllocateBooking(booking) ? (
|
||||
<AllocateBookingWizard
|
||||
booking={booking}
|
||||
opened={allocateOpen}
|
||||
onClose={() => setAllocateOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,12 +66,20 @@ const FleetFormDialog = ({
|
||||
const [values, setValues] = useState<Record<string, unknown>>({});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// Seed the form ONLY when the dialog opens or the edited record changes — NOT
|
||||
// when `fields`/`emptyValues` get new object refs (they're rebuilt whenever the
|
||||
// dynamic select options finish loading). Re-seeding on those would wipe the
|
||||
// user's in-progress edits (e.g. a changed Current Yard / status) the moment
|
||||
// the yard or wagon-type options resolve.
|
||||
const recordId =
|
||||
initialRecord && "id" in initialRecord ? String(initialRecord.id) : null;
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setValues(buildInitialValues(fields, emptyValues, initialRecord));
|
||||
setErrors({});
|
||||
}
|
||||
}, [open, fields, emptyValues, initialRecord]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, recordId]);
|
||||
|
||||
const shortFields = useMemo(
|
||||
() => fields.filter((f) => f.type !== "textarea"),
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
MessageSquareWarning,
|
||||
Play,
|
||||
ShieldCheck,
|
||||
TrainTrack,
|
||||
Truck,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
|
||||
@@ -388,47 +386,10 @@ export function getBookingActions(
|
||||
actions = withCancel(OPERATION_REVIEW_ACTIONS);
|
||||
break;
|
||||
case "PAID":
|
||||
if (
|
||||
canAllocateBooking({ status, schedulingStatus: ctx.schedulingStatus })
|
||||
) {
|
||||
actions = [
|
||||
{
|
||||
id: "allocateBooking",
|
||||
label: "Allocate booking",
|
||||
shortLabel: "Allocate",
|
||||
description: "Assign to train, wagons, and finalize schedule",
|
||||
confirmTitle: "Allocate booking?",
|
||||
confirmDescription: "Opens the train allocation wizard.",
|
||||
variant: "default",
|
||||
icon: TrainTrack,
|
||||
primary: true,
|
||||
},
|
||||
{
|
||||
id: "startTransit",
|
||||
label: "Start transit",
|
||||
shortLabel: "Transit",
|
||||
description: "Begin rail movement",
|
||||
confirmTitle: "Start transit?",
|
||||
confirmDescription: "The booking will move to in transit status.",
|
||||
variant: "default",
|
||||
icon: Truck,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
actions = [
|
||||
{
|
||||
id: "startTransit",
|
||||
label: "Start transit",
|
||||
shortLabel: "Transit",
|
||||
description: "Begin rail movement",
|
||||
confirmTitle: "Start transit?",
|
||||
confirmDescription: "The booking will move to in transit status.",
|
||||
variant: "default",
|
||||
icon: Truck,
|
||||
primary: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
// Allocate is handled by the Operations "Ready to allocate" queue, not the
|
||||
// per-booking action menu. Start transit was removed entirely. No per-row
|
||||
// action remains in the PAID state.
|
||||
actions = [];
|
||||
break;
|
||||
case "IN_TRANSIT":
|
||||
actions = [
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
FileSignature,
|
||||
Layers,
|
||||
LayoutGrid,
|
||||
Milestone,
|
||||
Package,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
@@ -288,6 +289,16 @@ export default function BookingRequestDetailPage() {
|
||||
booking={booking}
|
||||
mutations={mutations}
|
||||
/>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="default"
|
||||
leftSection={<Milestone size={16} />}
|
||||
onClick={() =>
|
||||
navigate(`/dashboard/bookings/${booking.id}/milestones`)
|
||||
}
|
||||
>
|
||||
View clearance milestones
|
||||
</Button>
|
||||
{showContractButton && (
|
||||
<Button
|
||||
fullWidth
|
||||
|
||||
@@ -246,7 +246,6 @@ const FleetResourcePage = () => {
|
||||
meta: { headerClassName, cellClassName },
|
||||
cell: ({ row }) => {
|
||||
const value = (row.original as unknown as Record<string, unknown>)[col.accessorKey];
|
||||
console.log(`${col.accessorKey}:`, value, 'format:', col.format);
|
||||
return formatFleetCell(value, col.format, col.accessorKey);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -147,8 +147,8 @@ export const FLEET_RESOURCES: FleetResourceConfig[] = [
|
||||
{ id: "maxPullWeightTons", header: "Max pull (tons)", accessorKey: "maxPullWeightTons", format: "number" },
|
||||
{ id: "maxTrainLengthMeters", header: "Max length (m)", accessorKey: "maxTrainLengthMeters", format: "number" },
|
||||
],
|
||||
// Code is auto-generated server-side (LOCO-NNN) — omitted from the form.
|
||||
formFields: [
|
||||
{ name: "code", label: "Code", type: "text", required: true },
|
||||
{ name: "name", label: "Name", type: "text" },
|
||||
{ name: "locomotiveType", label: "Locomotive type", type: "select", required: true, options: LOCOMOTIVE_TYPE_OPTIONS },
|
||||
{ name: "status", label: "Status", type: "select", required: true, options: LOCOMOTIVE_STATUS_OPTIONS },
|
||||
@@ -160,12 +160,11 @@ export const FLEET_RESOURCES: FleetResourceConfig[] = [
|
||||
{ name: "maxSpeedKmh", label: "Max speed (km/h)", type: "number" },
|
||||
],
|
||||
emptyValues: {
|
||||
code: "",
|
||||
name: "",
|
||||
locomotiveType: "DIESEL",
|
||||
status: "AVAILABLE",
|
||||
currentYardId: "",
|
||||
maxPullWeightTons: 0,
|
||||
maxPullWeightTons: 2500,
|
||||
maxTrainLengthMeters: 760,
|
||||
powerKw: "",
|
||||
tractionForceKn: "",
|
||||
|
||||
Reference in New Issue
Block a user