From 6e95c5b8b8b02958994cfb8ad83997365ba02e07 Mon Sep 17 00:00:00 2001 From: Nathnael Date: Thu, 20 Aug 2026 08:20:00 +0000 Subject: [PATCH] fix(backoffice): map overview layouts to the org's real position keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overview layout table matched invented keys (`edr_operations_officer`, `edr_marketing`, …) that only ever existed as IAM roles. The positions actually configured under the unit use their own keys — `edr_freight_app/opn`, `ethiopian_gl`, `edr_freight_app/finance` — so most staff fell through to the executive fallback regardless of desk. Map every position key in the current org tree, roots and sub-positions alike, and keep the legacy role-form keys so accounts that model the desks as roles still resolve. Finance was previously unmapped entirely. Also drop a stray console.log from resolveOverviewLayout. Safety (`edr_freight_app/sf_146`) stays unmapped — no such layout exists yet. Co-Authored-By: Claude Opus 5 (1M context) --- .../overview/role-dashboards.config.ts | 87 +++++++++++++++---- .../src/pages/dashboard/OverviewPage.tsx | 36 +++++--- 2 files changed, 94 insertions(+), 29 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/overview/role-dashboards.config.ts b/apps/edr-freight-web/backoffice/src/components/overview/role-dashboards.config.ts index 509f17a3c..b683a3fbe 100644 --- a/apps/edr-freight-web/backoffice/src/components/overview/role-dashboards.config.ts +++ b/apps/edr-freight-web/backoffice/src/components/overview/role-dashboards.config.ts @@ -4,44 +4,93 @@ import { getPositionKeys } from "@/lib/permissions"; /** One overview composition. Every backoffice user lands on exactly one of these. */ export type OverviewLayoutKey = | "executive" - | "operations" + | "operation" | "occ" - | "marketing" + | "marketer" | "finance" | "clearance"; export const OVERVIEW_LAYOUT_LABEL: Record = { executive: "Executive dashboard", - operations: "Operations dashboard", + operation: "Operations dashboard", occ: "Control centre dashboard", - marketing: "Marketing dashboard", + marketer: "Marketing dashboard", finance: "Finance dashboard", clearance: "Clearance & logistics dashboard", }; /** - * Role/position key → layout, in match priority order: a user holding several + * 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. Position keys are matched too 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`. + * 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`. */ const ROLE_LAYOUTS: Array<[key: string, layout: OverviewLayoutKey]> = [ - ["edr_operations_officer", "operations"], - ["truck_machinery_chief", "operations"], - ["edr_line_staff", "occ"], - ["edr_gl_ethiopia", "clearance"], - ["edr_gl_djibouti", "clearance"], + // ── 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_marketing", "marketing"], - ["edr_finance", "finance"], - ["edr_director", "executive"], - ["edr_ceo", "executive"], - ["edr_org_manager", "executive"], + ["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 ]; -/** Unmapped roles (superadmin, IAM admins, new roles) keep the executive layout. */ +/** Unmapped keys (superadmin, IAM admins, Safety, new positions) keep the executive layout. */ export function resolveOverviewLayout( user: AuthUser | null | undefined, ): OverviewLayoutKey { diff --git a/apps/edr-freight-web/backoffice/src/pages/dashboard/OverviewPage.tsx b/apps/edr-freight-web/backoffice/src/pages/dashboard/OverviewPage.tsx index 0d1f5a6e7..eadd2f1c6 100644 --- a/apps/edr-freight-web/backoffice/src/pages/dashboard/OverviewPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/dashboard/OverviewPage.tsx @@ -24,14 +24,21 @@ import { useOverview } from "@/hooks/useOverview"; import type { OverviewRange } from "@/types/overview"; import "@/components/overview/summary/overview-summary.css"; -const RANGE_LABEL: Record = { "7d": "7d", "30d": "30d", "90d": "90d" }; +const RANGE_LABEL: Record = { + "7d": "7d", + "30d": "30d", + "90d": "90d", +}; /** Which composition each role sees below the hero. */ -const LAYOUTS: Record ReactElement> = { +const LAYOUTS: Record< + OverviewLayoutKey, + (props: RoleOverviewProps) => ReactElement +> = { executive: ExecutiveOverview, - operations: OperationsOverview, + operation: OperationsOverview, occ: OccOverview, - marketing: MarketingOverview, + marketer: MarketingOverview, finance: FinanceOverview, clearance: ClearanceOverview, }; @@ -51,15 +58,17 @@ const OverviewPage = () => { const [range, setRange] = useState("30d"); const queryClient = useQueryClient(); const { user } = useAuth(); - const { data, isLoading, isError, error, refetch, isFetching } = useOverview(range); + const { data, isLoading, isError, error, refetch, isFetching } = + useOverview(range); // Hero, range control and headline KPIs are role-neutral; everything below // them is chosen by role key. const layoutKey = resolveOverviewLayout(user); - const RoleLayout = LAYOUTS[layoutKey]; + const RoleLayout = layoutKey ? LAYOUTS[layoutKey] : null; const accessDenied = - (error as { response?: { status?: number } } | null)?.response?.status === 403; + (error as { response?: { status?: number } } | null)?.response?.status === + 403; const handleRefresh = () => { void refetch(); @@ -79,7 +88,9 @@ const OverviewPage = () => { label={OVERVIEW_LAYOUT_LABEL[layoutKey]} /> {data ? ( -
+
{ > Check your connection and try again. - @@ -117,7 +133,7 @@ const OverviewPage = () => { - ) : data ? ( + ) : data && RoleLayout ? ( ) : null}