implement freight permissions for trains, wagons, and routes

This commit is contained in:
Marshal
2026-07-23 05:57:14 +00:00
parent 2481f43f1f
commit 129a4d1d0a
19 changed files with 369 additions and 237 deletions

View File

@@ -3,6 +3,8 @@ import { Box, Button, Card, Container, Group, Modal, Select, Stack, Text, Title
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "@/services/api";
import { useAuth } from "@/auth/useAuth";
import { canFleetAction } from "@/lib/permissions";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
import { Inbox, Plus, Warehouse } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
@@ -37,6 +39,10 @@ const FleetResourcePage = () => {
const slug = getFleetSlugFromPath(location.pathname) ?? DEFAULT_SLUG;
const config = getFleetResource(slug);
const { toast } = useToast();
const { user } = useAuth();
const canCreate = canFleetAction(user, slug, "create");
const canUpdate = canFleetAction(user, slug, "update");
const canDelete = canFleetAction(user, slug, "delete");
const { pagination, setPagination } = usePagination({ pageSize: 10 });
const [search, setSearch] = useState("");
@@ -278,12 +284,16 @@ const FleetResourcePage = () => {
record={row.original}
config={config}
layout="compact"
onEdit={(record) => {
setEditing(record);
setFormOpen(true);
}}
onRemove={setRemoveTarget}
onAssignDriver={setAssigningDriver}
onEdit={
canUpdate
? (record) => {
setEditing(record);
setFormOpen(true);
}
: undefined
}
onRemove={canDelete ? setRemoveTarget : undefined}
onAssignDriver={canUpdate ? setAssigningDriver : undefined}
onHistory={setHistoryTarget}
/>
</div>
@@ -291,7 +301,7 @@ const FleetResourcePage = () => {
});
return base;
}, [config, dynamicOptions.yards]);
}, [config, dynamicOptions.yards, canUpdate, canDelete]);
const tableStatus = isLoading ? "loading" : isError ? "error" : "success";
@@ -390,15 +400,17 @@ const FleetResourcePage = () => {
<Group gap="sm">
{slug === "wagons" ? (
<>
<Button
variant="light"
color="edr-green"
leftSection={<Warehouse size={16} />}
styles={{ label: { fontWeight: 500 } }}
onClick={() => setWagonWorkspaceOpen(true)}
>
Yard Workspace
</Button>
{canUpdate ? (
<Button
variant="light"
color="edr-green"
leftSection={<Warehouse size={16} />}
styles={{ label: { fontWeight: 500 } }}
onClick={() => setWagonWorkspaceOpen(true)}
>
Yard Workspace
</Button>
) : null}
<Button
variant="light"
color="grape"
@@ -410,12 +422,14 @@ const FleetResourcePage = () => {
</Button>
</>
) : null}
<Button leftSection={<Plus size={16} />} styles={{ label: { fontWeight: 500 } }} onClick={() => {
setEditing(null);
setFormOpen(true);
}}>
{config.addLabel}
</Button>
{canCreate ? (
<Button leftSection={<Plus size={16} />} styles={{ label: { fontWeight: 500 } }} onClick={() => {
setEditing(null);
setFormOpen(true);
}}>
{config.addLabel}
</Button>
) : null}
</Group>
</Group>
@@ -529,11 +543,15 @@ const FleetResourcePage = () => {
pageCount={pageCount}
totalCount={filteredRows.length}
onPaginationChange={setPagination}
onEdit={(record) => {
setEditing(record);
setFormOpen(true);
}}
onRemove={setRemoveTarget}
onEdit={
canUpdate
? (record) => {
setEditing(record);
setFormOpen(true);
}
: undefined
}
onRemove={canDelete ? setRemoveTarget : undefined}
/>
)}
</Stack>

View File

@@ -36,6 +36,8 @@ import RuleEngineListFooter from "@/components/ruleEngine/RuleEngineListFooter";
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
import { api } from "@/services/api";
import { ruleEngineService } from "@/services/ruleEngine/ruleEngine.service";
import { useAuth } from "@/auth/useAuth";
import { canFleetAction } from "@/lib/permissions";
import { useToast } from "@/hooks/use-toast";
import {
formatRouteLabel,
@@ -160,6 +162,10 @@ export default function RoutesPage() {
const { viewMode, setViewMode } = useFleetViewMode("routes");
const { pagination, setPagination } = usePagination({ pageSize: 10 });
const { toast } = useToast();
const { user } = useAuth();
const canCreate = canFleetAction(user, "routes", "create");
const canUpdate = canFleetAction(user, "routes", "update");
const canDelete = canFleetAction(user, "routes", "delete");
const routesQuery = useQuery(api.routes.list.queryOptions());
const yardsQuery = useQuery(api.routes.yards.queryOptions());
@@ -441,26 +447,30 @@ export default function RoutesPage() {
<Eye size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label="Edit">
<ActionIcon variant="subtle" color="gray" onClick={() => openEdit(row.original)}>
<Edit size={16} />
</ActionIcon>
</Tooltip>
<Tooltip label="Mark stop working">
<ActionIcon
variant="subtle"
color="red"
disabled={row.original.status === "STOP_WORKING" || deactivateMutation.isPending}
onClick={() => handleDeactivate(row.original)}
>
<Trash2 size={16} />
</ActionIcon>
</Tooltip>
{canUpdate ? (
<Tooltip label="Edit">
<ActionIcon variant="subtle" color="gray" onClick={() => openEdit(row.original)}>
<Edit size={16} />
</ActionIcon>
</Tooltip>
) : null}
{canDelete ? (
<Tooltip label="Mark stop working">
<ActionIcon
variant="subtle"
color="red"
disabled={row.original.status === "STOP_WORKING" || deactivateMutation.isPending}
onClick={() => handleDeactivate(row.original)}
>
<Trash2 size={16} />
</ActionIcon>
</Tooltip>
) : null}
</Group>
),
},
];
}, [deactivateMutation.isPending]);
}, [deactivateMutation.isPending, canUpdate, canDelete]);
return (
<PageContainer>
@@ -468,9 +478,11 @@ export default function RoutesPage() {
title="Routes"
subtitle="Define rail corridors, segment distances, and operational status for train scheduling."
action={
<Button leftSection={<Plus size={18} />} onClick={openCreate}>
Add route
</Button>
canCreate ? (
<Button leftSection={<Plus size={18} />} onClick={openCreate}>
Add route
</Button>
) : undefined
}
/>
@@ -555,9 +567,11 @@ export default function RoutesPage() {
<Button variant="light" size="compact-sm" onClick={() => setViewing(route)}>
View
</Button>
<Button variant="light" size="compact-sm" onClick={() => openEdit(route)}>
Edit
</Button>
{canUpdate ? (
<Button variant="light" size="compact-sm" onClick={() => openEdit(route)}>
Edit
</Button>
) : null}
</Group>
</Stack>
</Card>