fix issue

This commit is contained in:
Marshal
2026-07-16 00:33:31 +00:00
parent 234a74e812
commit 41fe04652f
51 changed files with 1895 additions and 206 deletions

View File

@@ -95,7 +95,13 @@ function usedWeight(schedule: TrainScheduleDetail): number {
);
}
/** Max pull weight across all locomotives on the set (0 when unknown). */
/**
* Pull capacity of the set = the WEAKEST locomotive's max pull weight (0 when
* unknown). The API caps at the weakest loco, not the sum of all locos — a
* consist can only pull as hard as its weakest engine. Note: the API also adds
* the consist tare to the used weight when it checks this cap; tare isn't
* available client-side, so this meter compares cargo-only load against pull.
*/
function pullCapacity(schedule: TrainScheduleDetail): number {
const set = schedule.trainSet;
if (!set) return 0;
@@ -105,7 +111,8 @@ function pullCapacity(schedule: TrainScheduleDetail): number {
: set.locomotive
? [set.locomotive]
: [];
return locos.reduce((sum, l) => sum + (Number(l.maxPullWeightTons) || 0), 0);
if (locos.length === 0) return 0;
return Math.min(...locos.map((l) => Number(l.maxPullWeightTons) || 0));
}
export function ScheduleWorkspacePanel({

View File

@@ -19,7 +19,7 @@ export function AssignWagonDialog({ trainId }: { trainId: string }) {
const { toast } = useToast();
const available = (wagons ?? []).filter(
(w) => w.status === Freight.WagonStatus.Available || !w.trainId,
(w) => w.status === Freight.WagonStatus.Available && !w.trainId,
);
const yardLabelById = useMemo(

View File

@@ -183,10 +183,22 @@ const WagonYardWorkspaceModal = ({ opened, onClose }: WagonYardWorkspaceModalPro
return yardWagons.filter((w) => w.currentYardId === yardId && w.wagonTypeId === typeId);
}, [yardWagons, yardId, typeId]);
const availableWagons = useMemo(() => matching.filter((w) => w.status === AVAILABLE), [matching]);
const assignedWagons = useMemo(() => matching.filter((w) => w.status === ASSIGNED), [matching]);
// Wagons coupled to a built train (trainId set) are managed through the
// train-builder flow — they can't be bulk-flipped or transferred here, so
// keep them out of the action pools and bucket them under "Other".
const availableWagons = useMemo(
() => matching.filter((w) => w.status === AVAILABLE && !w.trainId),
[matching],
);
const assignedWagons = useMemo(
() => matching.filter((w) => w.status === ASSIGNED && !w.trainId),
[matching],
);
const otherWagons = useMemo(
() => matching.filter((w) => w.status !== AVAILABLE && w.status !== ASSIGNED),
() =>
matching.filter(
(w) => w.trainId != null || (w.status !== AVAILABLE && w.status !== ASSIGNED),
),
[matching],
);
const total = matching.length;

View File

@@ -298,7 +298,17 @@ const FleetResourcePage = () => {
const handleFormSubmit = async (values: Record<string, unknown>) => {
try {
if (editing && "id" in editing) {
await update.mutateAsync({ slug, id: String(editing.id), data: values });
// PATCH only the fields the user actually changed. Re-sending the whole
// form used to re-submit status/currentYardId on every save — which,
// for a locomotive/wagon coupled to a built train, silently diverged
// the consist (editing a name could move the loco to another yard).
const editingRecord = editing as unknown as Record<string, unknown>;
const changed = Object.fromEntries(
Object.entries(values).filter(
([key, value]) => value !== editingRecord[key],
),
);
await update.mutateAsync({ slug, id: String(editing.id), data: changed });
toast({ title: `${config.entityLabel} updated` });
} else {
await create.mutateAsync({ slug, data: values });