@pablo2410/shared-ui 0.9.3 → 0.10.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/{HealthFooter-BP7S_iT-.d.ts → HealthFooter-wAa7a7WR.d.ts} +14 -1
- package/dist/chunk-3E3GLIQV.js +112 -0
- package/dist/chunk-3E3GLIQV.js.map +1 -0
- package/dist/{chunk-NXSFRNI7.js → chunk-3G43UG63.js} +1 -35
- package/dist/chunk-3G43UG63.js.map +1 -0
- package/dist/chunk-4QJMLQSH.js +28 -0
- package/dist/chunk-4QJMLQSH.js.map +1 -0
- package/dist/{chunk-QAQ4SQBH.js → chunk-HWXEAHLJ.js} +241 -190
- package/dist/chunk-HWXEAHLJ.js.map +1 -0
- package/dist/chunk-PLS7NDDR.js +162 -0
- package/dist/chunk-PLS7NDDR.js.map +1 -0
- package/dist/chunk-PS4K7FKB.js +37 -0
- package/dist/chunk-PS4K7FKB.js.map +1 -0
- package/dist/{chunk-4ATPOBMZ.js → chunk-ZLOUWBQN.js} +24 -4
- package/dist/chunk-ZLOUWBQN.js.map +1 -0
- package/dist/crossNav-Bd8-0qoi.d.ts +44 -0
- package/dist/hierarchy/core.d.ts +3 -89
- package/dist/hierarchy/core.js +6 -4
- package/dist/hierarchy/index.d.ts +58 -3
- package/dist/hierarchy/index.js +24 -6
- package/dist/layout/index.d.ts +5 -3
- package/dist/layout/index.js +8 -3
- package/dist/orgHierarchy-BLW4dt8Q.d.ts +90 -0
- package/dist/services/index.d.ts +35 -3
- package/dist/services/index.js +31 -116
- package/dist/services/index.js.map +1 -1
- package/dist/shell/index.d.ts +4 -2
- package/dist/shell/index.js +13 -4
- package/dist/shell/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4ATPOBMZ.js.map +0 -1
- package/dist/chunk-NXSFRNI7.js.map +0 -1
- package/dist/chunk-QAQ4SQBH.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React__default, { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { C as CrossServiceLink } from './crossNav-Bd8-0qoi.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* SharedSidebar — Config-driven sidebar component for all Oplytics subdomains.
|
|
@@ -145,6 +146,10 @@ interface DashboardLayoutProps {
|
|
|
145
146
|
isActive?: (itemPath: string, activePath: string) => boolean;
|
|
146
147
|
/** URL for the "← Service Hub" link at the top of the nav (full-page nav). */
|
|
147
148
|
serviceHubUrl?: string;
|
|
149
|
+
/** Direct links to sibling services, rendered under the Service Hub link.
|
|
150
|
+
* Build with `buildCrossServiceLinks` (services/crossNav) so provisioning
|
|
151
|
+
* gating and the ?oh= hierarchy carry-over are handled consistently. */
|
|
152
|
+
crossServiceLinks?: CrossServiceLink[];
|
|
148
153
|
/** Footer "Settings" route. Omit to hide the footer. */
|
|
149
154
|
settingsPath?: string;
|
|
150
155
|
/** Authenticated user (drives the header user menu). */
|
|
@@ -185,6 +190,14 @@ interface DashboardLayoutProps {
|
|
|
185
190
|
}
|
|
186
191
|
declare function DashboardLayout(props: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
|
|
187
192
|
|
|
193
|
+
interface CrossServiceNavProps {
|
|
194
|
+
/** Display-ready links from buildCrossServiceLinks. Renders nothing when empty. */
|
|
195
|
+
links: CrossServiceLink[];
|
|
196
|
+
/** Section label above the links (hidden when the sidebar is collapsed). */
|
|
197
|
+
label?: string;
|
|
198
|
+
}
|
|
199
|
+
declare function CrossServiceNav({ links, label, }: CrossServiceNavProps): react_jsx_runtime.JSX.Element | null;
|
|
200
|
+
|
|
188
201
|
interface HealthFooterUser {
|
|
189
202
|
/** JWT role string (e.g. "platform_admin"). */
|
|
190
203
|
role?: string;
|
|
@@ -216,4 +229,4 @@ interface HealthFooterProps {
|
|
|
216
229
|
}
|
|
217
230
|
declare function HealthFooter({ user, enterprise, className, style, buildVersion, buildDate, commitHash, }: HealthFooterProps): react_jsx_runtime.JSX.Element;
|
|
218
231
|
|
|
219
|
-
export { DashboardLayout as D, HealthFooter as H, type MenuItem as M, type SharedSidebarConfig as S, type DashboardLayoutEnterprise as a, type DashboardLayoutProps as b, type DashboardLayoutUser as c, type
|
|
232
|
+
export { CrossServiceNav as C, DashboardLayout as D, HealthFooter as H, type MenuItem as M, type SharedSidebarConfig as S, type DashboardLayoutEnterprise as a, type DashboardLayoutProps as b, type DashboardLayoutUser as c, type CrossServiceNavProps as d, type HealthFooterEnterprise as e, type HealthFooterProps as f, type HealthFooterUser as g, type MenuSection as h, type SharedSidebarProps as i, getInitials as j, isAdminRole as k, isMenuItemActive as l, useSidebarResize as u };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ORG_HIERARCHY_EMPTY
|
|
3
|
+
} from "./chunk-PS4K7FKB.js";
|
|
4
|
+
|
|
5
|
+
// src/hierarchy/selectionParams.ts
|
|
6
|
+
var ORG_HIERARCHY_PARAM = "oh";
|
|
7
|
+
function serializeSelectionParam(selection) {
|
|
8
|
+
if (!selection.enterprise) return null;
|
|
9
|
+
const ids = [selection.enterprise.id];
|
|
10
|
+
for (const node of [
|
|
11
|
+
selection.businessUnit,
|
|
12
|
+
selection.site,
|
|
13
|
+
selection.area,
|
|
14
|
+
selection.asset
|
|
15
|
+
]) {
|
|
16
|
+
if (!node) break;
|
|
17
|
+
ids.push(node.id);
|
|
18
|
+
}
|
|
19
|
+
return ids.join(".");
|
|
20
|
+
}
|
|
21
|
+
function parseSelectionParam(value) {
|
|
22
|
+
const segments = value.split(".");
|
|
23
|
+
if (segments.length === 0 || segments.length > 5) return null;
|
|
24
|
+
const ids = [];
|
|
25
|
+
for (const seg of segments) {
|
|
26
|
+
if (!/^\d+$/.test(seg)) return null;
|
|
27
|
+
const n = Number(seg);
|
|
28
|
+
if (!Number.isSafeInteger(n) || n <= 0) return null;
|
|
29
|
+
ids.push(n);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
enterpriseId: ids[0] ?? null,
|
|
33
|
+
businessUnitId: ids[1] ?? null,
|
|
34
|
+
siteId: ids[2] ?? null,
|
|
35
|
+
areaId: ids[3] ?? null,
|
|
36
|
+
assetId: ids[4] ?? null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function appendSelectionParam(url, selection) {
|
|
40
|
+
const value = selection ? serializeSelectionParam(selection) : null;
|
|
41
|
+
if (!value) return url;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = new URL(url);
|
|
44
|
+
parsed.searchParams.set(ORG_HIERARCHY_PARAM, value);
|
|
45
|
+
return parsed.toString();
|
|
46
|
+
} catch {
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function readSelectionParamFromLocation() {
|
|
51
|
+
if (typeof window === "undefined") return null;
|
|
52
|
+
const value = new URLSearchParams(window.location.search).get(
|
|
53
|
+
ORG_HIERARCHY_PARAM
|
|
54
|
+
);
|
|
55
|
+
return value ? parseSelectionParam(value) : null;
|
|
56
|
+
}
|
|
57
|
+
function stripSelectionParamFromLocation() {
|
|
58
|
+
if (typeof window === "undefined") return;
|
|
59
|
+
const params = new URLSearchParams(window.location.search);
|
|
60
|
+
if (!params.has(ORG_HIERARCHY_PARAM)) return;
|
|
61
|
+
params.delete(ORG_HIERARCHY_PARAM);
|
|
62
|
+
const query = params.toString();
|
|
63
|
+
const next = window.location.pathname + (query ? `?${query}` : "") + window.location.hash;
|
|
64
|
+
window.history.replaceState(window.history.state, "", next);
|
|
65
|
+
}
|
|
66
|
+
function resolveSelectionFromIds(ids, hierarchy) {
|
|
67
|
+
const selection = { ...ORG_HIERARCHY_EMPTY };
|
|
68
|
+
const enterprise = ids.enterpriseId ? hierarchy.enterprises.find((e) => e.id === ids.enterpriseId) : void 0;
|
|
69
|
+
if (!enterprise) return selection;
|
|
70
|
+
selection.enterprise = {
|
|
71
|
+
id: enterprise.id,
|
|
72
|
+
name: enterprise.name,
|
|
73
|
+
code: enterprise.code
|
|
74
|
+
};
|
|
75
|
+
const businessUnit = ids.businessUnitId ? hierarchy.businessUnits.find(
|
|
76
|
+
(bu) => bu.id === ids.businessUnitId && bu.enterpriseId === enterprise.id
|
|
77
|
+
) : void 0;
|
|
78
|
+
if (ids.businessUnitId && !businessUnit) return selection;
|
|
79
|
+
if (businessUnit) {
|
|
80
|
+
selection.businessUnit = {
|
|
81
|
+
id: businessUnit.id,
|
|
82
|
+
name: businessUnit.name,
|
|
83
|
+
code: businessUnit.code
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const site = ids.siteId ? hierarchy.sites.find(
|
|
87
|
+
(s) => s.id === ids.siteId && s.enterpriseId === enterprise.id && (!businessUnit || s.businessUnitId === businessUnit.id)
|
|
88
|
+
) : void 0;
|
|
89
|
+
if (!site) return selection;
|
|
90
|
+
selection.site = { id: site.id, name: site.name, code: site.code };
|
|
91
|
+
const area = ids.areaId ? hierarchy.areas.find((a) => a.id === ids.areaId && a.siteId === site.id) : void 0;
|
|
92
|
+
if (!area) return selection;
|
|
93
|
+
selection.area = { id: area.id, name: area.name, code: area.code };
|
|
94
|
+
const asset = ids.assetId ? hierarchy.assets.find(
|
|
95
|
+
(a) => a.id === ids.assetId && a.areaId === area.id
|
|
96
|
+
) : void 0;
|
|
97
|
+
if (asset) {
|
|
98
|
+
selection.asset = { id: asset.id, name: asset.name, code: asset.code };
|
|
99
|
+
}
|
|
100
|
+
return selection;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
ORG_HIERARCHY_PARAM,
|
|
105
|
+
serializeSelectionParam,
|
|
106
|
+
parseSelectionParam,
|
|
107
|
+
appendSelectionParam,
|
|
108
|
+
readSelectionParamFromLocation,
|
|
109
|
+
stripSelectionParamFromLocation,
|
|
110
|
+
resolveSelectionFromIds
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=chunk-3E3GLIQV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hierarchy/selectionParams.ts"],"sourcesContent":["/**\n * selectionParams — serialize/deserialize the org-hierarchy selection through\n * a URL query param so it can travel across subdomains (cross-service links).\n *\n * Format: ?oh=<entId>.<buId>.<siteId>.<areaId>.<assetId>, dot-joined ids\n * top-down, truncated at the first unselected level (the cascade setters\n * guarantee the selection is always a contiguous chain, so no gaps occur).\n * e.g. ?oh=3.7.12 = enterprise 3 > business unit 7 > site 12.\n *\n * Node ids come from the Portal DB and are identical across every service,\n * which is what makes this transport valid.\n */\nimport type {\n OrgHierarchyData,\n OrgHierarchySelection,\n} from \"./orgHierarchy\";\nimport { ORG_HIERARCHY_EMPTY } from \"./orgHierarchy\";\n\n/** Query-param key carrying the org-hierarchy selection between services. */\nexport const ORG_HIERARCHY_PARAM = \"oh\";\n\n/** Selection expressed as raw ids (what the URL param decodes to). */\nexport interface OrgSelectionIds {\n enterpriseId: number | null;\n businessUnitId: number | null;\n siteId: number | null;\n areaId: number | null;\n assetId: number | null;\n}\n\n/**\n * Serialize a selection to the compact param value.\n * Returns null when there is nothing meaningful to carry (no enterprise).\n */\nexport function serializeSelectionParam(\n selection: OrgHierarchySelection,\n): string | null {\n if (!selection.enterprise) return null;\n const ids: number[] = [selection.enterprise.id];\n for (const node of [\n selection.businessUnit,\n selection.site,\n selection.area,\n selection.asset,\n ]) {\n if (!node) break; // contiguous chain — stop at the first unselected level\n ids.push(node.id);\n }\n return ids.join(\".\");\n}\n\n/**\n * Parse a param value back to ids. Returns null for anything malformed\n * (empty, non-numeric, negative, or too many segments).\n */\nexport function parseSelectionParam(value: string): OrgSelectionIds | null {\n const segments = value.split(\".\");\n if (segments.length === 0 || segments.length > 5) return null;\n const ids: number[] = [];\n for (const seg of segments) {\n if (!/^\\d+$/.test(seg)) return null;\n const n = Number(seg);\n if (!Number.isSafeInteger(n) || n <= 0) return null;\n ids.push(n);\n }\n return {\n enterpriseId: ids[0] ?? null,\n businessUnitId: ids[1] ?? null,\n siteId: ids[2] ?? null,\n areaId: ids[3] ?? null,\n assetId: ids[4] ?? null,\n };\n}\n\n/**\n * Append the selection param to an absolute URL (used when building\n * cross-service links). Returns the URL unchanged when there is no selection\n * or the URL is unparseable.\n */\nexport function appendSelectionParam(\n url: string,\n selection: OrgHierarchySelection | null | undefined,\n): string {\n const value = selection ? serializeSelectionParam(selection) : null;\n if (!value) return url;\n try {\n const parsed = new URL(url);\n parsed.searchParams.set(ORG_HIERARCHY_PARAM, value);\n return parsed.toString();\n } catch {\n return url;\n }\n}\n\n/** Read (without modifying) the selection param from the current location. */\nexport function readSelectionParamFromLocation(): OrgSelectionIds | null {\n if (typeof window === \"undefined\") return null;\n const value = new URLSearchParams(window.location.search).get(\n ORG_HIERARCHY_PARAM,\n );\n return value ? parseSelectionParam(value) : null;\n}\n\n/**\n * Remove ONLY the selection param from the address bar (history.replaceState),\n * preserving every other query param and the hash.\n */\nexport function stripSelectionParamFromLocation(): void {\n if (typeof window === \"undefined\") return;\n const params = new URLSearchParams(window.location.search);\n if (!params.has(ORG_HIERARCHY_PARAM)) return;\n params.delete(ORG_HIERARCHY_PARAM);\n const query = params.toString();\n const next =\n window.location.pathname +\n (query ? `?${query}` : \"\") +\n window.location.hash;\n window.history.replaceState(window.history.state, \"\", next);\n}\n\n/**\n * Resolve raw ids against the (already enterprise-scoped) hierarchy data,\n * validating existence AND parent-child integrity level by level. Returns the\n * deepest valid prefix as full nodes; an invalid enterprise yields the empty\n * selection so callers fall back to their normal default behaviour.\n */\nexport function resolveSelectionFromIds(\n ids: OrgSelectionIds,\n hierarchy: OrgHierarchyData,\n): OrgHierarchySelection {\n const selection: OrgHierarchySelection = { ...ORG_HIERARCHY_EMPTY };\n\n const enterprise = ids.enterpriseId\n ? hierarchy.enterprises.find((e) => e.id === ids.enterpriseId)\n : undefined;\n if (!enterprise) return selection;\n selection.enterprise = {\n id: enterprise.id,\n name: enterprise.name,\n code: enterprise.code,\n };\n\n const businessUnit = ids.businessUnitId\n ? hierarchy.businessUnits.find(\n (bu) => bu.id === ids.businessUnitId && bu.enterpriseId === enterprise.id,\n )\n : undefined;\n // A provided-but-invalid id breaks the chain: everything below it is\n // discarded so the result is always a contiguous, internally consistent\n // prefix of the requested selection.\n if (ids.businessUnitId && !businessUnit) return selection;\n if (businessUnit) {\n selection.businessUnit = {\n id: businessUnit.id,\n name: businessUnit.name,\n code: businessUnit.code,\n };\n }\n\n const site = ids.siteId\n ? hierarchy.sites.find(\n (s) =>\n s.id === ids.siteId &&\n s.enterpriseId === enterprise.id &&\n (!businessUnit || s.businessUnitId === businessUnit.id),\n )\n : undefined;\n if (!site) return selection;\n selection.site = { id: site.id, name: site.name, code: site.code };\n\n const area = ids.areaId\n ? hierarchy.areas.find((a) => a.id === ids.areaId && a.siteId === site.id)\n : undefined;\n if (!area) return selection;\n selection.area = { id: area.id, name: area.name, code: area.code };\n\n const asset = ids.assetId\n ? hierarchy.assets.find(\n (a) => a.id === ids.assetId && a.areaId === area.id,\n )\n : undefined;\n if (asset) {\n selection.asset = { id: asset.id, name: asset.name, code: asset.code };\n }\n\n return selection;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,33 +1,3 @@
|
|
|
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
1
|
// src/hierarchy/enterpriseOverride.ts
|
|
32
2
|
var ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
|
|
33
3
|
function parseCookieValue(cookieHeader, name) {
|
|
@@ -64,12 +34,8 @@ function scopeHierarchyToEnterprise(data, enterpriseId) {
|
|
|
64
34
|
}
|
|
65
35
|
|
|
66
36
|
export {
|
|
67
|
-
ORG_HIERARCHY_EMPTY,
|
|
68
|
-
ORG_HIERARCHY_DATA_EMPTY,
|
|
69
|
-
ORG_HIERARCHY_LEVELS,
|
|
70
|
-
ORG_HIERARCHY_LABELS,
|
|
71
37
|
ACTIVE_ENTERPRISE_COOKIE,
|
|
72
38
|
applyEnterpriseOverride,
|
|
73
39
|
scopeHierarchyToEnterprise
|
|
74
40
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
41
|
+
//# sourceMappingURL=chunk-3G43UG63.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hierarchy/enterpriseOverride.ts","../src/hierarchy/scopeHierarchy.ts"],"sourcesContent":["/**\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":";AAiBO,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,28 @@
|
|
|
1
|
+
// src/services/platformDomain.ts
|
|
2
|
+
var DEFAULT_PLATFORM_DOMAIN = "oplyticsdigital.net";
|
|
3
|
+
var IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
4
|
+
function getPlatformDomain() {
|
|
5
|
+
if (typeof window === "undefined") return DEFAULT_PLATFORM_DOMAIN;
|
|
6
|
+
const host = window.location.hostname;
|
|
7
|
+
if (!host || host === "localhost" || IPV4.test(host)) {
|
|
8
|
+
return DEFAULT_PLATFORM_DOMAIN;
|
|
9
|
+
}
|
|
10
|
+
const parts = host.split(".");
|
|
11
|
+
if (parts.length >= 3) return parts.slice(1).join(".");
|
|
12
|
+
if (parts.length === 2) return host;
|
|
13
|
+
return DEFAULT_PLATFORM_DOMAIN;
|
|
14
|
+
}
|
|
15
|
+
function platformUrl(subdomainLabel, path = "") {
|
|
16
|
+
return `https://${subdomainLabel}.${getPlatformDomain()}${path}`;
|
|
17
|
+
}
|
|
18
|
+
function portalUrl(path = "") {
|
|
19
|
+
return platformUrl("portal", path);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_PLATFORM_DOMAIN,
|
|
24
|
+
getPlatformDomain,
|
|
25
|
+
platformUrl,
|
|
26
|
+
portalUrl
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-4QJMLQSH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/platformDomain.ts"],"sourcesContent":["/**\n * Platform domain resolution — single source of truth for building\n * cross-service URLs (Service Hub cards, sidebar cross-nav, footers,\n * \"Back to Portal\" links).\n *\n * Every service runs on a sibling subdomain of one parent domain\n * (portal.oplyticsdigital.net, sqdcp.oplyticsdigital.net, …), so the parent\n * domain is derived at runtime from the current hostname: strip the service\n * label, keep the rest. Links therefore stay on whichever domain the app is\n * served from — no per-app build args, and a future domain move needs no\n * code change here.\n *\n * Outside a browser (SSR, tests) or on hosts with no parent domain\n * (localhost, raw IPs) the canonical production domain is used.\n */\n\nexport const DEFAULT_PLATFORM_DOMAIN = \"oplyticsdigital.net\";\n\nconst IPV4 = /^\\d{1,3}(\\.\\d{1,3}){3}$/;\n\nexport function getPlatformDomain(): string {\n if (typeof window === \"undefined\") return DEFAULT_PLATFORM_DOMAIN;\n const host = window.location.hostname;\n if (!host || host === \"localhost\" || IPV4.test(host)) {\n return DEFAULT_PLATFORM_DOMAIN;\n }\n const parts = host.split(\".\");\n // sqdcp.oplyticsdigital.net → oplyticsdigital.net\n if (parts.length >= 3) return parts.slice(1).join(\".\");\n // already on the apex (oplyticsdigital.net)\n if (parts.length === 2) return host;\n return DEFAULT_PLATFORM_DOMAIN;\n}\n\n/**\n * Absolute https URL for a service subdomain label on the current platform\n * domain, e.g. `platformUrl(\"sqdcp\")` → `https://sqdcp.oplyticsdigital.net`.\n */\nexport function platformUrl(subdomainLabel: string, path = \"\"): string {\n return `https://${subdomainLabel}.${getPlatformDomain()}${path}`;\n}\n\n/** Portal URL builder — the portal always lives at the `portal` label. */\nexport function portalUrl(path = \"\"): string {\n return platformUrl(\"portal\", path);\n}\n"],"mappings":";AAgBO,IAAM,0BAA0B;AAEvC,IAAM,OAAO;AAEN,SAAS,oBAA4B;AAC1C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,OAAO,OAAO,SAAS;AAC7B,MAAI,CAAC,QAAQ,SAAS,eAAe,KAAK,KAAK,IAAI,GAAG;AACpD,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAE5B,MAAI,MAAM,UAAU,EAAG,QAAO,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;AAErD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO;AACT;AAMO,SAAS,YAAY,gBAAwB,OAAO,IAAY;AACrE,SAAO,WAAW,cAAc,IAAI,kBAAkB,CAAC,GAAG,IAAI;AAChE;AAGO,SAAS,UAAU,OAAO,IAAY;AAC3C,SAAO,YAAY,UAAU,IAAI;AACnC;","names":[]}
|