mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
fix train builder and consolidation
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
@@ -324,6 +325,21 @@ export default function NewBookingPage() {
|
||||
const containerWeight = lines.reduce((s, l) => s + (l.quantity || 0) * (l.vgmPerUnitTons || 0), 0);
|
||||
const cargoTotalWeightVgm = freightType === "CONTAINER" ? containerWeight : bulkWeight;
|
||||
|
||||
// 20ft containers ride two per wagon, so a booking must hold an even number
|
||||
// of them — odd counts would leave half a wagon waiting on a co-loader
|
||||
// (cross-booking consolidation is disabled for now).
|
||||
const twentyFtCount = useMemo(() => {
|
||||
const sizeById = new Map<string, string>();
|
||||
for (const group of refData?.containers ?? []) {
|
||||
for (const type of group.types) sizeById.set(type.id, group.size);
|
||||
}
|
||||
return lines.reduce((sum, l) => {
|
||||
const size = l.containerTypeId ? (sizeById.get(l.containerTypeId) ?? "") : "";
|
||||
return String(size).includes("20") ? sum + (l.quantity || 0) : sum;
|
||||
}, 0);
|
||||
}, [refData?.containers, lines]);
|
||||
const hasOdd20ft = freightType === "CONTAINER" && twentyFtCount % 2 === 1;
|
||||
|
||||
// ---- validation ----
|
||||
const lineValid = (l: ContainerLine) =>
|
||||
Boolean(l.containerTypeId) && l.quantity >= 1 && l.vgmPerUnitTons > 0;
|
||||
@@ -344,7 +360,7 @@ export default function NewBookingPage() {
|
||||
(isGovernment ? Boolean(govCompanyId && govProfileId) : Boolean(companyId)) &&
|
||||
(freightType === "BULK"
|
||||
? Boolean(cargoTypeId) && bulkWeight > 0
|
||||
: allLinesValid);
|
||||
: allLinesValid && !hasOdd20ft);
|
||||
|
||||
const updateLine = (key: string, patch: Partial<ContainerLine>) =>
|
||||
setLines((prev) => prev.map((l) => (l.key === key ? { ...l, ...patch } : l)));
|
||||
@@ -842,6 +858,20 @@ export default function NewBookingPage() {
|
||||
</Group>
|
||||
) : null}
|
||||
|
||||
{hasOdd20ft ? (
|
||||
<Alert color="red" icon={<AlertTriangle size={16} />} radius="md" mt="md">
|
||||
<Text size="sm" fw={600}>
|
||||
Odd number of 20ft containers ({twentyFtCount})
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>
|
||||
20ft containers travel two per wagon, so they must be booked in
|
||||
even numbers. Add one more 20ft container or remove one — e.g.
|
||||
book {twentyFtCount + 1} or {twentyFtCount - 1} instead of{" "}
|
||||
{twentyFtCount}.
|
||||
</Text>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Stack gap="sm" mt="lg">
|
||||
<Button
|
||||
size="md"
|
||||
|
||||
@@ -172,8 +172,8 @@ export default function TrainBuilderDetailPage() {
|
||||
{ label: "Locomotives", value: composition.locomotives.length, icon: TrainFront },
|
||||
{ label: "Wagons", value: totals.wagonCount, icon: TrainIcon },
|
||||
{
|
||||
label: "Payload available",
|
||||
value: `${totals.payloadCapacityTons}T of ${totals.maxPullWeightTons}T`,
|
||||
label: "Tare weight / haul limit",
|
||||
value: `${totals.totalTareTons}T of ${totals.maxPullWeightTons}T`,
|
||||
icon: Weight,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -43,6 +43,7 @@ import { CountdownTimer, DataTable, type ColumnDef } from "@edr/ui-common";
|
||||
import { KpiStrip, PageContainer } from "@/components/page";
|
||||
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
|
||||
import Breadcrumbs from "@/components/ui/Breadcrumbs";
|
||||
import AdjustConsistModal from "@/components/trainScheduling/AdjustConsistModal";
|
||||
import { TrainCompositionDiagram } from "@/components/trainScheduling/TrainCompositionDiagram";
|
||||
import {
|
||||
TrainConsistView,
|
||||
@@ -684,6 +685,7 @@ export default function BatchScheduleDetailPage() {
|
||||
);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string | null>("overview");
|
||||
const [adjustConsistOpen, setAdjustConsistOpen] = useState(false);
|
||||
const [selectedBookingId, setSelectedBookingId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -870,6 +872,17 @@ export default function BatchScheduleDetailPage() {
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
{data.train && ["DRAFT", "SCHEDULED"].includes(data.status) ? (
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<TrainFront size={16} />}
|
||||
onClick={() => setAdjustConsistOpen(true)}
|
||||
>
|
||||
Adjust consist
|
||||
</Button>
|
||||
) : null}
|
||||
{data.windowPhase === "DOC_REVIEW" ? (
|
||||
<Button
|
||||
color="yellow"
|
||||
@@ -1181,6 +1194,12 @@ export default function BatchScheduleDetailPage() {
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
<AdjustConsistModal
|
||||
scheduleId={data.scheduleId}
|
||||
opened={adjustConsistOpen}
|
||||
onClose={() => setAdjustConsistOpen(false)}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import { IntercityRideAlongPanel } from "@/components/trainScheduling/IntercityR
|
||||
import { YardWorkPanel } from "@/components/trainScheduling/YardWorkPanel";
|
||||
// import { ImportLoadingConfirmationPanel } from "@/components/trainScheduling/ImportLoadingConfirmationPanel";
|
||||
import { RescheduleTrainDialog } from "@/components/trainScheduling/RescheduleTrainDialog";
|
||||
import AdjustConsistModal from "@/components/trainScheduling/AdjustConsistModal";
|
||||
import BookingWindowSettingsModal from "@/components/trainScheduling/BookingWindowSettingsModal";
|
||||
// import { ScheduleBatchPanel } from "@/components/trainScheduling/ScheduleBatchPanel";
|
||||
import { ScheduleBookingsStep } from "@/components/trainScheduling/ScheduleBookingsStep";
|
||||
@@ -101,6 +102,7 @@ export default function TrainScheduleV2DetailPage() {
|
||||
const [previewResult, setPreviewResult] = useState<TrainSchedulePreviewResponse | null>(null);
|
||||
const [containerPlacements, setContainerPlacements] = useState<ContainerPlacement[]>([]);
|
||||
const [maintenanceOpen, setMaintenanceOpen] = useState(false);
|
||||
const [adjustConsistOpen, setAdjustConsistOpen] = useState(false);
|
||||
const [windowSettingsOpen, setWindowSettingsOpen] = useState(false);
|
||||
const [gatepassSecuredAt, setGatepassSecuredAt] = useState("");
|
||||
const [gatepassReference, setGatepassReference] = useState("");
|
||||
@@ -949,6 +951,18 @@ export default function TrainScheduleV2DetailPage() {
|
||||
Reschedule train
|
||||
</Button>
|
||||
) : null}
|
||||
{schedule.train && ["DRAFT", "SCHEDULED"].includes(schedule.status) ? (
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
radius="lg"
|
||||
size="sm"
|
||||
leftSection={<Train size={16} />}
|
||||
onClick={() => setAdjustConsistOpen(true)}
|
||||
>
|
||||
Adjust consist
|
||||
</Button>
|
||||
) : null}
|
||||
{gatepassApplies ? (
|
||||
gatepassSecured ? (
|
||||
<Button
|
||||
@@ -1173,6 +1187,12 @@ export default function TrainScheduleV2DetailPage() {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<AdjustConsistModal
|
||||
scheduleId={schedule.id}
|
||||
opened={adjustConsistOpen}
|
||||
onClose={() => setAdjustConsistOpen(false)}
|
||||
/>
|
||||
|
||||
<BookingWindowSettingsModal
|
||||
scheduleId={scheduleId ?? null}
|
||||
opened={windowSettingsOpen}
|
||||
|
||||
Reference in New Issue
Block a user