mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 14:48:18 +00:00
changes
This commit is contained in:
@@ -6,11 +6,13 @@ import {
|
||||
type DraggableStateSnapshot,
|
||||
type DropResult,
|
||||
} from "@hello-pangea/dnd";
|
||||
import { ActionIcon, Badge, Box, Group, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon, Badge, Box, Group, Menu, Stack, Text, Tooltip } from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { GripVertical, MapPin, Trash2, Wrench } from "lucide-react";
|
||||
import { memo, useCallback, useMemo, type ReactNode } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { api } from "@/services/api";
|
||||
import type { TrainCompositionWagon } from "@/services/trainBuilder.service";
|
||||
import { wagonTypeColor } from "./trainStatus";
|
||||
|
||||
@@ -38,6 +40,7 @@ function ConsistWagonList({
|
||||
onReorder,
|
||||
onRemove,
|
||||
onMaintenance,
|
||||
onChangeYard,
|
||||
busy = false,
|
||||
}: ConsistWagonListProps) {
|
||||
const onDragEnd = useCallback((result: DropResult) => {
|
||||
@@ -107,6 +110,7 @@ function ConsistWagonList({
|
||||
busy={busy}
|
||||
onRemove={onRemove}
|
||||
onMaintenance={onMaintenance}
|
||||
onChangeYard={onChangeYard}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
@@ -129,9 +133,68 @@ export interface ConsistWagonListProps {
|
||||
onRemove: (wagonId: string) => void;
|
||||
/** Detach the wagon and move it to MAINTENANCE status (page confirms first). */
|
||||
onMaintenance: (wagon: TrainCompositionWagon) => void;
|
||||
/** Move one wagon to another yard from its yard badge; absent = read-only badge. */
|
||||
onChangeYard?: (wagonId: string, currentYardId: string) => void;
|
||||
busy?: boolean;
|
||||
}
|
||||
|
||||
/** Yard badge that opens a yard picker when `onChange` is provided. */
|
||||
function WagonYardBadge({
|
||||
wagon,
|
||||
busy,
|
||||
onChange,
|
||||
}: {
|
||||
wagon: TrainCompositionWagon;
|
||||
busy: boolean;
|
||||
onChange?: (wagonId: string, currentYardId: string) => void;
|
||||
}) {
|
||||
const label = wagon.currentYard?.label ?? wagon.currentYard?.code ?? "No yard";
|
||||
const yardsQuery = useQuery(
|
||||
api.routes.yards.queryOptions({ staleTime: 5 * 60_000, enabled: Boolean(onChange) }),
|
||||
);
|
||||
if (!onChange) {
|
||||
return wagon.currentYard ? (
|
||||
<Badge variant="outline" color="gray" size="xs" radius="sm" leftSection={<MapPin size={10} />}>
|
||||
{label}
|
||||
</Badge>
|
||||
) : null;
|
||||
}
|
||||
return (
|
||||
<Menu shadow="md" width={240} withinPortal>
|
||||
<Menu.Target>
|
||||
<Badge
|
||||
component="button"
|
||||
type="button"
|
||||
variant="outline"
|
||||
color="blue"
|
||||
size="xs"
|
||||
radius="sm"
|
||||
leftSection={<MapPin size={10} />}
|
||||
disabled={busy}
|
||||
style={{ cursor: busy ? "default" : "pointer" }}
|
||||
// Stop the drag handle from swallowing the click.
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
aria-label={`Change yard of wagon ${wagon.wagonNumber}`}
|
||||
>
|
||||
{label}
|
||||
</Badge>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>Move wagon to yard</Menu.Label>
|
||||
{(yardsQuery.data ?? []).map((y) => (
|
||||
<Menu.Item
|
||||
key={y.id}
|
||||
disabled={y.id === wagon.currentYard?.id}
|
||||
onClick={() => onChange(wagon.id, y.id)}
|
||||
>
|
||||
{y.label ?? y.code}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
const WagonRow = memo(function WagonRow({
|
||||
wagon,
|
||||
index,
|
||||
@@ -141,6 +204,7 @@ const WagonRow = memo(function WagonRow({
|
||||
busy,
|
||||
onRemove,
|
||||
onMaintenance,
|
||||
onChangeYard,
|
||||
}: {
|
||||
wagon: TrainCompositionWagon;
|
||||
index: number;
|
||||
@@ -150,6 +214,7 @@ const WagonRow = memo(function WagonRow({
|
||||
busy: boolean;
|
||||
onRemove: (wagonId: string) => void;
|
||||
onMaintenance: (wagon: TrainCompositionWagon) => void;
|
||||
onChangeYard?: (wagonId: string, currentYardId: string) => void;
|
||||
}) {
|
||||
const color = wagonTypeColor(wagon.wagonType?.code);
|
||||
|
||||
@@ -195,11 +260,7 @@ const WagonRow = memo(function WagonRow({
|
||||
{wagon.wagonType.code}
|
||||
</Badge>
|
||||
) : null}
|
||||
{wagon.currentYard ? (
|
||||
<Badge variant="outline" color="gray" size="xs" radius="sm" leftSection={<MapPin size={10} />}>
|
||||
{wagon.currentYard.label ?? wagon.currentYard.code}
|
||||
</Badge>
|
||||
) : null}
|
||||
<WagonYardBadge wagon={wagon} busy={busy} onChange={onChangeYard} />
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{wagon.wagonType
|
||||
|
||||
@@ -218,6 +218,8 @@ export const FREIGHT_PERMS = {
|
||||
/** Train-builder detail Actions menu — each item its own grant. */
|
||||
changeLocomotives: "edr_freight_app:trains:change_locomotives",
|
||||
changeYard: "edr_freight_app:trains:change_yard",
|
||||
/** Move ONE coupled wagon to another yard from the Wagon order list. */
|
||||
changeWagonYard: "edr_freight_app:trains:change_wagon_yard",
|
||||
toggleActive: "edr_freight_app:trains:toggle_active",
|
||||
disband: "edr_freight_app:trains:disband",
|
||||
},
|
||||
|
||||
@@ -94,6 +94,7 @@ export default function TrainBuilderDetailPage() {
|
||||
const canAssign = hasPermission(user, FREIGHT_PERMS.trains.assignWagons);
|
||||
const canChangeLocomotives = hasPermission(user, FREIGHT_PERMS.trains.changeLocomotives);
|
||||
const canChangeYard = hasPermission(user, FREIGHT_PERMS.trains.changeYard);
|
||||
const canChangeWagonYard = hasPermission(user, FREIGHT_PERMS.trains.changeWagonYard);
|
||||
const canToggleActive = hasPermission(user, FREIGHT_PERMS.trains.toggleActive);
|
||||
const canDisband = hasPermission(user, FREIGHT_PERMS.trains.disband);
|
||||
|
||||
@@ -109,6 +110,7 @@ export default function TrainBuilderDetailPage() {
|
||||
);
|
||||
const assignWagons = useMutation(api.trainBuilder.assignWagons.mutationOptions());
|
||||
const removeWagon = useMutation(api.trainBuilder.removeWagon.mutationOptions());
|
||||
const setWagonYard = useMutation(api.trainBuilder.setWagonYard.mutationOptions());
|
||||
const maintenanceWagon = useMutation(
|
||||
api.trainBuilder.sendWagonToMaintenance.mutationOptions(),
|
||||
);
|
||||
@@ -162,6 +164,7 @@ export default function TrainBuilderDetailPage() {
|
||||
const busy =
|
||||
assignWagons.isPending ||
|
||||
removeWagon.isPending ||
|
||||
setWagonYard.isPending ||
|
||||
maintenanceWagon.isPending ||
|
||||
reorderWagons.isPending;
|
||||
|
||||
@@ -217,6 +220,16 @@ export default function TrainBuilderDetailPage() {
|
||||
},
|
||||
[withToast, removeWagon.mutateAsync, trainId],
|
||||
);
|
||||
const handleChangeWagonYard = useCallback(
|
||||
(wagonId: string, currentYardId: string) => {
|
||||
if (!trainId) return;
|
||||
void withToast(
|
||||
() => setWagonYard.mutateAsync({ id: trainId, wagonId, currentYardId }),
|
||||
"Could not change wagon yard",
|
||||
);
|
||||
},
|
||||
[withToast, setWagonYard.mutateAsync, trainId],
|
||||
);
|
||||
const handleMaintenance = useCallback(
|
||||
(wagon: TrainCompositionWagon) => setMaintenanceTarget(wagon),
|
||||
[],
|
||||
@@ -499,6 +512,9 @@ export default function TrainBuilderDetailPage() {
|
||||
onReorder={handleReorder}
|
||||
onRemove={handleRemove}
|
||||
onMaintenance={handleMaintenance}
|
||||
onChangeYard={
|
||||
composition.editable && canChangeWagonYard ? handleChangeWagonYard : undefined
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
@@ -2132,6 +2132,19 @@ export const api = {
|
||||
seedComposition,
|
||||
),
|
||||
|
||||
setWagonYard: endpoint<
|
||||
{ id: string; wagonId: string; currentYardId: string },
|
||||
TrainComposition
|
||||
>(
|
||||
"train-builder",
|
||||
"setWagonYard",
|
||||
({ id, wagonId, currentYardId }) =>
|
||||
trainBuilderService.setWagonYard(id, wagonId, currentYardId).then((r) => r.data),
|
||||
undefined,
|
||||
() => TRAIN_BUILDER_WAGON_INVALIDATIONS,
|
||||
seedComposition,
|
||||
),
|
||||
|
||||
removeWagon: endpoint<{ id: string; wagonId: string }, TrainComposition>(
|
||||
"train-builder",
|
||||
"removeWagon",
|
||||
|
||||
@@ -315,6 +315,9 @@ export const trainBuilderService = {
|
||||
/** Relocate the train — coupled locomotives and wagons move with it. */
|
||||
setYard: (id: string, currentYardId: string) =>
|
||||
apiClient.patch<TrainComposition>(`${BASE}/${id}/yard`, { currentYardId }),
|
||||
/** Move one coupled wagon to another yard; the train stays put. */
|
||||
setWagonYard: (id: string, wagonId: string, currentYardId: string) =>
|
||||
apiClient.patch<TrainComposition>(`${BASE}/${id}/wagons/${wagonId}/yard`, { currentYardId }),
|
||||
assignWagons: (id: string, wagonIds: string[]) =>
|
||||
apiClient.post<TrainComposition>(`${BASE}/${id}/wagons`, { wagonIds }),
|
||||
removeWagon: (id: string, wagonId: string) =>
|
||||
|
||||
Reference in New Issue
Block a user