@pablo2410/shared-ui 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PHZHX3MF.js → chunk-GU3OMCTY.js} +4 -4
- package/dist/chunk-NXSFRNI7.js +75 -0
- package/dist/chunk-NXSFRNI7.js.map +1 -0
- package/dist/hierarchy/core.d.ts +144 -0
- package/dist/hierarchy/core.js +19 -0
- package/dist/hierarchy/core.js.map +1 -0
- package/dist/hierarchy/index.d.ts +3 -118
- package/dist/hierarchy/index.js +12 -43
- package/dist/hierarchy/index.js.map +1 -1
- package/dist/layout/index.js +2 -2
- package/dist/reporting/index.js +3 -3
- package/dist/shell/index.js +2 -2
- package/package.json +5 -1
- /package/dist/{chunk-PHZHX3MF.js.map → chunk-GU3OMCTY.js.map} +0 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cn
|
|
3
|
-
} from "./chunk-JT3XLKKD.js";
|
|
4
1
|
import {
|
|
5
2
|
Avatar,
|
|
6
3
|
AvatarFallback,
|
|
@@ -22,6 +19,9 @@ import {
|
|
|
22
19
|
useIsMobile,
|
|
23
20
|
useSidebar
|
|
24
21
|
} from "./chunk-LTUYIBMA.js";
|
|
22
|
+
import {
|
|
23
|
+
cn
|
|
24
|
+
} from "./chunk-JT3XLKKD.js";
|
|
25
25
|
|
|
26
26
|
// src/layout/SharedSidebar.tsx
|
|
27
27
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -895,4 +895,4 @@ export {
|
|
|
895
895
|
policyDeploymentConfig,
|
|
896
896
|
SERVICE_CONFIGS
|
|
897
897
|
};
|
|
898
|
-
//# sourceMappingURL=chunk-
|
|
898
|
+
//# sourceMappingURL=chunk-GU3OMCTY.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/hierarchy/orgHierarchy.ts
|
|
2
|
+
var ORG_HIERARCHY_EMPTY = {
|
|
3
|
+
enterprise: null,
|
|
4
|
+
businessUnit: null,
|
|
5
|
+
site: null,
|
|
6
|
+
area: null,
|
|
7
|
+
asset: null
|
|
8
|
+
};
|
|
9
|
+
var ORG_HIERARCHY_DATA_EMPTY = {
|
|
10
|
+
enterprises: [],
|
|
11
|
+
businessUnits: [],
|
|
12
|
+
sites: [],
|
|
13
|
+
areas: [],
|
|
14
|
+
assets: []
|
|
15
|
+
};
|
|
16
|
+
var ORG_HIERARCHY_LEVELS = [
|
|
17
|
+
"enterprise",
|
|
18
|
+
"businessUnit",
|
|
19
|
+
"site",
|
|
20
|
+
"area",
|
|
21
|
+
"asset"
|
|
22
|
+
];
|
|
23
|
+
var ORG_HIERARCHY_LABELS = {
|
|
24
|
+
enterprise: "Enterprise",
|
|
25
|
+
businessUnit: "Business Unit",
|
|
26
|
+
site: "Site",
|
|
27
|
+
area: "Area",
|
|
28
|
+
asset: "Asset"
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/hierarchy/enterpriseOverride.ts
|
|
32
|
+
var ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
|
|
33
|
+
function parseCookieValue(cookieHeader, name) {
|
|
34
|
+
const match = cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
|
|
35
|
+
if (!match) return null;
|
|
36
|
+
return decodeURIComponent(match.slice(name.length + 1));
|
|
37
|
+
}
|
|
38
|
+
function applyEnterpriseOverride(user, cookieHeader) {
|
|
39
|
+
if (!user || user.role !== "platform_admin") return user;
|
|
40
|
+
const activeEnterprise = parseCookieValue(
|
|
41
|
+
cookieHeader,
|
|
42
|
+
ACTIVE_ENTERPRISE_COOKIE
|
|
43
|
+
);
|
|
44
|
+
if (!activeEnterprise) return user;
|
|
45
|
+
const parsedId = parseInt(activeEnterprise, 10);
|
|
46
|
+
if (isNaN(parsedId) || parsedId <= 0) return user;
|
|
47
|
+
return { ...user, enterpriseId: parsedId };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/hierarchy/scopeHierarchy.ts
|
|
51
|
+
function scopeHierarchyToEnterprise(data, enterpriseId) {
|
|
52
|
+
if (!data || enterpriseId === null) {
|
|
53
|
+
return { enterprises: [], businessUnits: [], sites: [], areas: [], assets: [] };
|
|
54
|
+
}
|
|
55
|
+
const sites = data.sites.filter((s) => s.enterpriseId === enterpriseId);
|
|
56
|
+
const siteIds = new Set(sites.map((s) => s.id));
|
|
57
|
+
return {
|
|
58
|
+
enterprises: data.enterprises.filter((e) => e.id === enterpriseId),
|
|
59
|
+
businessUnits: data.businessUnits.filter((bu) => bu.enterpriseId === enterpriseId),
|
|
60
|
+
sites,
|
|
61
|
+
areas: data.areas.filter((a) => siteIds.has(a.siteId)),
|
|
62
|
+
assets: data.assets.filter((a) => siteIds.has(a.siteId))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
ORG_HIERARCHY_EMPTY,
|
|
68
|
+
ORG_HIERARCHY_DATA_EMPTY,
|
|
69
|
+
ORG_HIERARCHY_LEVELS,
|
|
70
|
+
ORG_HIERARCHY_LABELS,
|
|
71
|
+
ACTIVE_ENTERPRISE_COOKIE,
|
|
72
|
+
applyEnterpriseOverride,
|
|
73
|
+
scopeHierarchyToEnterprise
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-NXSFRNI7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hierarchy/orgHierarchy.ts","../src/hierarchy/enterpriseOverride.ts","../src/hierarchy/scopeHierarchy.ts"],"sourcesContent":["/**\n * @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities\n *\n * Hierarchy: Enterprise > Business Unit > Site > Area > Asset\n *\n * Single source of truth for org-hierarchy types used by:\n * - Portal (direct DB via tRPC)\n * - All subdomains (via Portal Service API)\n *\n * The canonical data shape matches the Portal Service API response:\n * GET /api/service/hierarchy\n */\n\n/* ── Base Node ── */\n\nexport interface OrgHierarchyNode {\n id: number;\n name: string;\n code?: string;\n}\n\n/* ── Flat hierarchy data — matches Portal Service API /api/service/hierarchy ── */\n\nexport interface OrgHierarchyEnterprise extends OrgHierarchyNode {}\n\nexport interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {\n enterpriseId: number;\n}\n\nexport interface OrgHierarchySite extends OrgHierarchyNode {\n enterpriseId: number;\n businessUnitId: number;\n location?: string | null;\n}\n\nexport interface OrgHierarchyArea extends OrgHierarchyNode {\n siteId: number;\n}\n\nexport interface OrgHierarchyAsset extends OrgHierarchyNode {\n siteId: number;\n areaId: number;\n assetType?: string;\n}\n\n/**\n * Canonical flat hierarchy payload.\n * Every consumer (Portal tRPC, Service API REST) must return this shape.\n */\nexport interface OrgHierarchyData {\n enterprises: OrgHierarchyEnterprise[];\n businessUnits: OrgHierarchyBusinessUnit[];\n sites: OrgHierarchySite[];\n areas: OrgHierarchyArea[];\n assets: OrgHierarchyAsset[];\n}\n\n/* ── Selection state ── */\n\nexport interface OrgHierarchySelection {\n enterprise: OrgHierarchyNode | null;\n businessUnit: OrgHierarchyNode | null;\n site: OrgHierarchyNode | null;\n area: OrgHierarchyNode | null;\n asset: OrgHierarchyNode | null;\n}\n\nexport const ORG_HIERARCHY_EMPTY: OrgHierarchySelection = {\n enterprise: null,\n businessUnit: null,\n site: null,\n area: null,\n asset: null,\n};\n\n/**\n * An empty hierarchy payload. Use as the canonical \"no data\" value so every\n * consumer returns the same shape (never null/undefined for a missing tree).\n */\nexport const ORG_HIERARCHY_DATA_EMPTY: OrgHierarchyData = {\n enterprises: [],\n businessUnits: [],\n sites: [],\n areas: [],\n assets: [],\n};\n\n/* ── Context value ── */\n\nexport interface OrgHierarchyContextValue {\n /** Current selection at each level */\n selection: OrgHierarchySelection;\n\n /** Flat lists filtered by current selection */\n enterprises: OrgHierarchyNode[];\n businessUnits: OrgHierarchyNode[];\n sites: OrgHierarchyNode[];\n areas: OrgHierarchyNode[];\n assets: OrgHierarchyNode[];\n\n /** Setters — selecting a level clears all levels below it */\n selectEnterprise: (node: OrgHierarchyNode | null) => void;\n selectBusinessUnit: (node: OrgHierarchyNode | null) => void;\n selectSite: (node: OrgHierarchyNode | null) => void;\n selectArea: (node: OrgHierarchyNode | null) => void;\n selectAsset: (node: OrgHierarchyNode | null) => void;\n\n /** Whether the user can switch enterprises (platform_admin only) */\n canSwitchEnterprise: boolean;\n\n /** Loading state */\n loading: boolean;\n\n /** Breadcrumb trail as an array of { level, node } */\n breadcrumbs: { level: string; node: OrgHierarchyNode }[];\n}\n\n/* ── Hierarchy levels (for iteration) ── */\n\nexport const ORG_HIERARCHY_LEVELS = [\n \"enterprise\",\n \"businessUnit\",\n \"site\",\n \"area\",\n \"asset\",\n] as const;\n\nexport type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];\n\n/** Human-readable labels for each level */\nexport const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string> = {\n enterprise: \"Enterprise\",\n businessUnit: \"Business Unit\",\n site: \"Site\",\n area: \"Area\",\n asset: \"Asset\",\n};\n","/**\n * Enterprise Override — shared helper for platform_admin enterprise switching.\n *\n * When a platform_admin switches enterprise in the Portal UI, a cookie\n * `oplytics-active-enterprise` is set with the selected enterprise ID.\n * This cookie is shared across all *.oplytics.digital subdomains.\n *\n * Each subdomain's createContext should call applyEnterpriseOverride()\n * to override the user's DB-stored enterpriseId with the cookie value.\n *\n * Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect\n */\n\n/**\n * Cookie name for the platform_admin active enterprise override.\n * Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.\n */\nexport const ACTIVE_ENTERPRISE_COOKIE = \"oplytics-active-enterprise\";\n\n/**\n * Parse a single cookie value from a cookie header string.\n */\nfunction parseCookieValue(cookieHeader: string, name: string): string | null {\n const match = cookieHeader\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(`${name}=`));\n if (!match) return null;\n return decodeURIComponent(match.slice(name.length + 1));\n}\n\n/**\n * Apply the enterprise override for platform_admin users.\n *\n * If the user is a platform_admin and has the active enterprise cookie set,\n * returns a shallow copy of the user with the overridden enterpriseId.\n * Otherwise returns the user unchanged.\n *\n * @param user - The authenticated user (or null)\n * @param cookieHeader - The raw Cookie header string from the request\n * @returns The user with potentially overridden enterpriseId\n */\nexport function applyEnterpriseOverride<\n T extends { role: string; enterpriseId?: number | null },\n>(user: T | null, cookieHeader: string): T | null {\n if (!user || user.role !== \"platform_admin\") return user;\n\n const activeEnterprise = parseCookieValue(\n cookieHeader,\n ACTIVE_ENTERPRISE_COOKIE,\n );\n if (!activeEnterprise) return user;\n\n const parsedId = parseInt(activeEnterprise, 10);\n if (isNaN(parsedId) || parsedId <= 0) return user;\n\n // Return a shallow copy with the overridden enterpriseId\n return { ...user, enterpriseId: parsedId };\n}\n","/**\n * scopeHierarchyToEnterprise — the single source of truth for enterprise isolation.\n *\n * The Portal Service API (`GET /api/service/hierarchy`) is called with a\n * platform-level service key, so it returns every enterprise's hierarchy. This\n * function filters that payload down to ONE enterprise before it reaches a\n * subdomain client. It is pure and isomorphic (no I/O), so the same rule runs in\n * the fetch service, in tests, and anywhere else scoping is needed.\n *\n * Policy (deliberate — do not \"helpfully\" relax it):\n * - `null` data → empty hierarchy.\n * - `null` enterpriseId → empty hierarchy. Enterprise selection is owned solely\n * by the Portal switcher; a caller with no resolved enterprise (e.g. a\n * platform admin who hasn't picked one in Portal yet) sees nothing until they\n * select. Every non-admin always has exactly one enterprise, so they are\n * unaffected. Admin/Portal tooling that genuinely needs the full tree must use\n * the unscoped fetch, never this function.\n */\nimport type { OrgHierarchyData } from \"./orgHierarchy\";\n\nexport function scopeHierarchyToEnterprise(\n data: OrgHierarchyData | null,\n enterpriseId: number | null,\n): OrgHierarchyData {\n if (!data || enterpriseId === null) {\n return { enterprises: [], businessUnits: [], sites: [], areas: [], assets: [] };\n }\n\n const sites = data.sites.filter((s) => s.enterpriseId === enterpriseId);\n const siteIds = new Set(sites.map((s) => s.id));\n\n return {\n enterprises: data.enterprises.filter((e) => e.id === enterpriseId),\n businessUnits: data.businessUnits.filter((bu) => bu.enterpriseId === enterpriseId),\n sites,\n areas: data.areas.filter((a) => siteIds.has(a.siteId)),\n assets: data.assets.filter((a) => siteIds.has(a.siteId)),\n };\n}\n"],"mappings":";AAmEO,IAAM,sBAA6C;AAAA,EACxD,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAMO,IAAM,2BAA6C;AAAA,EACxD,aAAa,CAAC;AAAA,EACd,eAAe,CAAC;AAAA,EAChB,OAAO,CAAC;AAAA,EACR,OAAO,CAAC;AAAA,EACR,QAAQ,CAAC;AACX;AAkCO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,uBAA0D;AAAA,EACrE,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;;;ACvHO,IAAM,2BAA2B;AAKxC,SAAS,iBAAiB,cAAsB,MAA6B;AAC3E,QAAM,QAAQ,aACX,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,CAAC;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,mBAAmB,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AACxD;AAaO,SAAS,wBAEd,MAAgB,cAAgC;AAChD,MAAI,CAAC,QAAQ,KAAK,SAAS,iBAAkB,QAAO;AAEpD,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,EACF;AACA,MAAI,CAAC,iBAAkB,QAAO;AAE9B,QAAM,WAAW,SAAS,kBAAkB,EAAE;AAC9C,MAAI,MAAM,QAAQ,KAAK,YAAY,EAAG,QAAO;AAG7C,SAAO,EAAE,GAAG,MAAM,cAAc,SAAS;AAC3C;;;ACtCO,SAAS,2BACd,MACA,cACkB;AAClB,MAAI,CAAC,QAAQ,iBAAiB,MAAM;AAClC,WAAO,EAAE,aAAa,CAAC,GAAG,eAAe,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAChF;AAEA,QAAM,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,iBAAiB,YAAY;AACtE,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAE9C,SAAO;AAAA,IACL,aAAa,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,OAAO,YAAY;AAAA,IACjE,eAAe,KAAK,cAAc,OAAO,CAAC,OAAO,GAAG,iBAAiB,YAAY;AAAA,IACjF;AAAA,IACA,OAAO,KAAK,MAAM,OAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,MAAM,CAAC;AAAA,IACrD,QAAQ,KAAK,OAAO,OAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,MAAM,CAAC;AAAA,EACzD;AACF;","names":[]}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities
|
|
3
|
+
*
|
|
4
|
+
* Hierarchy: Enterprise > Business Unit > Site > Area > Asset
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth for org-hierarchy types used by:
|
|
7
|
+
* - Portal (direct DB via tRPC)
|
|
8
|
+
* - All subdomains (via Portal Service API)
|
|
9
|
+
*
|
|
10
|
+
* The canonical data shape matches the Portal Service API response:
|
|
11
|
+
* GET /api/service/hierarchy
|
|
12
|
+
*/
|
|
13
|
+
interface OrgHierarchyNode {
|
|
14
|
+
id: number;
|
|
15
|
+
name: string;
|
|
16
|
+
code?: string;
|
|
17
|
+
}
|
|
18
|
+
interface OrgHierarchyEnterprise extends OrgHierarchyNode {
|
|
19
|
+
}
|
|
20
|
+
interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {
|
|
21
|
+
enterpriseId: number;
|
|
22
|
+
}
|
|
23
|
+
interface OrgHierarchySite extends OrgHierarchyNode {
|
|
24
|
+
enterpriseId: number;
|
|
25
|
+
businessUnitId: number;
|
|
26
|
+
location?: string | null;
|
|
27
|
+
}
|
|
28
|
+
interface OrgHierarchyArea extends OrgHierarchyNode {
|
|
29
|
+
siteId: number;
|
|
30
|
+
}
|
|
31
|
+
interface OrgHierarchyAsset extends OrgHierarchyNode {
|
|
32
|
+
siteId: number;
|
|
33
|
+
areaId: number;
|
|
34
|
+
assetType?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Canonical flat hierarchy payload.
|
|
38
|
+
* Every consumer (Portal tRPC, Service API REST) must return this shape.
|
|
39
|
+
*/
|
|
40
|
+
interface OrgHierarchyData {
|
|
41
|
+
enterprises: OrgHierarchyEnterprise[];
|
|
42
|
+
businessUnits: OrgHierarchyBusinessUnit[];
|
|
43
|
+
sites: OrgHierarchySite[];
|
|
44
|
+
areas: OrgHierarchyArea[];
|
|
45
|
+
assets: OrgHierarchyAsset[];
|
|
46
|
+
}
|
|
47
|
+
interface OrgHierarchySelection {
|
|
48
|
+
enterprise: OrgHierarchyNode | null;
|
|
49
|
+
businessUnit: OrgHierarchyNode | null;
|
|
50
|
+
site: OrgHierarchyNode | null;
|
|
51
|
+
area: OrgHierarchyNode | null;
|
|
52
|
+
asset: OrgHierarchyNode | null;
|
|
53
|
+
}
|
|
54
|
+
declare const ORG_HIERARCHY_EMPTY: OrgHierarchySelection;
|
|
55
|
+
/**
|
|
56
|
+
* An empty hierarchy payload. Use as the canonical "no data" value so every
|
|
57
|
+
* consumer returns the same shape (never null/undefined for a missing tree).
|
|
58
|
+
*/
|
|
59
|
+
declare const ORG_HIERARCHY_DATA_EMPTY: OrgHierarchyData;
|
|
60
|
+
interface OrgHierarchyContextValue {
|
|
61
|
+
/** Current selection at each level */
|
|
62
|
+
selection: OrgHierarchySelection;
|
|
63
|
+
/** Flat lists filtered by current selection */
|
|
64
|
+
enterprises: OrgHierarchyNode[];
|
|
65
|
+
businessUnits: OrgHierarchyNode[];
|
|
66
|
+
sites: OrgHierarchyNode[];
|
|
67
|
+
areas: OrgHierarchyNode[];
|
|
68
|
+
assets: OrgHierarchyNode[];
|
|
69
|
+
/** Setters — selecting a level clears all levels below it */
|
|
70
|
+
selectEnterprise: (node: OrgHierarchyNode | null) => void;
|
|
71
|
+
selectBusinessUnit: (node: OrgHierarchyNode | null) => void;
|
|
72
|
+
selectSite: (node: OrgHierarchyNode | null) => void;
|
|
73
|
+
selectArea: (node: OrgHierarchyNode | null) => void;
|
|
74
|
+
selectAsset: (node: OrgHierarchyNode | null) => void;
|
|
75
|
+
/** Whether the user can switch enterprises (platform_admin only) */
|
|
76
|
+
canSwitchEnterprise: boolean;
|
|
77
|
+
/** Loading state */
|
|
78
|
+
loading: boolean;
|
|
79
|
+
/** Breadcrumb trail as an array of { level, node } */
|
|
80
|
+
breadcrumbs: {
|
|
81
|
+
level: string;
|
|
82
|
+
node: OrgHierarchyNode;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
declare const ORG_HIERARCHY_LEVELS: readonly ["enterprise", "businessUnit", "site", "area", "asset"];
|
|
86
|
+
type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];
|
|
87
|
+
/** Human-readable labels for each level */
|
|
88
|
+
declare const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Enterprise Override — shared helper for platform_admin enterprise switching.
|
|
92
|
+
*
|
|
93
|
+
* When a platform_admin switches enterprise in the Portal UI, a cookie
|
|
94
|
+
* `oplytics-active-enterprise` is set with the selected enterprise ID.
|
|
95
|
+
* This cookie is shared across all *.oplytics.digital subdomains.
|
|
96
|
+
*
|
|
97
|
+
* Each subdomain's createContext should call applyEnterpriseOverride()
|
|
98
|
+
* to override the user's DB-stored enterpriseId with the cookie value.
|
|
99
|
+
*
|
|
100
|
+
* Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect
|
|
101
|
+
*/
|
|
102
|
+
/**
|
|
103
|
+
* Cookie name for the platform_admin active enterprise override.
|
|
104
|
+
* Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.
|
|
105
|
+
*/
|
|
106
|
+
declare const ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
|
|
107
|
+
/**
|
|
108
|
+
* Apply the enterprise override for platform_admin users.
|
|
109
|
+
*
|
|
110
|
+
* If the user is a platform_admin and has the active enterprise cookie set,
|
|
111
|
+
* returns a shallow copy of the user with the overridden enterpriseId.
|
|
112
|
+
* Otherwise returns the user unchanged.
|
|
113
|
+
*
|
|
114
|
+
* @param user - The authenticated user (or null)
|
|
115
|
+
* @param cookieHeader - The raw Cookie header string from the request
|
|
116
|
+
* @returns The user with potentially overridden enterpriseId
|
|
117
|
+
*/
|
|
118
|
+
declare function applyEnterpriseOverride<T extends {
|
|
119
|
+
role: string;
|
|
120
|
+
enterpriseId?: number | null;
|
|
121
|
+
}>(user: T | null, cookieHeader: string): T | null;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* scopeHierarchyToEnterprise — the single source of truth for enterprise isolation.
|
|
125
|
+
*
|
|
126
|
+
* The Portal Service API (`GET /api/service/hierarchy`) is called with a
|
|
127
|
+
* platform-level service key, so it returns every enterprise's hierarchy. This
|
|
128
|
+
* function filters that payload down to ONE enterprise before it reaches a
|
|
129
|
+
* subdomain client. It is pure and isomorphic (no I/O), so the same rule runs in
|
|
130
|
+
* the fetch service, in tests, and anywhere else scoping is needed.
|
|
131
|
+
*
|
|
132
|
+
* Policy (deliberate — do not "helpfully" relax it):
|
|
133
|
+
* - `null` data → empty hierarchy.
|
|
134
|
+
* - `null` enterpriseId → empty hierarchy. Enterprise selection is owned solely
|
|
135
|
+
* by the Portal switcher; a caller with no resolved enterprise (e.g. a
|
|
136
|
+
* platform admin who hasn't picked one in Portal yet) sees nothing until they
|
|
137
|
+
* select. Every non-admin always has exactly one enterprise, so they are
|
|
138
|
+
* unaffected. Admin/Portal tooling that genuinely needs the full tree must use
|
|
139
|
+
* the unscoped fetch, never this function.
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
declare function scopeHierarchyToEnterprise(data: OrgHierarchyData | null, enterpriseId: number | null): OrgHierarchyData;
|
|
143
|
+
|
|
144
|
+
export { ACTIVE_ENTERPRISE_COOKIE, ORG_HIERARCHY_DATA_EMPTY, ORG_HIERARCHY_EMPTY, ORG_HIERARCHY_LABELS, ORG_HIERARCHY_LEVELS, type OrgHierarchyArea, type OrgHierarchyAsset, type OrgHierarchyBusinessUnit, type OrgHierarchyContextValue, type OrgHierarchyData, type OrgHierarchyEnterprise, type OrgHierarchyLevel, type OrgHierarchyNode, type OrgHierarchySelection, type OrgHierarchySite, applyEnterpriseOverride, scopeHierarchyToEnterprise };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTIVE_ENTERPRISE_COOKIE,
|
|
3
|
+
ORG_HIERARCHY_DATA_EMPTY,
|
|
4
|
+
ORG_HIERARCHY_EMPTY,
|
|
5
|
+
ORG_HIERARCHY_LABELS,
|
|
6
|
+
ORG_HIERARCHY_LEVELS,
|
|
7
|
+
applyEnterpriseOverride,
|
|
8
|
+
scopeHierarchyToEnterprise
|
|
9
|
+
} from "../chunk-NXSFRNI7.js";
|
|
10
|
+
export {
|
|
11
|
+
ACTIVE_ENTERPRISE_COOKIE,
|
|
12
|
+
ORG_HIERARCHY_DATA_EMPTY,
|
|
13
|
+
ORG_HIERARCHY_EMPTY,
|
|
14
|
+
ORG_HIERARCHY_LABELS,
|
|
15
|
+
ORG_HIERARCHY_LEVELS,
|
|
16
|
+
applyEnterpriseOverride,
|
|
17
|
+
scopeHierarchyToEnterprise
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,123 +1,8 @@
|
|
|
1
|
+
import { OrgHierarchyData, OrgHierarchyContextValue } from './core.js';
|
|
2
|
+
export { ACTIVE_ENTERPRISE_COOKIE, ORG_HIERARCHY_DATA_EMPTY, ORG_HIERARCHY_EMPTY, ORG_HIERARCHY_LABELS, ORG_HIERARCHY_LEVELS, OrgHierarchyArea, OrgHierarchyAsset, OrgHierarchyBusinessUnit, OrgHierarchyEnterprise, OrgHierarchyLevel, OrgHierarchyNode, OrgHierarchySelection, OrgHierarchySite, applyEnterpriseOverride, scopeHierarchyToEnterprise } from './core.js';
|
|
1
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
4
|
import { ReactNode } from 'react';
|
|
3
5
|
|
|
4
|
-
/**
|
|
5
|
-
* @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities
|
|
6
|
-
*
|
|
7
|
-
* Hierarchy: Enterprise > Business Unit > Site > Area > Asset
|
|
8
|
-
*
|
|
9
|
-
* Single source of truth for org-hierarchy types used by:
|
|
10
|
-
* - Portal (direct DB via tRPC)
|
|
11
|
-
* - All subdomains (via Portal Service API)
|
|
12
|
-
*
|
|
13
|
-
* The canonical data shape matches the Portal Service API response:
|
|
14
|
-
* GET /api/service/hierarchy
|
|
15
|
-
*/
|
|
16
|
-
interface OrgHierarchyNode {
|
|
17
|
-
id: number;
|
|
18
|
-
name: string;
|
|
19
|
-
code?: string;
|
|
20
|
-
}
|
|
21
|
-
interface OrgHierarchyEnterprise extends OrgHierarchyNode {
|
|
22
|
-
}
|
|
23
|
-
interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {
|
|
24
|
-
enterpriseId: number;
|
|
25
|
-
}
|
|
26
|
-
interface OrgHierarchySite extends OrgHierarchyNode {
|
|
27
|
-
enterpriseId: number;
|
|
28
|
-
businessUnitId: number;
|
|
29
|
-
location?: string | null;
|
|
30
|
-
}
|
|
31
|
-
interface OrgHierarchyArea extends OrgHierarchyNode {
|
|
32
|
-
siteId: number;
|
|
33
|
-
}
|
|
34
|
-
interface OrgHierarchyAsset extends OrgHierarchyNode {
|
|
35
|
-
siteId: number;
|
|
36
|
-
areaId: number;
|
|
37
|
-
assetType?: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Canonical flat hierarchy payload.
|
|
41
|
-
* Every consumer (Portal tRPC, Service API REST) must return this shape.
|
|
42
|
-
*/
|
|
43
|
-
interface OrgHierarchyData {
|
|
44
|
-
enterprises: OrgHierarchyEnterprise[];
|
|
45
|
-
businessUnits: OrgHierarchyBusinessUnit[];
|
|
46
|
-
sites: OrgHierarchySite[];
|
|
47
|
-
areas: OrgHierarchyArea[];
|
|
48
|
-
assets: OrgHierarchyAsset[];
|
|
49
|
-
}
|
|
50
|
-
interface OrgHierarchySelection {
|
|
51
|
-
enterprise: OrgHierarchyNode | null;
|
|
52
|
-
businessUnit: OrgHierarchyNode | null;
|
|
53
|
-
site: OrgHierarchyNode | null;
|
|
54
|
-
area: OrgHierarchyNode | null;
|
|
55
|
-
asset: OrgHierarchyNode | null;
|
|
56
|
-
}
|
|
57
|
-
declare const ORG_HIERARCHY_EMPTY: OrgHierarchySelection;
|
|
58
|
-
interface OrgHierarchyContextValue {
|
|
59
|
-
/** Current selection at each level */
|
|
60
|
-
selection: OrgHierarchySelection;
|
|
61
|
-
/** Flat lists filtered by current selection */
|
|
62
|
-
enterprises: OrgHierarchyNode[];
|
|
63
|
-
businessUnits: OrgHierarchyNode[];
|
|
64
|
-
sites: OrgHierarchyNode[];
|
|
65
|
-
areas: OrgHierarchyNode[];
|
|
66
|
-
assets: OrgHierarchyNode[];
|
|
67
|
-
/** Setters — selecting a level clears all levels below it */
|
|
68
|
-
selectEnterprise: (node: OrgHierarchyNode | null) => void;
|
|
69
|
-
selectBusinessUnit: (node: OrgHierarchyNode | null) => void;
|
|
70
|
-
selectSite: (node: OrgHierarchyNode | null) => void;
|
|
71
|
-
selectArea: (node: OrgHierarchyNode | null) => void;
|
|
72
|
-
selectAsset: (node: OrgHierarchyNode | null) => void;
|
|
73
|
-
/** Whether the user can switch enterprises (platform_admin only) */
|
|
74
|
-
canSwitchEnterprise: boolean;
|
|
75
|
-
/** Loading state */
|
|
76
|
-
loading: boolean;
|
|
77
|
-
/** Breadcrumb trail as an array of { level, node } */
|
|
78
|
-
breadcrumbs: {
|
|
79
|
-
level: string;
|
|
80
|
-
node: OrgHierarchyNode;
|
|
81
|
-
}[];
|
|
82
|
-
}
|
|
83
|
-
declare const ORG_HIERARCHY_LEVELS: readonly ["enterprise", "businessUnit", "site", "area", "asset"];
|
|
84
|
-
type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];
|
|
85
|
-
/** Human-readable labels for each level */
|
|
86
|
-
declare const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string>;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Enterprise Override — shared helper for platform_admin enterprise switching.
|
|
90
|
-
*
|
|
91
|
-
* When a platform_admin switches enterprise in the Portal UI, a cookie
|
|
92
|
-
* `oplytics-active-enterprise` is set with the selected enterprise ID.
|
|
93
|
-
* This cookie is shared across all *.oplytics.digital subdomains.
|
|
94
|
-
*
|
|
95
|
-
* Each subdomain's createContext should call applyEnterpriseOverride()
|
|
96
|
-
* to override the user's DB-stored enterpriseId with the cookie value.
|
|
97
|
-
*
|
|
98
|
-
* Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect
|
|
99
|
-
*/
|
|
100
|
-
/**
|
|
101
|
-
* Cookie name for the platform_admin active enterprise override.
|
|
102
|
-
* Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.
|
|
103
|
-
*/
|
|
104
|
-
declare const ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
|
|
105
|
-
/**
|
|
106
|
-
* Apply the enterprise override for platform_admin users.
|
|
107
|
-
*
|
|
108
|
-
* If the user is a platform_admin and has the active enterprise cookie set,
|
|
109
|
-
* returns a shallow copy of the user with the overridden enterpriseId.
|
|
110
|
-
* Otherwise returns the user unchanged.
|
|
111
|
-
*
|
|
112
|
-
* @param user - The authenticated user (or null)
|
|
113
|
-
* @param cookieHeader - The raw Cookie header string from the request
|
|
114
|
-
* @returns The user with potentially overridden enterpriseId
|
|
115
|
-
*/
|
|
116
|
-
declare function applyEnterpriseOverride<T extends {
|
|
117
|
-
role: string;
|
|
118
|
-
enterpriseId?: number | null;
|
|
119
|
-
}>(user: T | null, cookieHeader: string): T | null;
|
|
120
|
-
|
|
121
6
|
interface CreateOrgHierarchyContextOptions {
|
|
122
7
|
/**
|
|
123
8
|
* Unique sessionStorage key for this subdomain's selection persistence.
|
|
@@ -176,4 +61,4 @@ interface HierarchyNavigatorProps {
|
|
|
176
61
|
}
|
|
177
62
|
declare function HierarchyNavigator({ hierarchy: ctx, compact, maxDepth, className, renderDropdown, }: HierarchyNavigatorProps): react_jsx_runtime.JSX.Element;
|
|
178
63
|
|
|
179
|
-
export {
|
|
64
|
+
export { type CreateOrgHierarchyContextOptions, type DropdownRenderProps, HierarchyNavigator, type HierarchyNavigatorProps, OrgHierarchyContextValue, OrgHierarchyData, createOrgHierarchyContext };
|
package/dist/hierarchy/index.js
CHANGED
|
@@ -1,49 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTIVE_ENTERPRISE_COOKIE,
|
|
3
|
+
ORG_HIERARCHY_DATA_EMPTY,
|
|
4
|
+
ORG_HIERARCHY_EMPTY,
|
|
5
|
+
ORG_HIERARCHY_LABELS,
|
|
6
|
+
ORG_HIERARCHY_LEVELS,
|
|
7
|
+
applyEnterpriseOverride,
|
|
8
|
+
scopeHierarchyToEnterprise
|
|
9
|
+
} from "../chunk-NXSFRNI7.js";
|
|
1
10
|
import {
|
|
2
11
|
cn
|
|
3
12
|
} from "../chunk-JT3XLKKD.js";
|
|
4
13
|
|
|
5
|
-
// src/hierarchy/orgHierarchy.ts
|
|
6
|
-
var ORG_HIERARCHY_EMPTY = {
|
|
7
|
-
enterprise: null,
|
|
8
|
-
businessUnit: null,
|
|
9
|
-
site: null,
|
|
10
|
-
area: null,
|
|
11
|
-
asset: null
|
|
12
|
-
};
|
|
13
|
-
var ORG_HIERARCHY_LEVELS = [
|
|
14
|
-
"enterprise",
|
|
15
|
-
"businessUnit",
|
|
16
|
-
"site",
|
|
17
|
-
"area",
|
|
18
|
-
"asset"
|
|
19
|
-
];
|
|
20
|
-
var ORG_HIERARCHY_LABELS = {
|
|
21
|
-
enterprise: "Enterprise",
|
|
22
|
-
businessUnit: "Business Unit",
|
|
23
|
-
site: "Site",
|
|
24
|
-
area: "Area",
|
|
25
|
-
asset: "Asset"
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// src/hierarchy/enterpriseOverride.ts
|
|
29
|
-
var ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
|
|
30
|
-
function parseCookieValue(cookieHeader, name) {
|
|
31
|
-
const match = cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
|
|
32
|
-
if (!match) return null;
|
|
33
|
-
return decodeURIComponent(match.slice(name.length + 1));
|
|
34
|
-
}
|
|
35
|
-
function applyEnterpriseOverride(user, cookieHeader) {
|
|
36
|
-
if (!user || user.role !== "platform_admin") return user;
|
|
37
|
-
const activeEnterprise = parseCookieValue(
|
|
38
|
-
cookieHeader,
|
|
39
|
-
ACTIVE_ENTERPRISE_COOKIE
|
|
40
|
-
);
|
|
41
|
-
if (!activeEnterprise) return user;
|
|
42
|
-
const parsedId = parseInt(activeEnterprise, 10);
|
|
43
|
-
if (isNaN(parsedId) || parsedId <= 0) return user;
|
|
44
|
-
return { ...user, enterpriseId: parsedId };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
14
|
// src/hierarchy/OrgHierarchyContext.tsx
|
|
48
15
|
import {
|
|
49
16
|
createContext,
|
|
@@ -466,10 +433,12 @@ function HierarchyNavigator({
|
|
|
466
433
|
export {
|
|
467
434
|
ACTIVE_ENTERPRISE_COOKIE,
|
|
468
435
|
HierarchyNavigator,
|
|
436
|
+
ORG_HIERARCHY_DATA_EMPTY,
|
|
469
437
|
ORG_HIERARCHY_EMPTY,
|
|
470
438
|
ORG_HIERARCHY_LABELS,
|
|
471
439
|
ORG_HIERARCHY_LEVELS,
|
|
472
440
|
applyEnterpriseOverride,
|
|
473
|
-
createOrgHierarchyContext
|
|
441
|
+
createOrgHierarchyContext,
|
|
442
|
+
scopeHierarchyToEnterprise
|
|
474
443
|
};
|
|
475
444
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hierarchy/orgHierarchy.ts","../../src/hierarchy/enterpriseOverride.ts","../../src/hierarchy/OrgHierarchyContext.tsx","../../src/hierarchy/HierarchyNavigator.tsx"],"sourcesContent":["/**\n * @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities\n *\n * Hierarchy: Enterprise > Business Unit > Site > Area > Asset\n *\n * Single source of truth for org-hierarchy types used by:\n * - Portal (direct DB via tRPC)\n * - All subdomains (via Portal Service API)\n *\n * The canonical data shape matches the Portal Service API response:\n * GET /api/service/hierarchy\n */\n\n/* ── Base Node ── */\n\nexport interface OrgHierarchyNode {\n id: number;\n name: string;\n code?: string;\n}\n\n/* ── Flat hierarchy data — matches Portal Service API /api/service/hierarchy ── */\n\nexport interface OrgHierarchyEnterprise extends OrgHierarchyNode {}\n\nexport interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {\n enterpriseId: number;\n}\n\nexport interface OrgHierarchySite extends OrgHierarchyNode {\n enterpriseId: number;\n businessUnitId: number;\n location?: string | null;\n}\n\nexport interface OrgHierarchyArea extends OrgHierarchyNode {\n siteId: number;\n}\n\nexport interface OrgHierarchyAsset extends OrgHierarchyNode {\n siteId: number;\n areaId: number;\n assetType?: string;\n}\n\n/**\n * Canonical flat hierarchy payload.\n * Every consumer (Portal tRPC, Service API REST) must return this shape.\n */\nexport interface OrgHierarchyData {\n enterprises: OrgHierarchyEnterprise[];\n businessUnits: OrgHierarchyBusinessUnit[];\n sites: OrgHierarchySite[];\n areas: OrgHierarchyArea[];\n assets: OrgHierarchyAsset[];\n}\n\n/* ── Selection state ── */\n\nexport interface OrgHierarchySelection {\n enterprise: OrgHierarchyNode | null;\n businessUnit: OrgHierarchyNode | null;\n site: OrgHierarchyNode | null;\n area: OrgHierarchyNode | null;\n asset: OrgHierarchyNode | null;\n}\n\nexport const ORG_HIERARCHY_EMPTY: OrgHierarchySelection = {\n enterprise: null,\n businessUnit: null,\n site: null,\n area: null,\n asset: null,\n};\n\n/* ── Context value ── */\n\nexport interface OrgHierarchyContextValue {\n /** Current selection at each level */\n selection: OrgHierarchySelection;\n\n /** Flat lists filtered by current selection */\n enterprises: OrgHierarchyNode[];\n businessUnits: OrgHierarchyNode[];\n sites: OrgHierarchyNode[];\n areas: OrgHierarchyNode[];\n assets: OrgHierarchyNode[];\n\n /** Setters — selecting a level clears all levels below it */\n selectEnterprise: (node: OrgHierarchyNode | null) => void;\n selectBusinessUnit: (node: OrgHierarchyNode | null) => void;\n selectSite: (node: OrgHierarchyNode | null) => void;\n selectArea: (node: OrgHierarchyNode | null) => void;\n selectAsset: (node: OrgHierarchyNode | null) => void;\n\n /** Whether the user can switch enterprises (platform_admin only) */\n canSwitchEnterprise: boolean;\n\n /** Loading state */\n loading: boolean;\n\n /** Breadcrumb trail as an array of { level, node } */\n breadcrumbs: { level: string; node: OrgHierarchyNode }[];\n}\n\n/* ── Hierarchy levels (for iteration) ── */\n\nexport const ORG_HIERARCHY_LEVELS = [\n \"enterprise\",\n \"businessUnit\",\n \"site\",\n \"area\",\n \"asset\",\n] as const;\n\nexport type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];\n\n/** Human-readable labels for each level */\nexport const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string> = {\n enterprise: \"Enterprise\",\n businessUnit: \"Business Unit\",\n site: \"Site\",\n area: \"Area\",\n asset: \"Asset\",\n};\n","/**\n * Enterprise Override — shared helper for platform_admin enterprise switching.\n *\n * When a platform_admin switches enterprise in the Portal UI, a cookie\n * `oplytics-active-enterprise` is set with the selected enterprise ID.\n * This cookie is shared across all *.oplytics.digital subdomains.\n *\n * Each subdomain's createContext should call applyEnterpriseOverride()\n * to override the user's DB-stored enterpriseId with the cookie value.\n *\n * Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect\n */\n\n/**\n * Cookie name for the platform_admin active enterprise override.\n * Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.\n */\nexport const ACTIVE_ENTERPRISE_COOKIE = \"oplytics-active-enterprise\";\n\n/**\n * Parse a single cookie value from a cookie header string.\n */\nfunction parseCookieValue(cookieHeader: string, name: string): string | null {\n const match = cookieHeader\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(`${name}=`));\n if (!match) return null;\n return decodeURIComponent(match.slice(name.length + 1));\n}\n\n/**\n * Apply the enterprise override for platform_admin users.\n *\n * If the user is a platform_admin and has the active enterprise cookie set,\n * returns a shallow copy of the user with the overridden enterpriseId.\n * Otherwise returns the user unchanged.\n *\n * @param user - The authenticated user (or null)\n * @param cookieHeader - The raw Cookie header string from the request\n * @returns The user with potentially overridden enterpriseId\n */\nexport function applyEnterpriseOverride<\n T extends { role: string; enterpriseId?: number | null },\n>(user: T | null, cookieHeader: string): T | null {\n if (!user || user.role !== \"platform_admin\") return user;\n\n const activeEnterprise = parseCookieValue(\n cookieHeader,\n ACTIVE_ENTERPRISE_COOKIE,\n );\n if (!activeEnterprise) return user;\n\n const parsedId = parseInt(activeEnterprise, 10);\n if (isNaN(parsedId) || parsedId <= 0) return user;\n\n // Return a shallow copy with the overridden enterpriseId\n return { ...user, enterpriseId: parsedId };\n}\n","/**\n * OrgHierarchyContext — shared React context factory for the Organisational Hierarchy.\n *\n * Usage:\n * const { OrgHierarchyProvider, useOrgHierarchy } = createOrgHierarchyContext({\n * storageKey: \"oplytics-portal-org-hierarchy\",\n * useHierarchyData: () => {\n * const { data, isLoading } = trpc.hierarchy.flat.useQuery(...);\n * return { data: data ?? null, isLoading };\n * },\n * });\n *\n * All selection state, cascade setters, breadcrumbs, and session persistence\n * are handled here — no per-subdomain duplication.\n */\nimport {\n createContext,\n useContext,\n useCallback,\n useMemo,\n useState,\n useEffect,\n type ReactNode,\n} from \"react\";\nimport type {\n OrgHierarchyData,\n OrgHierarchyNode,\n OrgHierarchySelection,\n OrgHierarchyContextValue,\n} from \"./orgHierarchy\";\nimport { ORG_HIERARCHY_EMPTY } from \"./orgHierarchy\";\n\n/* ── Session persistence helpers ── */\n\nfunction loadPersistedSelection(key: string): OrgHierarchySelection | null {\n try {\n const raw = sessionStorage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as OrgHierarchySelection;\n } catch {\n return null;\n }\n}\n\nfunction persistSelection(key: string, sel: OrgHierarchySelection) {\n try {\n sessionStorage.setItem(key, JSON.stringify(sel));\n } catch {\n // ignore storage errors\n }\n}\n\n/* ── Factory options ── */\n\nexport interface CreateOrgHierarchyContextOptions {\n /**\n * Unique sessionStorage key for this subdomain's selection persistence.\n * e.g. \"oplytics-portal-org-hierarchy\", \"oplytics-sqdcp-org-hierarchy\"\n */\n storageKey: string;\n\n /**\n * React hook that fetches the flat hierarchy data.\n * Must return { data, isLoading } where data is OrgHierarchyData | null.\n */\n useHierarchyData: () => {\n data: OrgHierarchyData | null;\n isLoading: boolean;\n };\n}\n\n/* ── Factory ── */\n\nexport function createOrgHierarchyContext(options: CreateOrgHierarchyContextOptions) {\n const { storageKey, useHierarchyData } = options;\n\n const OrgHierarchyCtx = createContext<OrgHierarchyContextValue>({\n selection: ORG_HIERARCHY_EMPTY,\n enterprises: [],\n businessUnits: [],\n sites: [],\n areas: [],\n assets: [],\n selectEnterprise: () => {},\n selectBusinessUnit: () => {},\n selectSite: () => {},\n selectArea: () => {},\n selectAsset: () => {},\n canSwitchEnterprise: false,\n loading: true,\n breadcrumbs: [],\n });\n\n function OrgHierarchyProvider({\n children,\n canSwitchEnterprise = false,\n onEnterpriseSwitched,\n }: {\n children: ReactNode;\n canSwitchEnterprise?: boolean;\n /**\n * Callback fired when a platform_admin switches enterprise.\n * Used to persist the switch to the backend (e.g. via auth.switchEnterprise mutation).\n * Only called when canSwitchEnterprise is true and the user explicitly changes enterprise.\n */\n onEnterpriseSwitched?: (enterpriseId: number | null) => void;\n }) {\n const [selection, setSelection] = useState<OrgHierarchySelection>(\n () => loadPersistedSelection(storageKey) || ORG_HIERARCHY_EMPTY\n );\n\n // Persist selection changes\n useEffect(() => {\n persistSelection(storageKey, selection);\n }, [selection]);\n\n // Fetch hierarchy data via the provided hook\n const { data: hierarchy, isLoading } = useHierarchyData();\n\n // ── Derive filtered lists from flat data ──\n\n const enterprises = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.enterprises) return [];\n return hierarchy.enterprises.map((e) => ({\n id: e.id,\n name: e.name,\n code: e.code,\n }));\n }, [hierarchy]);\n\n const businessUnits = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.businessUnits || !selection.enterprise) return [];\n return hierarchy.businessUnits\n .filter((bu) => bu.enterpriseId === selection.enterprise!.id)\n .map((bu) => ({ id: bu.id, name: bu.name, code: bu.code }));\n }, [hierarchy, selection.enterprise]);\n\n const sites = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.sites || !selection.enterprise) return [];\n let filtered = hierarchy.sites.filter(\n (s) => s.enterpriseId === selection.enterprise!.id\n );\n if (selection.businessUnit) {\n filtered = filtered.filter(\n (s) => s.businessUnitId === selection.businessUnit!.id\n );\n }\n return filtered.map((s) => ({ id: s.id, name: s.name, code: s.code }));\n }, [hierarchy, selection.enterprise, selection.businessUnit]);\n\n const areas = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.areas || !selection.site) return [];\n return hierarchy.areas\n .filter((a) => a.siteId === selection.site!.id)\n .map((a) => ({ id: a.id, name: a.name, code: a.code }));\n }, [hierarchy, selection.site]);\n\n const assets = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.assets || !selection.area) return [];\n return hierarchy.assets\n .filter((a) => a.areaId === selection.area!.id)\n .map((a) => ({ id: a.id, name: a.name, code: a.code }));\n }, [hierarchy, selection.area]);\n\n // Auto-select first enterprise for non-platform_admin users\n useEffect(() => {\n if (isLoading || enterprises.length === 0) return;\n if (!selection.enterprise) {\n if (!canSwitchEnterprise || enterprises.length === 1) {\n setSelection((prev) => ({ ...prev, enterprise: enterprises[0] }));\n }\n }\n }, [isLoading, enterprises, selection.enterprise, canSwitchEnterprise]);\n\n // ── Cascade setters ──\n\n const selectEnterprise = useCallback((node: OrgHierarchyNode | null) => {\n setSelection({\n enterprise: node,\n businessUnit: null,\n site: null,\n area: null,\n asset: null,\n });\n // Notify backend of enterprise switch (platform_admin only)\n if (canSwitchEnterprise && onEnterpriseSwitched) {\n onEnterpriseSwitched(node?.id ?? null);\n }\n }, [canSwitchEnterprise, onEnterpriseSwitched]);\n\n const selectBusinessUnit = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n businessUnit: node,\n site: null,\n area: null,\n asset: null,\n }));\n }, []);\n\n const selectSite = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n site: node,\n area: null,\n asset: null,\n }));\n }, []);\n\n const selectArea = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n area: node,\n asset: null,\n }));\n }, []);\n\n const selectAsset = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n asset: node,\n }));\n }, []);\n\n // ── Breadcrumbs ──\n\n const breadcrumbs = useMemo(() => {\n const crumbs: { level: string; node: OrgHierarchyNode }[] = [];\n if (selection.enterprise)\n crumbs.push({ level: \"Enterprise\", node: selection.enterprise });\n if (selection.businessUnit)\n crumbs.push({ level: \"Business Unit\", node: selection.businessUnit });\n if (selection.site)\n crumbs.push({ level: \"Site\", node: selection.site });\n if (selection.area)\n crumbs.push({ level: \"Area\", node: selection.area });\n if (selection.asset)\n crumbs.push({ level: \"Asset\", node: selection.asset });\n return crumbs;\n }, [selection]);\n\n // ── Context value ──\n\n const value = useMemo<OrgHierarchyContextValue>(\n () => ({\n selection,\n enterprises,\n businessUnits,\n sites,\n areas,\n assets,\n selectEnterprise,\n selectBusinessUnit,\n selectSite,\n selectArea,\n selectAsset,\n canSwitchEnterprise,\n loading: isLoading,\n breadcrumbs,\n }),\n [\n selection,\n enterprises,\n businessUnits,\n sites,\n areas,\n assets,\n selectEnterprise,\n selectBusinessUnit,\n selectSite,\n selectArea,\n selectAsset,\n canSwitchEnterprise,\n isLoading,\n breadcrumbs,\n ]\n );\n\n return (\n <OrgHierarchyCtx.Provider value={value}>\n {children}\n </OrgHierarchyCtx.Provider>\n );\n }\n\n function useOrgHierarchy(): OrgHierarchyContextValue {\n return useContext(OrgHierarchyCtx);\n }\n\n return { OrgHierarchyProvider, useOrgHierarchy };\n}\n\n/* ── Re-export types for convenience ── */\nexport type {\n OrgHierarchyNode,\n OrgHierarchyData,\n OrgHierarchySelection,\n OrgHierarchyContextValue,\n} from \"./orgHierarchy\";\n","/**\n * HierarchyNavigator — breadcrumb-style hierarchy switcher.\n *\n * Displays: Enterprise / BU / Site / Area / Asset\n * Each level is a clickable dropdown to switch context.\n * Platform admins can switch enterprises; others see their scoped enterprise.\n *\n * NOTE: This component requires the consuming app to provide:\n * - A hierarchy context via useHierarchy prop\n * - shadcn/ui DropdownMenu components via the renderDropdown prop\n *\n * For apps using the createOrgHierarchyContext factory, pass the useOrgHierarchy hook.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent, #E2E8F0 text, #8890A0 muted\n */\nimport { cn } from \"../utils/cn\";\nimport type { OrgHierarchyNode, OrgHierarchyContextValue } from \"./orgHierarchy\";\nimport { ChevronRight, ChevronDown, Building2, Network, MapPin, LayoutGrid, Box } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\n/* ── Level config ── */\n\ninterface LevelConfig {\n key: string;\n label: string;\n icon: React.ElementType;\n getNodes: () => OrgHierarchyNode[];\n getSelected: () => OrgHierarchyNode | null;\n onSelect: (node: OrgHierarchyNode | null) => void;\n canSwitch?: boolean;\n}\n\n/* ── Dropdown render prop types ── */\n\nexport interface DropdownRenderProps {\n trigger: ReactNode;\n items: Array<{\n key: number;\n label: string;\n code?: string;\n icon: React.ElementType;\n isActive: boolean;\n onClick: () => void;\n }>;\n align?: \"start\" | \"end\";\n}\n\n/* ── Single breadcrumb level ── */\n\nfunction BreadcrumbLevel({\n config,\n isLast,\n compact,\n renderDropdown,\n}: {\n config: LevelConfig;\n isLast: boolean;\n compact?: boolean;\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}) {\n const nodes = config.getNodes();\n const selected = config.getSelected();\n const Icon = config.icon;\n const canSwitch = config.canSwitch !== false;\n const hasMultiple = nodes.length > 1;\n\n if (!selected) return null;\n\n // If only one option or can't switch, show as static text\n if (!hasMultiple || !canSwitch) {\n return (\n <div className=\"flex items-center gap-1\">\n <div\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#E2E8F0]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0 text-[#8890A0]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate max-w-[120px]\">{selected.name}</span>\n </div>\n {!isLast && (\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n )}\n </div>\n );\n }\n\n // Multiple options — use renderDropdown if provided, otherwise basic button\n const trigger = (\n <button\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#E2E8F0] hover:bg-[#1E2738] hover:text-white\",\n \"transition-colors cursor-pointer outline-none\",\n \"focus-visible:ring-1 focus-visible:ring-[#8C34E9]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0 text-[#8890A0]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate max-w-[120px]\">{selected.name}</span>\n <ChevronDown className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-2.5 w-2.5\" : \"h-3 w-3\")} />\n </button>\n );\n\n const items = nodes.map((node) => ({\n key: node.id,\n label: node.name,\n code: node.code,\n icon: Icon,\n isActive: selected.id === node.id,\n onClick: () => config.onSelect(node),\n }));\n\n return (\n <div className=\"flex items-center gap-1\">\n {renderDropdown ? (\n renderDropdown({ trigger, items, align: \"start\" })\n ) : (\n // Fallback: simple select-style rendering\n <div className=\"relative group\">\n {trigger}\n </div>\n )}\n {!isLast && (\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n )}\n </div>\n );\n}\n\n/* ── Drill-down prompt ── */\n\nfunction DrillDownPrompt({\n label,\n icon: Icon,\n nodes,\n onSelect,\n compact,\n renderDropdown,\n}: {\n label: string;\n icon: React.ElementType;\n nodes: OrgHierarchyNode[];\n onSelect: (node: OrgHierarchyNode) => void;\n compact?: boolean;\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}) {\n if (nodes.length === 0) return null;\n\n // Auto-select if only one option\n if (nodes.length === 1) {\n return (\n <div className=\"flex items-center gap-1\">\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <button\n onClick={() => onSelect(nodes[0])}\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#596475] hover:bg-[#1E2738] hover:text-[#8890A0]\",\n \"transition-colors cursor-pointer\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate\">{nodes[0].name}</span>\n </button>\n </div>\n );\n }\n\n const trigger = (\n <button\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#596475] hover:bg-[#1E2738] hover:text-[#8890A0]\",\n \"transition-colors cursor-pointer outline-none\",\n \"border border-dashed border-[#2A2A3E] hover:border-[#596475]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate\">Select {label}</span>\n <ChevronDown className={cn(\"shrink-0\", compact ? \"h-2.5 w-2.5\" : \"h-3 w-3\")} />\n </button>\n );\n\n const items = nodes.map((node) => ({\n key: node.id,\n label: node.name,\n code: node.code,\n icon: Icon,\n isActive: false,\n onClick: () => onSelect(node),\n }));\n\n return (\n <div className=\"flex items-center gap-1\">\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n {renderDropdown ? (\n renderDropdown({ trigger, items, align: \"start\" })\n ) : (\n <div className=\"relative group\">\n {trigger}\n </div>\n )}\n </div>\n );\n}\n\n/* ── Main component ── */\n\nexport interface HierarchyNavigatorProps {\n /** The hierarchy context value — pass from useOrgHierarchy() */\n hierarchy: OrgHierarchyContextValue;\n /** Show in compact mode (smaller text, tighter spacing) */\n compact?: boolean;\n /** Maximum depth to show (default: all 5 levels) */\n maxDepth?: number;\n /** CSS class for the container */\n className?: string;\n /**\n * Render prop for dropdown menus. If not provided, dropdowns won't open.\n * Consuming apps should pass a function that renders their shadcn/ui DropdownMenu.\n */\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}\n\nexport function HierarchyNavigator({\n hierarchy: ctx,\n compact = false,\n maxDepth = 5,\n className,\n renderDropdown,\n}: HierarchyNavigatorProps) {\n if (ctx.loading) {\n return (\n <div className={cn(\"flex items-center gap-2 animate-pulse\", className)}>\n <div className=\"h-6 w-24 rounded bg-[#1E2738]\" />\n <ChevronRight className=\"h-3.5 w-3.5 text-[#596475]\" />\n <div className=\"h-6 w-20 rounded bg-[#1E2738]\" />\n </div>\n );\n }\n\n // Build the level configs\n const levels: LevelConfig[] = [\n {\n key: \"enterprise\",\n label: \"Enterprise\",\n icon: Building2,\n getNodes: () => ctx.enterprises,\n getSelected: () => ctx.selection.enterprise,\n onSelect: ctx.selectEnterprise,\n canSwitch: ctx.canSwitchEnterprise,\n },\n {\n key: \"businessUnit\",\n label: \"Business Unit\",\n icon: Network,\n getNodes: () => ctx.businessUnits,\n getSelected: () => ctx.selection.businessUnit,\n onSelect: ctx.selectBusinessUnit,\n },\n {\n key: \"site\",\n label: \"Site\",\n icon: MapPin,\n getNodes: () => ctx.sites,\n getSelected: () => ctx.selection.site,\n onSelect: ctx.selectSite,\n },\n {\n key: \"area\",\n label: \"Area\",\n icon: LayoutGrid,\n getNodes: () => ctx.areas,\n getSelected: () => ctx.selection.area,\n onSelect: ctx.selectArea,\n },\n {\n key: \"asset\",\n label: \"Asset\",\n icon: Box,\n getNodes: () => ctx.assets,\n getSelected: () => ctx.selection.asset,\n onSelect: ctx.selectAsset,\n },\n ].slice(0, maxDepth);\n\n // Determine which levels are selected and which is the next drill-down\n const selectedLevels = levels.filter((l) => l.getSelected() !== null);\n const nextLevel = levels.find((l) => l.getSelected() === null && l.getNodes().length > 0);\n\n return (\n <nav\n className={cn(\"flex items-center flex-wrap gap-0.5\", className)}\n aria-label=\"Hierarchy navigation\"\n >\n {selectedLevels.map((level, idx) => (\n <BreadcrumbLevel\n key={level.key}\n config={level}\n isLast={idx === selectedLevels.length - 1 && !nextLevel}\n compact={compact}\n renderDropdown={renderDropdown}\n />\n ))}\n {nextLevel && (\n <DrillDownPrompt\n label={nextLevel.label}\n icon={nextLevel.icon}\n nodes={nextLevel.getNodes()}\n onSelect={nextLevel.onSelect}\n compact={compact}\n renderDropdown={renderDropdown}\n />\n )}\n {selectedLevels.length === 0 && !nextLevel && (\n <span className=\"text-sm text-[#596475] italic\">No hierarchy data available</span>\n )}\n </nav>\n );\n}\n"],"mappings":"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","names":["jsx"]}
|
|
1
|
+
{"version":3,"sources":["../../src/hierarchy/OrgHierarchyContext.tsx","../../src/hierarchy/HierarchyNavigator.tsx"],"sourcesContent":["/**\n * OrgHierarchyContext — shared React context factory for the Organisational Hierarchy.\n *\n * Usage:\n * const { OrgHierarchyProvider, useOrgHierarchy } = createOrgHierarchyContext({\n * storageKey: \"oplytics-portal-org-hierarchy\",\n * useHierarchyData: () => {\n * const { data, isLoading } = trpc.hierarchy.flat.useQuery(...);\n * return { data: data ?? null, isLoading };\n * },\n * });\n *\n * All selection state, cascade setters, breadcrumbs, and session persistence\n * are handled here — no per-subdomain duplication.\n */\nimport {\n createContext,\n useContext,\n useCallback,\n useMemo,\n useState,\n useEffect,\n type ReactNode,\n} from \"react\";\nimport type {\n OrgHierarchyData,\n OrgHierarchyNode,\n OrgHierarchySelection,\n OrgHierarchyContextValue,\n} from \"./orgHierarchy\";\nimport { ORG_HIERARCHY_EMPTY } from \"./orgHierarchy\";\n\n/* ── Session persistence helpers ── */\n\nfunction loadPersistedSelection(key: string): OrgHierarchySelection | null {\n try {\n const raw = sessionStorage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as OrgHierarchySelection;\n } catch {\n return null;\n }\n}\n\nfunction persistSelection(key: string, sel: OrgHierarchySelection) {\n try {\n sessionStorage.setItem(key, JSON.stringify(sel));\n } catch {\n // ignore storage errors\n }\n}\n\n/* ── Factory options ── */\n\nexport interface CreateOrgHierarchyContextOptions {\n /**\n * Unique sessionStorage key for this subdomain's selection persistence.\n * e.g. \"oplytics-portal-org-hierarchy\", \"oplytics-sqdcp-org-hierarchy\"\n */\n storageKey: string;\n\n /**\n * React hook that fetches the flat hierarchy data.\n * Must return { data, isLoading } where data is OrgHierarchyData | null.\n */\n useHierarchyData: () => {\n data: OrgHierarchyData | null;\n isLoading: boolean;\n };\n}\n\n/* ── Factory ── */\n\nexport function createOrgHierarchyContext(options: CreateOrgHierarchyContextOptions) {\n const { storageKey, useHierarchyData } = options;\n\n const OrgHierarchyCtx = createContext<OrgHierarchyContextValue>({\n selection: ORG_HIERARCHY_EMPTY,\n enterprises: [],\n businessUnits: [],\n sites: [],\n areas: [],\n assets: [],\n selectEnterprise: () => {},\n selectBusinessUnit: () => {},\n selectSite: () => {},\n selectArea: () => {},\n selectAsset: () => {},\n canSwitchEnterprise: false,\n loading: true,\n breadcrumbs: [],\n });\n\n function OrgHierarchyProvider({\n children,\n canSwitchEnterprise = false,\n onEnterpriseSwitched,\n }: {\n children: ReactNode;\n canSwitchEnterprise?: boolean;\n /**\n * Callback fired when a platform_admin switches enterprise.\n * Used to persist the switch to the backend (e.g. via auth.switchEnterprise mutation).\n * Only called when canSwitchEnterprise is true and the user explicitly changes enterprise.\n */\n onEnterpriseSwitched?: (enterpriseId: number | null) => void;\n }) {\n const [selection, setSelection] = useState<OrgHierarchySelection>(\n () => loadPersistedSelection(storageKey) || ORG_HIERARCHY_EMPTY\n );\n\n // Persist selection changes\n useEffect(() => {\n persistSelection(storageKey, selection);\n }, [selection]);\n\n // Fetch hierarchy data via the provided hook\n const { data: hierarchy, isLoading } = useHierarchyData();\n\n // ── Derive filtered lists from flat data ──\n\n const enterprises = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.enterprises) return [];\n return hierarchy.enterprises.map((e) => ({\n id: e.id,\n name: e.name,\n code: e.code,\n }));\n }, [hierarchy]);\n\n const businessUnits = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.businessUnits || !selection.enterprise) return [];\n return hierarchy.businessUnits\n .filter((bu) => bu.enterpriseId === selection.enterprise!.id)\n .map((bu) => ({ id: bu.id, name: bu.name, code: bu.code }));\n }, [hierarchy, selection.enterprise]);\n\n const sites = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.sites || !selection.enterprise) return [];\n let filtered = hierarchy.sites.filter(\n (s) => s.enterpriseId === selection.enterprise!.id\n );\n if (selection.businessUnit) {\n filtered = filtered.filter(\n (s) => s.businessUnitId === selection.businessUnit!.id\n );\n }\n return filtered.map((s) => ({ id: s.id, name: s.name, code: s.code }));\n }, [hierarchy, selection.enterprise, selection.businessUnit]);\n\n const areas = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.areas || !selection.site) return [];\n return hierarchy.areas\n .filter((a) => a.siteId === selection.site!.id)\n .map((a) => ({ id: a.id, name: a.name, code: a.code }));\n }, [hierarchy, selection.site]);\n\n const assets = useMemo<OrgHierarchyNode[]>(() => {\n if (!hierarchy?.assets || !selection.area) return [];\n return hierarchy.assets\n .filter((a) => a.areaId === selection.area!.id)\n .map((a) => ({ id: a.id, name: a.name, code: a.code }));\n }, [hierarchy, selection.area]);\n\n // Auto-select first enterprise for non-platform_admin users\n useEffect(() => {\n if (isLoading || enterprises.length === 0) return;\n if (!selection.enterprise) {\n if (!canSwitchEnterprise || enterprises.length === 1) {\n setSelection((prev) => ({ ...prev, enterprise: enterprises[0] }));\n }\n }\n }, [isLoading, enterprises, selection.enterprise, canSwitchEnterprise]);\n\n // ── Cascade setters ──\n\n const selectEnterprise = useCallback((node: OrgHierarchyNode | null) => {\n setSelection({\n enterprise: node,\n businessUnit: null,\n site: null,\n area: null,\n asset: null,\n });\n // Notify backend of enterprise switch (platform_admin only)\n if (canSwitchEnterprise && onEnterpriseSwitched) {\n onEnterpriseSwitched(node?.id ?? null);\n }\n }, [canSwitchEnterprise, onEnterpriseSwitched]);\n\n const selectBusinessUnit = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n businessUnit: node,\n site: null,\n area: null,\n asset: null,\n }));\n }, []);\n\n const selectSite = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n site: node,\n area: null,\n asset: null,\n }));\n }, []);\n\n const selectArea = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n area: node,\n asset: null,\n }));\n }, []);\n\n const selectAsset = useCallback((node: OrgHierarchyNode | null) => {\n setSelection((prev) => ({\n ...prev,\n asset: node,\n }));\n }, []);\n\n // ── Breadcrumbs ──\n\n const breadcrumbs = useMemo(() => {\n const crumbs: { level: string; node: OrgHierarchyNode }[] = [];\n if (selection.enterprise)\n crumbs.push({ level: \"Enterprise\", node: selection.enterprise });\n if (selection.businessUnit)\n crumbs.push({ level: \"Business Unit\", node: selection.businessUnit });\n if (selection.site)\n crumbs.push({ level: \"Site\", node: selection.site });\n if (selection.area)\n crumbs.push({ level: \"Area\", node: selection.area });\n if (selection.asset)\n crumbs.push({ level: \"Asset\", node: selection.asset });\n return crumbs;\n }, [selection]);\n\n // ── Context value ──\n\n const value = useMemo<OrgHierarchyContextValue>(\n () => ({\n selection,\n enterprises,\n businessUnits,\n sites,\n areas,\n assets,\n selectEnterprise,\n selectBusinessUnit,\n selectSite,\n selectArea,\n selectAsset,\n canSwitchEnterprise,\n loading: isLoading,\n breadcrumbs,\n }),\n [\n selection,\n enterprises,\n businessUnits,\n sites,\n areas,\n assets,\n selectEnterprise,\n selectBusinessUnit,\n selectSite,\n selectArea,\n selectAsset,\n canSwitchEnterprise,\n isLoading,\n breadcrumbs,\n ]\n );\n\n return (\n <OrgHierarchyCtx.Provider value={value}>\n {children}\n </OrgHierarchyCtx.Provider>\n );\n }\n\n function useOrgHierarchy(): OrgHierarchyContextValue {\n return useContext(OrgHierarchyCtx);\n }\n\n return { OrgHierarchyProvider, useOrgHierarchy };\n}\n\n/* ── Re-export types for convenience ── */\nexport type {\n OrgHierarchyNode,\n OrgHierarchyData,\n OrgHierarchySelection,\n OrgHierarchyContextValue,\n} from \"./orgHierarchy\";\n","/**\n * HierarchyNavigator — breadcrumb-style hierarchy switcher.\n *\n * Displays: Enterprise / BU / Site / Area / Asset\n * Each level is a clickable dropdown to switch context.\n * Platform admins can switch enterprises; others see their scoped enterprise.\n *\n * NOTE: This component requires the consuming app to provide:\n * - A hierarchy context via useHierarchy prop\n * - shadcn/ui DropdownMenu components via the renderDropdown prop\n *\n * For apps using the createOrgHierarchyContext factory, pass the useOrgHierarchy hook.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent, #E2E8F0 text, #8890A0 muted\n */\nimport { cn } from \"../utils/cn\";\nimport type { OrgHierarchyNode, OrgHierarchyContextValue } from \"./orgHierarchy\";\nimport { ChevronRight, ChevronDown, Building2, Network, MapPin, LayoutGrid, Box } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\n/* ── Level config ── */\n\ninterface LevelConfig {\n key: string;\n label: string;\n icon: React.ElementType;\n getNodes: () => OrgHierarchyNode[];\n getSelected: () => OrgHierarchyNode | null;\n onSelect: (node: OrgHierarchyNode | null) => void;\n canSwitch?: boolean;\n}\n\n/* ── Dropdown render prop types ── */\n\nexport interface DropdownRenderProps {\n trigger: ReactNode;\n items: Array<{\n key: number;\n label: string;\n code?: string;\n icon: React.ElementType;\n isActive: boolean;\n onClick: () => void;\n }>;\n align?: \"start\" | \"end\";\n}\n\n/* ── Single breadcrumb level ── */\n\nfunction BreadcrumbLevel({\n config,\n isLast,\n compact,\n renderDropdown,\n}: {\n config: LevelConfig;\n isLast: boolean;\n compact?: boolean;\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}) {\n const nodes = config.getNodes();\n const selected = config.getSelected();\n const Icon = config.icon;\n const canSwitch = config.canSwitch !== false;\n const hasMultiple = nodes.length > 1;\n\n if (!selected) return null;\n\n // If only one option or can't switch, show as static text\n if (!hasMultiple || !canSwitch) {\n return (\n <div className=\"flex items-center gap-1\">\n <div\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#E2E8F0]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0 text-[#8890A0]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate max-w-[120px]\">{selected.name}</span>\n </div>\n {!isLast && (\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n )}\n </div>\n );\n }\n\n // Multiple options — use renderDropdown if provided, otherwise basic button\n const trigger = (\n <button\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#E2E8F0] hover:bg-[#1E2738] hover:text-white\",\n \"transition-colors cursor-pointer outline-none\",\n \"focus-visible:ring-1 focus-visible:ring-[#8C34E9]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0 text-[#8890A0]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate max-w-[120px]\">{selected.name}</span>\n <ChevronDown className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-2.5 w-2.5\" : \"h-3 w-3\")} />\n </button>\n );\n\n const items = nodes.map((node) => ({\n key: node.id,\n label: node.name,\n code: node.code,\n icon: Icon,\n isActive: selected.id === node.id,\n onClick: () => config.onSelect(node),\n }));\n\n return (\n <div className=\"flex items-center gap-1\">\n {renderDropdown ? (\n renderDropdown({ trigger, items, align: \"start\" })\n ) : (\n // Fallback: simple select-style rendering\n <div className=\"relative group\">\n {trigger}\n </div>\n )}\n {!isLast && (\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n )}\n </div>\n );\n}\n\n/* ── Drill-down prompt ── */\n\nfunction DrillDownPrompt({\n label,\n icon: Icon,\n nodes,\n onSelect,\n compact,\n renderDropdown,\n}: {\n label: string;\n icon: React.ElementType;\n nodes: OrgHierarchyNode[];\n onSelect: (node: OrgHierarchyNode) => void;\n compact?: boolean;\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}) {\n if (nodes.length === 0) return null;\n\n // Auto-select if only one option\n if (nodes.length === 1) {\n return (\n <div className=\"flex items-center gap-1\">\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <button\n onClick={() => onSelect(nodes[0])}\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#596475] hover:bg-[#1E2738] hover:text-[#8890A0]\",\n \"transition-colors cursor-pointer\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate\">{nodes[0].name}</span>\n </button>\n </div>\n );\n }\n\n const trigger = (\n <button\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md text-sm\",\n \"text-[#596475] hover:bg-[#1E2738] hover:text-[#8890A0]\",\n \"transition-colors cursor-pointer outline-none\",\n \"border border-dashed border-[#2A2A3E] hover:border-[#596475]\",\n compact && \"px-1.5 py-0.5 text-xs\"\n )}\n >\n <Icon className={cn(\"shrink-0\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n <span className=\"truncate\">Select {label}</span>\n <ChevronDown className={cn(\"shrink-0\", compact ? \"h-2.5 w-2.5\" : \"h-3 w-3\")} />\n </button>\n );\n\n const items = nodes.map((node) => ({\n key: node.id,\n label: node.name,\n code: node.code,\n icon: Icon,\n isActive: false,\n onClick: () => onSelect(node),\n }));\n\n return (\n <div className=\"flex items-center gap-1\">\n <ChevronRight className={cn(\"shrink-0 text-[#596475]\", compact ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n {renderDropdown ? (\n renderDropdown({ trigger, items, align: \"start\" })\n ) : (\n <div className=\"relative group\">\n {trigger}\n </div>\n )}\n </div>\n );\n}\n\n/* ── Main component ── */\n\nexport interface HierarchyNavigatorProps {\n /** The hierarchy context value — pass from useOrgHierarchy() */\n hierarchy: OrgHierarchyContextValue;\n /** Show in compact mode (smaller text, tighter spacing) */\n compact?: boolean;\n /** Maximum depth to show (default: all 5 levels) */\n maxDepth?: number;\n /** CSS class for the container */\n className?: string;\n /**\n * Render prop for dropdown menus. If not provided, dropdowns won't open.\n * Consuming apps should pass a function that renders their shadcn/ui DropdownMenu.\n */\n renderDropdown?: (props: DropdownRenderProps) => ReactNode;\n}\n\nexport function HierarchyNavigator({\n hierarchy: ctx,\n compact = false,\n maxDepth = 5,\n className,\n renderDropdown,\n}: HierarchyNavigatorProps) {\n if (ctx.loading) {\n return (\n <div className={cn(\"flex items-center gap-2 animate-pulse\", className)}>\n <div className=\"h-6 w-24 rounded bg-[#1E2738]\" />\n <ChevronRight className=\"h-3.5 w-3.5 text-[#596475]\" />\n <div className=\"h-6 w-20 rounded bg-[#1E2738]\" />\n </div>\n );\n }\n\n // Build the level configs\n const levels: LevelConfig[] = [\n {\n key: \"enterprise\",\n label: \"Enterprise\",\n icon: Building2,\n getNodes: () => ctx.enterprises,\n getSelected: () => ctx.selection.enterprise,\n onSelect: ctx.selectEnterprise,\n canSwitch: ctx.canSwitchEnterprise,\n },\n {\n key: \"businessUnit\",\n label: \"Business Unit\",\n icon: Network,\n getNodes: () => ctx.businessUnits,\n getSelected: () => ctx.selection.businessUnit,\n onSelect: ctx.selectBusinessUnit,\n },\n {\n key: \"site\",\n label: \"Site\",\n icon: MapPin,\n getNodes: () => ctx.sites,\n getSelected: () => ctx.selection.site,\n onSelect: ctx.selectSite,\n },\n {\n key: \"area\",\n label: \"Area\",\n icon: LayoutGrid,\n getNodes: () => ctx.areas,\n getSelected: () => ctx.selection.area,\n onSelect: ctx.selectArea,\n },\n {\n key: \"asset\",\n label: \"Asset\",\n icon: Box,\n getNodes: () => ctx.assets,\n getSelected: () => ctx.selection.asset,\n onSelect: ctx.selectAsset,\n },\n ].slice(0, maxDepth);\n\n // Determine which levels are selected and which is the next drill-down\n const selectedLevels = levels.filter((l) => l.getSelected() !== null);\n const nextLevel = levels.find((l) => l.getSelected() === null && l.getNodes().length > 0);\n\n return (\n <nav\n className={cn(\"flex items-center flex-wrap gap-0.5\", className)}\n aria-label=\"Hierarchy navigation\"\n >\n {selectedLevels.map((level, idx) => (\n <BreadcrumbLevel\n key={level.key}\n config={level}\n isLast={idx === selectedLevels.length - 1 && !nextLevel}\n compact={compact}\n renderDropdown={renderDropdown}\n />\n ))}\n {nextLevel && (\n <DrillDownPrompt\n label={nextLevel.label}\n icon={nextLevel.icon}\n nodes={nextLevel.getNodes()}\n onSelect={nextLevel.onSelect}\n compact={compact}\n renderDropdown={renderDropdown}\n />\n )}\n {selectedLevels.length === 0 && !nextLevel && (\n <span className=\"text-sm text-[#596475] italic\">No hierarchy data available</span>\n )}\n </nav>\n );\n}\n"],"mappings":"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","names":["jsx"]}
|
package/dist/layout/index.js
CHANGED
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
policyDeploymentConfig,
|
|
18
18
|
sqdcpConfig,
|
|
19
19
|
useSidebarResize
|
|
20
|
-
} from "../chunk-
|
|
21
|
-
import "../chunk-JT3XLKKD.js";
|
|
20
|
+
} from "../chunk-GU3OMCTY.js";
|
|
22
21
|
import "../chunk-LTUYIBMA.js";
|
|
22
|
+
import "../chunk-JT3XLKKD.js";
|
|
23
23
|
export {
|
|
24
24
|
DashboardLayout,
|
|
25
25
|
DashboardLayoutSkeleton,
|
package/dist/reporting/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cn
|
|
3
|
-
} from "../chunk-JT3XLKKD.js";
|
|
4
1
|
import {
|
|
5
2
|
Badge,
|
|
6
3
|
Button,
|
|
@@ -24,6 +21,9 @@ import {
|
|
|
24
21
|
SelectValue,
|
|
25
22
|
Textarea
|
|
26
23
|
} from "../chunk-LTUYIBMA.js";
|
|
24
|
+
import {
|
|
25
|
+
cn
|
|
26
|
+
} from "../chunk-JT3XLKKD.js";
|
|
27
27
|
|
|
28
28
|
// src/reporting/ReportingToolbar.tsx
|
|
29
29
|
import { useState } from "react";
|
package/dist/shell/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DashboardLayout,
|
|
3
3
|
HealthFooter
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-JT3XLKKD.js";
|
|
4
|
+
} from "../chunk-GU3OMCTY.js";
|
|
6
5
|
import "../chunk-LTUYIBMA.js";
|
|
6
|
+
import "../chunk-JT3XLKKD.js";
|
|
7
7
|
|
|
8
8
|
// src/shell/index.ts
|
|
9
9
|
var OplyticsServiceShell = DashboardLayout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pablo2410/shared-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Shared UI component library for all Oplytics.digital subdomains",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/hierarchy/index.d.ts",
|
|
17
17
|
"import": "./dist/hierarchy/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./hierarchy/core": {
|
|
20
|
+
"types": "./dist/hierarchy/core.d.ts",
|
|
21
|
+
"import": "./dist/hierarchy/core.js"
|
|
22
|
+
},
|
|
19
23
|
"./rbac": {
|
|
20
24
|
"types": "./dist/rbac/index.d.ts",
|
|
21
25
|
"import": "./dist/rbac/index.js"
|
|
File without changes
|