mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
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:
@@ -68,6 +68,8 @@ export function ClearanceOpsTabs({
|
||||
Boolean(exchangeEntityId) &&
|
||||
(hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions) ||
|
||||
hasPermission(user, FREIGHT_PERMS.contracts.clearanceDjActions));
|
||||
// Risk assignment + incident reporting hit bookings:operations endpoints.
|
||||
const canOps = hasPermission(user, FREIGHT_PERMS.bookings.operations);
|
||||
const hasTabs = (showOpsTabs && hasOps) || showDocuments || showExchange;
|
||||
|
||||
if (!hasTabs) {
|
||||
@@ -98,12 +100,12 @@ export function ClearanceOpsTabs({
|
||||
Document exchange
|
||||
</Tabs.Tab>
|
||||
) : null}
|
||||
{showOpsTabs && riskMs ? (
|
||||
{showOpsTabs && canOps && riskMs ? (
|
||||
<Tabs.Tab value="risk" leftSection={<ShieldAlert size={14} />}>
|
||||
Risk assignment
|
||||
</Tabs.Tab>
|
||||
) : null}
|
||||
{showOpsTabs && bookingId ? (
|
||||
{showOpsTabs && canOps && bookingId ? (
|
||||
<Tabs.Tab value="incidents" leftSection={<AlertTriangle size={14} />}>
|
||||
Incidents
|
||||
</Tabs.Tab>
|
||||
@@ -129,7 +131,7 @@ export function ClearanceOpsTabs({
|
||||
</Tabs.Panel>
|
||||
) : null}
|
||||
|
||||
{showOpsTabs && riskMs && bookingId ? (
|
||||
{showOpsTabs && canOps && riskMs && bookingId ? (
|
||||
<Tabs.Panel value="risk">
|
||||
<SectionCard icon={ShieldAlert} title="Customs risk" accent="edr-green">
|
||||
<AssignRiskCard bookingId={bookingId} milestone={riskMs} />
|
||||
@@ -137,7 +139,7 @@ export function ClearanceOpsTabs({
|
||||
</Tabs.Panel>
|
||||
) : null}
|
||||
|
||||
{showOpsTabs && bookingId ? (
|
||||
{showOpsTabs && canOps && bookingId ? (
|
||||
<Tabs.Panel value="incidents">
|
||||
<SectionCard icon={AlertTriangle} title="Incident reports" accent="edr-green">
|
||||
<Stack gap="sm">
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
fetchViewableFile,
|
||||
} from "@/services/files.service";
|
||||
import { useContractClearanceMutations } from "@/hooks/contracts/useContracts";
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
||||
import { useFileViewer } from "@/hooks/useFileViewer";
|
||||
|
||||
export interface ContractClearanceReviewSectionProps {
|
||||
@@ -123,6 +125,21 @@ export function ContractClearanceReviewSection({
|
||||
} | null>(null);
|
||||
const { view, viewer } = useFileViewer();
|
||||
|
||||
// Mirror the API guards: Path A (self-clearance) actions need
|
||||
// ops_clearance_review; Path B (customs) review needs clearance_review or
|
||||
// the ET phased key. Without the matching key every action would 403 — show
|
||||
// the audit view instead of dead buttons.
|
||||
const { user } = useAuth();
|
||||
const canReviewHere = selfClear
|
||||
? hasPermission(user, FREIGHT_PERMS.contracts.opsClearanceReview)
|
||||
: hasPermission(user, FREIGHT_PERMS.contracts.clearanceReview) ||
|
||||
hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions);
|
||||
// Finalize has its own API key on the customs path (contracts:finalize_clearance).
|
||||
const canFinalizeHere = selfClear
|
||||
? hasPermission(user, FREIGHT_PERMS.contracts.opsClearanceReview)
|
||||
: hasPermission(user, FREIGHT_PERMS.contracts.finalizeClearance);
|
||||
readOnly = readOnly || !canReviewHere;
|
||||
|
||||
const reviewerTeam = selfClear ? "Operations" : "Global Logistics";
|
||||
|
||||
const { data: clearance, isLoading } = useQuery({
|
||||
@@ -484,7 +501,7 @@ export function ContractClearanceReviewSection({
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<CheckCircle2 size={16} />}
|
||||
disabled={!clearance.allApproved}
|
||||
disabled={!clearance.allApproved || !canFinalizeHere}
|
||||
loading={finalizeClearance.isPending}
|
||||
onClick={() =>
|
||||
finalizeClearance.mutate(undefined, {
|
||||
|
||||
@@ -19,6 +19,8 @@ import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge";
|
||||
import { api } from "@/services/api";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import type { TrainScheduleDetail } from "@/types/trainScheduling";
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
|
||||
|
||||
interface ScheduleBatchPanelProps {
|
||||
schedule: TrainScheduleDetail;
|
||||
@@ -31,6 +33,8 @@ const windowColor: Record<string, string> = {
|
||||
};
|
||||
|
||||
export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) {
|
||||
const { user } = useAuth();
|
||||
const canMarkPaid = hasPermission(user, FREIGHT_PERMS.trainScheduling.markPaid);
|
||||
const { toast } = useToast();
|
||||
const actions = {
|
||||
runBatch: useMutation(api.trainScheduling.runBatch.mutationOptions()),
|
||||
@@ -179,7 +183,7 @@ export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) {
|
||||
<Table.Td>
|
||||
{!locked && (
|
||||
<Group gap={6} justify="flex-end" wrap="nowrap">
|
||||
{b.status !== "PAID" && (
|
||||
{canMarkPaid && b.status !== "PAID" && (
|
||||
<Button
|
||||
size="compact-xs"
|
||||
variant="light"
|
||||
|
||||
Reference in New Issue
Block a user