mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
feat(permissions): add granular permissions for train management actions
feat(train-builder): update permissions checks for train actions in UI feat(contracts): enhance contract view and shipment request pages with new features test(shipment-preview): add tests for customs clearing logic in booking preview style(contract-sign-bar): create CSS for fixed sign bar layout
This commit is contained in:
@@ -188,6 +188,11 @@ export const FREIGHT_PERMS = {
|
||||
update: "edr_freight_app:trains:update",
|
||||
delete: "edr_freight_app:trains:delete",
|
||||
assignWagons: "edr_freight_app:trains:assign_wagons",
|
||||
/** Train-builder detail Actions menu — each item its own grant. */
|
||||
changeLocomotives: "edr_freight_app:trains:change_locomotives",
|
||||
changeYard: "edr_freight_app:trains:change_yard",
|
||||
toggleActive: "edr_freight_app:trains:toggle_active",
|
||||
disband: "edr_freight_app:trains:disband",
|
||||
},
|
||||
routes: {
|
||||
view: "edr_freight_app:routes:view",
|
||||
|
||||
@@ -47,7 +47,7 @@ import { TrainCompositionDiagram } from "@/components/trainScheduling/TrainCompo
|
||||
import { KpiStrip, PageContainer, PageHeader } from "@/components/page";
|
||||
import { api } from "@/services/api";
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { canFleetAction, FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
||||
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import type { TrainCompositionWagon } from "@/services/trainBuilder.service";
|
||||
|
||||
@@ -83,9 +83,11 @@ export default function TrainBuilderDetailPage() {
|
||||
const [maintenanceTarget, setMaintenanceTarget] =
|
||||
useState<TrainCompositionWagon | null>(null);
|
||||
const { user } = useAuth();
|
||||
const canUpdate = canFleetAction(user, "trains", "update");
|
||||
const canDelete = canFleetAction(user, "trains", "delete");
|
||||
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 canToggleActive = hasPermission(user, FREIGHT_PERMS.trains.toggleActive);
|
||||
const canDisband = hasPermission(user, FREIGHT_PERMS.trains.disband);
|
||||
|
||||
const compositionQuery = useQuery(
|
||||
api.trainBuilder.composition.queryOptions({ input: { id }, enabled: Boolean(id) }),
|
||||
@@ -172,7 +174,7 @@ export default function TrainBuilderDetailPage() {
|
||||
</Group>
|
||||
}
|
||||
action={
|
||||
canUpdate || canDelete ? (
|
||||
canChangeLocomotives || canChangeYard || canToggleActive || canDisband ? (
|
||||
<Menu position="bottom-end" withinPortal shadow="md" width={220}>
|
||||
<Menu.Target>
|
||||
<Button variant="default" rightSection={<MoreHorizontal size={16} />}>
|
||||
@@ -180,47 +182,49 @@ export default function TrainBuilderDetailPage() {
|
||||
</Button>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
{canUpdate ? (
|
||||
<>
|
||||
<Menu.Item
|
||||
leftSection={<Replace size={15} />}
|
||||
disabled={!composition.editable}
|
||||
onClick={() => setLocoModalOpen(true)}
|
||||
>
|
||||
Change locomotives
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<MapPin size={15} />}
|
||||
disabled={!composition.editable}
|
||||
onClick={() => setYardModalOpen(true)}
|
||||
>
|
||||
Change yard
|
||||
</Menu.Item>
|
||||
{composition.status === "DEACTIVATED" ? (
|
||||
<Menu.Item
|
||||
leftSection={<Power size={15} />}
|
||||
disabled={blockingLocomotives.length > 0}
|
||||
onClick={() =>
|
||||
void withToast(async () => {
|
||||
await activate.mutateAsync(composition.id);
|
||||
toast({ title: `Train ${composition.code} reactivated` });
|
||||
}, "Could not reactivate train")
|
||||
}
|
||||
>
|
||||
Reactivate train
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item
|
||||
leftSection={<PowerOff size={15} />}
|
||||
disabled={composition.activeSchedules.length > 0}
|
||||
onClick={() => setDeactivateOpen(true)}
|
||||
>
|
||||
Deactivate train
|
||||
</Menu.Item>
|
||||
)}
|
||||
</>
|
||||
{canChangeLocomotives ? (
|
||||
<Menu.Item
|
||||
leftSection={<Replace size={15} />}
|
||||
disabled={!composition.editable}
|
||||
onClick={() => setLocoModalOpen(true)}
|
||||
>
|
||||
Change locomotives
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
{canDelete ? (
|
||||
{canChangeYard ? (
|
||||
<Menu.Item
|
||||
leftSection={<MapPin size={15} />}
|
||||
disabled={!composition.editable}
|
||||
onClick={() => setYardModalOpen(true)}
|
||||
>
|
||||
Change yard
|
||||
</Menu.Item>
|
||||
) : null}
|
||||
{canToggleActive ? (
|
||||
composition.status === "DEACTIVATED" ? (
|
||||
<Menu.Item
|
||||
leftSection={<Power size={15} />}
|
||||
disabled={blockingLocomotives.length > 0}
|
||||
onClick={() =>
|
||||
void withToast(async () => {
|
||||
await activate.mutateAsync(composition.id);
|
||||
toast({ title: `Train ${composition.code} reactivated` });
|
||||
}, "Could not reactivate train")
|
||||
}
|
||||
>
|
||||
Reactivate train
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item
|
||||
leftSection={<PowerOff size={15} />}
|
||||
disabled={composition.activeSchedules.length > 0}
|
||||
onClick={() => setDeactivateOpen(true)}
|
||||
>
|
||||
Deactivate train
|
||||
</Menu.Item>
|
||||
)
|
||||
) : null}
|
||||
{canDisband ? (
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<Trash2 size={15} />}
|
||||
@@ -277,7 +281,7 @@ export default function TrainBuilderDetailPage() {
|
||||
.
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
{canUpdate ? (
|
||||
{canChangeLocomotives ? (
|
||||
<Button
|
||||
size="compact-sm"
|
||||
variant="light"
|
||||
|
||||
Reference in New Issue
Block a user