feat(backoffice): guard routes and actions by permission

Mirrors the new keys in lib/permissions.ts, wraps the warehouse,
overview, reports, support and booking-request routes in
RequirePermission, and gates the dispatch, mark-paid, invoice pay/cancel,
export and support-send actions behind their own keys. Removes duplicate
route blocks.
This commit is contained in:
Nathnael
2026-08-07 07:31:31 +00:00
parent 0114673120
commit c1fcabcba2
10 changed files with 153 additions and 237 deletions

View File

@@ -42,6 +42,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { KpiStrip, PageContainer } from "@/components/page";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import {
autoFillPlacements,
mergePlacementsWithSaved,
@@ -102,6 +104,7 @@ const parseError = (error: unknown, fallback: string) => {
};
export default function TrainScheduleV2DetailPage() {
const { user: authUser } = useAuth();
const { scheduleId } = useParams<{ scheduleId: string }>();
const { toast } = useToast();
const [activeStep, setActiveStep] = useState(0);
@@ -394,7 +397,9 @@ export default function TrainScheduleV2DetailPage() {
: [];
const canEditBookings = ["DRAFT", "SCHEDULED"].includes(schedule.status);
const canDispatch = schedule.status === "SCHEDULED";
const canDispatch =
schedule.status === "SCHEDULED" &&
hasPermission(authUser, FREIGHT_PERMS.trainScheduling.dispatch);
// Dispatch readiness: bookings with no wagon, and wagon-loaded bookings whose
// cargo staff never marked loaded. Both are warnings, not blockers — staff can

View File

@@ -63,7 +63,7 @@ import { api } from "@/services/api";
import { formatRouteLabel } from "@/services/routes.service";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/auth/useAuth";
import { canCreateSchedule, canUpdateSchedule } from "@/lib/permissions";
import { FREIGHT_PERMS, canCreateSchedule, hasPermission } from "@/lib/permissions";
import type {
CreateScheduleWindowRulePayload,
FreightType,
@@ -111,7 +111,7 @@ export default function TrainScheduleV2ListPage() {
const { toast } = useToast();
const { user } = useAuth();
const canCreate = canCreateSchedule(user);
const canUpdate = canUpdateSchedule(user);
const canDispatch = hasPermission(user, FREIGHT_PERMS.trainScheduling.dispatch);
const { viewMode, setViewMode } = useFleetViewMode("train-scheduling-v2");
const { pagination, setPagination } = usePagination({ pageSize: 10 });
const [search, setSearch] = useState("");
@@ -491,7 +491,7 @@ export default function TrainScheduleV2ListPage() {
{/* Start the run. Same transition as the detail page's
Dispatch button — that page also shows unassigned-wagon
and not-loaded warnings, so it stays the fuller surface. */}
{canUpdate && schedule.status === "SCHEDULED" ? (
{canDispatch && schedule.status === "SCHEDULED" ? (
<Menu.Item
leftSection={<Play size={15} />}
onClick={() => setDispatchTarget(schedule)}