Merge pull request #1379 from Tria-plc/freight/nati-2

Freight/nati 2
This commit is contained in:
Nathnael Wondisha
2026-08-21 16:09:24 +03:00
committed by GitHub
38 changed files with 1286 additions and 1020 deletions

View File

@@ -1,6 +1,3 @@
import type { AuthUser } from "@/auth/types";
import { getPositionKeys } from "@/lib/permissions";
/** One overview composition. Every backoffice user lands on exactly one of these. */
export type OverviewLayoutKey =
| "executive"
@@ -20,80 +17,35 @@ export const OVERVIEW_LAYOUT_LABEL: Record<OverviewLayoutKey, string> = {
};
/**
* Position/role key → layout, in match priority order: a user holding several
* of these keys gets the first match, so the specific operational view wins
* over the broad executive one. Roles are matched alongside positions because
* the IAM payload models the GL desks as positions (`ethiopian_gl`) on some
* accounts and as roles (`edr_gl_ethiopia`) on others — see `getPositionKeys`.
*
* The `edr_freight_app/…` keys are the org's real position keys (root desks and
* their sub-positions) as configured under Unit → Departments. They are typed
* by hand in the Add/Edit Department form, so a new sub-position appears here
* only once someone adds it — unmapped keys fall through to `executive`.
* Priority order: a caller who holds more than one of the six
* `edr_freight_app:overview:<key>:view` permissions gets the FIRST match
* here — the specific operational view wins over the broad executive one.
* Mirrors `OVERVIEW_LAYOUT_KEYS` in the API's freight-permissions.registry.ts
* bit for bit; keep the two in sync if this ever changes.
*/
const ROLE_LAYOUTS: Array<[key: string, layout: OverviewLayoutKey]> = [
// ── Clearance & logistics: both GL desks, root and sub-positions ──────────
["ethiopian_gl", "clearance"],
["edr_freight_app/gl_003", "clearance"], // Ethiopian GL Chief
["edr_freight_app/off_001", "clearance"], // Ethiopian GL Director
["edr_freight_app/off_0056", "clearance"], // Ethiopian GL Officer
["djibouti_gl", "clearance"],
["edr_freight_app/dj_gl_001", "clearance"], // Djibouti GL Director
["edr_freight_app/dj_gl_002", "clearance"], // Djibouti GL Chief
["edr_freight_app/dj_gl_003", "clearance"], // Djibouti GL Officer
["edr_gl_ethiopia", "clearance"], // legacy role form
["edr_gl_djibouti", "clearance"], // legacy role form
// ── Control centre ───────────────────────────────────────────────────────
["edr_freight_app/occ_001", "occ"], // OCC
["edr_freight_app/occ_005", "occ"], // OCC Director
["edr_line_staff", "occ"], // legacy role form
// ── Operations: operations desk, track & machinery, rolling stock ─────────
["edr_freight_app/opn", "operation"], // Operation
["edr_freight_app/opcf", "operation"], // Operation Chief
["edr_freight_app/opdr", "operation"], // Operation Director
["edr_freight_app/opco", "operation"], // Operation Officer
["edr_freight_app/opp_005", "operation"], // Operation Dispatcher
["edr_freight_app/opp_0067", "operation"], // Gelan Operation Director
["edr_freight_app/track_001", "operation"], // Track And Machinery
["edr_freight_app/ttk_001", "operation"], // Track Director
["edr_freight_app/tto_001", "operation"], // Track Operator
["edr_freight_app/rool_001", "operation"], // Rolling Stock
["edr_freight_app/rl_003", "operation"], // Rolling Stock Director
["edr_freight_app/rl_009", "operation"], // Rolling Stock Team Lead
["edr_freight_app/rl_0090", "operation"], // Rolling Stock Dispatcher
["operation", "operation"],
["operations_chief", "operation"],
["dispatcher", "operation"],
["truck_machinery_chief", "operation"],
["edr_operations_officer", "operation"], // legacy role form
// ── Marketing ────────────────────────────────────────────────────────────
["edr_freight_app/edr_test_org_0022", "marketer"], // Commercial Marketing
["edr_freight_app/edr_test_org_00567", "marketer"], // Marketing Director
["edr_freight_app/edr_test_org_0054", "marketer"], // Marketing Chief
["edr_freight_app/edr_test_org_0013", "marketer"], // Marketing Officer
["marketer", "marketer"],
["edr_marketing", "marketer"], // legacy role form
// ── Finance ──────────────────────────────────────────────────────────────
["edr_freight_app/finance", "finance"],
["edr_finance", "finance"], // legacy role form
// ── Executive: org-wide desks with no operational queue of their own ──────
["ceo", "executive"],
["director", "executive"],
["chief", "executive"],
["edr_ceo", "executive"], // legacy role form
["edr_director", "executive"], // legacy role form
["edr_org_manager", "executive"], // legacy role form
const LAYOUT_PRIORITY: OverviewLayoutKey[] = [
"clearance",
"occ",
"operation",
"marketer",
"finance",
"executive",
];
/** Unmapped keys (superadmin, IAM admins, Safety, new positions) keep the executive layout. */
/**
* Which layout to render, given the keys `GET /overview/layouts` said the
* caller may see — the endpoint already filtered those by permission, so
* this only breaks the tie when a caller holds more than one. Same shape as
* the Reports page trusting `GET /reports`'s catalog rather than re-deriving
* access from permission keys client-side.
*
* Empty/unmapped falls back to the executive layout — same default the old
* role/position-key table used for superadmin, IAM admins, and any position
* that hasn't been granted one of these permissions yet.
*/
export function resolveOverviewLayout(
user: AuthUser | null | undefined,
allowed: OverviewLayoutKey[] | undefined,
): OverviewLayoutKey {
const held = new Set(getPositionKeys(user));
return ROLE_LAYOUTS.find(([key]) => held.has(key))?.[1] ?? "executive";
const held = new Set(allowed ?? []);
return LAYOUT_PRIORITY.find((key) => held.has(key)) ?? "executive";
}