@pablo2410/shared-ui 0.9.4 → 0.11.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.
@@ -146,6 +146,10 @@ interface DashboardLayoutProps {
146
146
  isActive?: (itemPath: string, activePath: string) => boolean;
147
147
  /** URL for the "← Service Hub" link at the top of the nav (full-page nav). */
148
148
  serviceHubUrl?: string;
149
+ /** URL for the "Portal" link in the header (right of the enterprise badge).
150
+ * Omit to hide the link. Defaults to the platform portal root when not
151
+ * explicitly disabled — pass `null` to hide it. */
152
+ portalUrl?: string | null;
149
153
  /** Direct links to sibling services, rendered under the Service Hub link.
150
154
  * Build with `buildCrossServiceLinks` (services/crossNav) so provisioning
151
155
  * gating and the ?oh= hierarchy carry-over are handled consistently. */
@@ -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":[]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ platformUrl
3
+ } from "./chunk-4QJMLQSH.js";
1
4
  import {
2
5
  appendSelectionParam
3
6
  } from "./chunk-3E3GLIQV.js";
@@ -14,7 +17,7 @@ var SERVICE_CATALOG = [
14
17
  accentColor: "#F59E0B",
15
18
  status: "live",
16
19
  category: "core",
17
- subdomain: "policydeployment.oplytics.digital"
20
+ subdomainLabel: "policy"
18
21
  },
19
22
  {
20
23
  slug: "sqdcp",
@@ -25,7 +28,7 @@ var SERVICE_CATALOG = [
25
28
  accentColor: "#1DB8CE",
26
29
  status: "live",
27
30
  category: "core",
28
- subdomain: "sqdcp.oplytics.digital"
31
+ subdomainLabel: "sqdcp"
29
32
  },
30
33
  {
31
34
  slug: "oee-manager",
@@ -36,7 +39,7 @@ var SERVICE_CATALOG = [
36
39
  accentColor: "#8C34E9",
37
40
  status: "live",
38
41
  category: "core",
39
- subdomain: "oeemanager.oplytics.digital"
42
+ subdomainLabel: "oeemanager"
40
43
  },
41
44
  {
42
45
  slug: "action-manager",
@@ -47,7 +50,7 @@ var SERVICE_CATALOG = [
47
50
  accentColor: "#22C55E",
48
51
  status: "live",
49
52
  category: "core",
50
- subdomain: "actionmanager.oplytics.digital"
53
+ subdomainLabel: "actionmanager"
51
54
  },
52
55
  {
53
56
  slug: "connect",
@@ -58,7 +61,7 @@ var SERVICE_CATALOG = [
58
61
  accentColor: "#1DB8CE",
59
62
  status: "in-development",
60
63
  category: "core",
61
- subdomain: "connect.oplytics.digital"
64
+ subdomainLabel: "connect"
62
65
  },
63
66
  // ─── HUB SERVICES ───
64
67
  {
@@ -70,7 +73,7 @@ var SERVICE_CATALOG = [
70
73
  accentColor: "#EF4444",
71
74
  status: "coming-soon",
72
75
  category: "hub",
73
- subdomain: "safetymanager.oplytics.digital"
76
+ subdomainLabel: "safetymanager"
74
77
  },
75
78
  {
76
79
  slug: "quality-manager",
@@ -81,7 +84,7 @@ var SERVICE_CATALOG = [
81
84
  accentColor: "#22C55E",
82
85
  status: "coming-soon",
83
86
  category: "hub",
84
- subdomain: null
87
+ subdomainLabel: null
85
88
  },
86
89
  {
87
90
  slug: "certification-manager",
@@ -92,7 +95,7 @@ var SERVICE_CATALOG = [
92
95
  accentColor: "#F97316",
93
96
  status: "coming-soon",
94
97
  category: "hub",
95
- subdomain: null
98
+ subdomainLabel: null
96
99
  }
97
100
  ];
98
101
  var coreServices = SERVICE_CATALOG.filter(
@@ -112,8 +115,8 @@ function getServiceBySlug(slug) {
112
115
  return SERVICE_CATALOG.find((s) => s.slug === slug);
113
116
  }
114
117
  function getServiceUrl(service) {
115
- if (service.subdomain) {
116
- return `https://${service.subdomain}`;
118
+ if (service.subdomainLabel) {
119
+ return platformUrl(service.subdomainLabel);
117
120
  }
118
121
  return null;
119
122
  }
@@ -132,7 +135,7 @@ function buildCrossServiceLinks(options) {
132
135
  const granted = new Set(grantedSlugs.map(normalizeServiceSlug));
133
136
  const current = normalizeServiceSlug(currentSlug);
134
137
  return SERVICE_CATALOG.filter(
135
- (svc) => svc.status === "live" && svc.subdomain !== null && svc.slug !== current && (isAdmin || granted.has(svc.slug))
138
+ (svc) => svc.status === "live" && svc.subdomainLabel !== null && svc.slug !== current && (isAdmin || granted.has(svc.slug))
136
139
  ).map((svc) => ({
137
140
  slug: svc.slug,
138
141
  name: svc.name,
@@ -156,4 +159,4 @@ export {
156
159
  normalizeServiceSlug,
157
160
  buildCrossServiceLinks
158
161
  };
159
- //# sourceMappingURL=chunk-HNOROBPK.js.map
162
+ //# sourceMappingURL=chunk-PLS7NDDR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/catalog.ts","../src/services/crossNav.ts"],"sourcesContent":["/**\n * Shared Service Catalog — Single source of truth for all Oplytics.digital services.\n *\n * Consumed by:\n * - oplytics-subdomains (ServiceHub.tsx) — service cards, access gating\n * - oplytics-marketing-site (services.ts) — marketing pages, hero sections\n *\n * Each consumer extends with context-specific fields locally:\n * - Platform adds: route, external URL, React icon component\n * - Marketing adds: heroImage, demoImage, problem, howItWorks, results, aiFeatures\n */\n\nimport { platformUrl } from \"./platformDomain\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ServiceStatus = \"live\" | \"in-development\" | \"coming-soon\";\nexport type ServiceCategory = \"core\" | \"hub\";\n\nexport interface ServiceDefinition {\n /** Unique identifier used for DB lookups, URL slugs, and cross-referencing */\n slug: string;\n /** Display name shown in cards, headers, and navigation */\n name: string;\n /** Short one-line value proposition */\n tagline: string;\n /** Longer description (1-2 sentences) for cards and summaries */\n description: string;\n /** Lucide icon name (string) — consumers resolve to component locally */\n icon: string;\n /** Hex accent color for cards, badges, and theming */\n accentColor: string;\n /** Current availability status */\n status: ServiceStatus;\n /** Core = standalone product, Hub = extension/add-on module */\n category: ServiceCategory;\n /**\n * Subdomain label on the platform domain (e.g. \"sqdcp\") — null if hosted\n * on portal. Full URLs are built via getServiceUrl(), which resolves the\n * platform domain at runtime (see ./platformDomain).\n */\n subdomainLabel: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Catalog\n// ---------------------------------------------------------------------------\n\nexport const SERVICE_CATALOG: ServiceDefinition[] = [\n // ─── CORE SERVICES ───\n {\n slug: \"policy-deployment\",\n name: \"Policy Deployment\",\n tagline: \"Align strategy to execution\",\n description:\n \"Hoshin Kanri and X-matrix planning. Cascade strategic objectives through every level of your organisation with live tracking and bowling charts.\",\n icon: \"Target\",\n accentColor: \"#F59E0B\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"policy\",\n },\n {\n slug: \"sqdcp\",\n name: \"SQDCP Dashboard\",\n tagline: \"Daily management boards, digitised\",\n description:\n \"Digital SQDCP boards that drive daily accountability. Safety, Quality, Delivery, Cost, and People metrics at a glance for every team.\",\n icon: \"LayoutGrid\",\n accentColor: \"#1DB8CE\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"sqdcp\",\n },\n {\n slug: \"oee-manager\",\n name: \"OEE Manager\",\n tagline: \"Real-time equipment effectiveness tracking\",\n description:\n \"Monitor Overall Equipment Effectiveness in real-time. Track availability, performance, and quality metrics across your entire production line.\",\n icon: \"Gauge\",\n accentColor: \"#8C34E9\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"oeemanager\",\n },\n {\n slug: \"action-manager\",\n name: \"Action Manager\",\n tagline: \"Track every action to closure\",\n description:\n \"Capture, assign, and track corrective and preventive actions from any source. Ensure nothing falls through the cracks.\",\n icon: \"ClipboardCheck\",\n accentColor: \"#22C55E\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"actionmanager\",\n },\n {\n slug: \"connect\",\n name: \"OplyticsConnect\",\n tagline: \"Integrate machines, sensors, and systems\",\n description:\n \"Industrial IoT connectivity layer. Connect PLCs, sensors, and legacy systems to the Oplytics platform with zero-code configuration.\",\n icon: \"Plug\",\n accentColor: \"#1DB8CE\",\n status: \"in-development\",\n category: \"core\",\n subdomainLabel: \"connect\",\n },\n\n // ─── HUB SERVICES ───\n {\n slug: \"safety-manager\",\n name: \"Safety Manager\",\n tagline: \"Proactive safety culture, powered by data\",\n description:\n \"Incident reporting, hazard tracking, safety observations, and compliance management in one unified platform.\",\n icon: \"Shield\",\n accentColor: \"#EF4444\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: \"safetymanager\",\n },\n {\n slug: \"quality-manager\",\n name: \"Quality Manager\",\n tagline: \"End-to-end quality assurance\",\n description:\n \"Non-conformance tracking, CAPA management, audit scheduling, and quality metrics dashboards.\",\n icon: \"CheckCircle\",\n accentColor: \"#22C55E\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: null,\n },\n {\n slug: \"certification-manager\",\n name: \"Certification Manager\",\n tagline: \"Stay audit-ready, always\",\n description:\n \"Manage ISO, IATF, and other certification requirements. Document control, audit trails, and compliance tracking.\",\n icon: \"Award\",\n accentColor: \"#F97316\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: null,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Derived helpers\n// ---------------------------------------------------------------------------\n\nexport const coreServices = SERVICE_CATALOG.filter(\n (s) => s.category === \"core\",\n);\nexport const hubServices = SERVICE_CATALOG.filter((s) => s.category === \"hub\");\nexport const liveServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"live\",\n);\nexport const inDevServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"in-development\",\n);\nexport const comingSoonServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"coming-soon\",\n);\n\nexport function getServiceBySlug(\n slug: string,\n): ServiceDefinition | undefined {\n return SERVICE_CATALOG.find((s) => s.slug === slug);\n}\n\nexport function getServiceUrl(service: ServiceDefinition): string | null {\n if (service.subdomainLabel) {\n return platformUrl(service.subdomainLabel);\n }\n return null;\n}\n","/**\n * crossNav — build the sidebar cross-service links (service → service jump\n * without going through the Portal Service Hub).\n *\n * Visibility mirrors the Portal ServiceHub rule: platform_admin and\n * enterprise_admin see every live service; everyone else only sees the\n * services granted in the portal `user_services` table. The current service\n * never links to itself. Each link URL carries the caller's current\n * org-hierarchy selection (?oh=…) so the breadcrumb arrives pre-aligned.\n */\nimport type { OrgHierarchySelection } from \"../hierarchy/orgHierarchy\";\nimport { appendSelectionParam } from \"../hierarchy/selectionParams\";\nimport { SERVICE_CATALOG, getServiceUrl } from \"./catalog\";\n\n/** Roles that see every live service regardless of individual grants. */\nexport const ADMIN_SEE_ALL_ROLES = [\"platform_admin\", \"enterprise_admin\"] as const;\n\n/**\n * Legacy grant slugs → canonical catalog slugs. Auto-provisioned accounts\n * were granted \"sqdcp-logic\" while the catalog slug is \"sqdcp\".\n */\nexport const SLUG_ALIASES: Record<string, string> = {\n \"sqdcp-logic\": \"sqdcp\",\n};\n\nexport function normalizeServiceSlug(slug: string): string {\n return SLUG_ALIASES[slug] ?? slug;\n}\n\n/** Display-ready cross-service link entry. */\nexport interface CrossServiceLink {\n slug: string;\n name: string;\n /** Lucide icon name from the service catalog (consumers resolve locally). */\n icon: string;\n accentColor: string;\n /** Absolute URL including the ?oh= hierarchy selection param. */\n url: string;\n}\n\nexport interface BuildCrossServiceLinksOptions {\n /** Catalog slug of the service doing the rendering (excluded from links). */\n currentSlug: string;\n /** Grant slugs from the portal (raw — aliases are normalized here). */\n grantedSlugs: string[];\n /** Portal role of the current user (drives the admin see-all rule). */\n role?: string | null;\n /** Current org-hierarchy selection to carry across (may be empty/null). */\n selection?: OrgHierarchySelection | null;\n}\n\nexport function buildCrossServiceLinks(\n options: BuildCrossServiceLinksOptions,\n): CrossServiceLink[] {\n const { currentSlug, grantedSlugs, role, selection } = options;\n\n const isAdmin =\n !!role && (ADMIN_SEE_ALL_ROLES as readonly string[]).includes(role);\n const granted = new Set(grantedSlugs.map(normalizeServiceSlug));\n const current = normalizeServiceSlug(currentSlug);\n\n return SERVICE_CATALOG.filter(\n (svc) =>\n svc.status === \"live\" &&\n svc.subdomainLabel !== null &&\n svc.slug !== current &&\n (isAdmin || granted.has(svc.slug)),\n ).map((svc) => ({\n slug: svc.slug,\n name: svc.name,\n icon: svc.icon,\n accentColor: svc.accentColor,\n url: appendSelectionParam(getServiceUrl(svc)!, selection),\n }));\n}\n"],"mappings":";;;;;;;;AAkDO,IAAM,kBAAuC;AAAA;AAAA,EAElD;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA;AAAA,EAGA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,EAClB;AACF;AAMO,IAAM,eAAe,gBAAgB;AAAA,EAC1C,CAAC,MAAM,EAAE,aAAa;AACxB;AACO,IAAM,cAAc,gBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,KAAK;AACtE,IAAM,eAAe,gBAAgB;AAAA,EAC1C,CAAC,MAAM,EAAE,WAAW;AACtB;AACO,IAAM,gBAAgB,gBAAgB;AAAA,EAC3C,CAAC,MAAM,EAAE,WAAW;AACtB;AACO,IAAM,qBAAqB,gBAAgB;AAAA,EAChD,CAAC,MAAM,EAAE,WAAW;AACtB;AAEO,SAAS,iBACd,MAC+B;AAC/B,SAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACpD;AAEO,SAAS,cAAc,SAA2C;AACvE,MAAI,QAAQ,gBAAgB;AAC1B,WAAO,YAAY,QAAQ,cAAc;AAAA,EAC3C;AACA,SAAO;AACT;;;ACtKO,IAAM,sBAAsB,CAAC,kBAAkB,kBAAkB;AAMjE,IAAM,eAAuC;AAAA,EAClD,eAAe;AACjB;AAEO,SAAS,qBAAqB,MAAsB;AACzD,SAAO,aAAa,IAAI,KAAK;AAC/B;AAwBO,SAAS,uBACd,SACoB;AACpB,QAAM,EAAE,aAAa,cAAc,MAAM,UAAU,IAAI;AAEvD,QAAM,UACJ,CAAC,CAAC,QAAS,oBAA0C,SAAS,IAAI;AACpE,QAAM,UAAU,IAAI,IAAI,aAAa,IAAI,oBAAoB,CAAC;AAC9D,QAAM,UAAU,qBAAqB,WAAW;AAEhD,SAAO,gBAAgB;AAAA,IACrB,CAAC,QACC,IAAI,WAAW,UACf,IAAI,mBAAmB,QACvB,IAAI,SAAS,YACZ,WAAW,QAAQ,IAAI,IAAI,IAAI;AAAA,EACpC,EAAE,IAAI,CAAC,SAAS;AAAA,IACd,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,KAAK,qBAAqB,cAAc,GAAG,GAAI,SAAS;AAAA,EAC1D,EAAE;AACJ;","names":[]}
@@ -1,3 +1,7 @@
1
+ import {
2
+ platformUrl,
3
+ portalUrl
4
+ } from "./chunk-4QJMLQSH.js";
1
5
  import {
2
6
  readActiveEnterpriseCookie
3
7
  } from "./chunk-ZLOUWBQN.js";
@@ -88,7 +92,7 @@ function useSidebarResize(config) {
88
92
  }
89
93
 
90
94
  // src/layout/DashboardLayout.tsx
91
- import { ArrowLeft, LogOut, PanelLeft, Settings } from "lucide-react";
95
+ import { ArrowLeft, ExternalLink as ExternalLink2, LogOut, PanelLeft, Settings } from "lucide-react";
92
96
 
93
97
  // src/layout/CrossServiceNav.tsx
94
98
  import {
@@ -252,12 +256,13 @@ function EnterpriseDebugLabel({
252
256
 
253
257
  // src/layout/DashboardLayout.tsx
254
258
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
255
- var DEFAULT_SERVICE_HUB_URL = "https://portal.oplytics.digital/app";
256
- var DEFAULT_USER_SETTINGS_URL = "https://portal.oplytics.digital/account";
259
+ var defaultServiceHubUrl = () => portalUrl("/app");
260
+ var defaultUserSettingsUrl = () => portalUrl("/account");
261
+ var defaultPortalUrl = () => portalUrl("/");
257
262
  function UserMenu({
258
263
  user,
259
264
  onLogout,
260
- userSettingsUrl = DEFAULT_USER_SETTINGS_URL,
265
+ userSettingsUrl = defaultUserSettingsUrl(),
261
266
  enterprise
262
267
  }) {
263
268
  const initials = (user.name || "U").split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
@@ -351,7 +356,8 @@ function DashboardShell({
351
356
  activePath,
352
357
  onNavigate,
353
358
  isActive,
354
- serviceHubUrl = DEFAULT_SERVICE_HUB_URL,
359
+ serviceHubUrl = defaultServiceHubUrl(),
360
+ portalUrl: portalUrl2 = defaultPortalUrl(),
355
361
  crossServiceLinks,
356
362
  settingsPath,
357
363
  user,
@@ -458,6 +464,18 @@ function DashboardShell({
458
464
  /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 shrink-0", children: [
459
465
  reportingToolbar,
460
466
  /* @__PURE__ */ jsx4(EnterpriseBadge, { enterprise }),
467
+ portalUrl2 && /* @__PURE__ */ jsxs4(
468
+ "a",
469
+ {
470
+ href: portalUrl2,
471
+ className: "hidden md:flex items-center gap-1 px-2 py-1 rounded-md text-[10px] text-[#8890A0] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors",
472
+ title: "Back to Portal",
473
+ children: [
474
+ /* @__PURE__ */ jsx4(ExternalLink2, { className: "h-3 w-3" }),
475
+ /* @__PURE__ */ jsx4("span", { children: "Portal" })
476
+ ]
477
+ }
478
+ ),
461
479
  /* @__PURE__ */ jsx4("div", { className: "w-px h-6 bg-[#1E2738] hidden md:block" }),
462
480
  user && /* @__PURE__ */ jsx4(
463
481
  UserMenu,
@@ -631,12 +649,12 @@ function SharedPageHeader({
631
649
  // src/layout/SharedFooter.tsx
632
650
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
633
651
  var FOOTER_SERVICES = [
634
- { name: "SQDCP", slug: "sqdcp", localRoute: "/services/sqdcp", externalUrl: "https://sqdcp.oplytics.digital" },
635
- { name: "OEE Manager", slug: "oee-manager", localRoute: "/services/oee-manager", externalUrl: "https://oee.oplytics.digital" },
636
- { name: "Policy Deployment", slug: "policy-deployment", localRoute: "/services/policy-deployment", externalUrl: "https://policy.oplytics.digital" },
637
- { name: "Action Manager", slug: "action-manager", localRoute: "/services/action-manager", externalUrl: "https://actions.oplytics.digital" },
652
+ { name: "SQDCP", slug: "sqdcp", localRoute: "/services/sqdcp", externalUrl: platformUrl("sqdcp") },
653
+ { name: "OEE Manager", slug: "oee-manager", localRoute: "/services/oee-manager", externalUrl: platformUrl("oeemanager") },
654
+ { name: "Policy Deployment", slug: "policy-deployment", localRoute: "/services/policy-deployment", externalUrl: platformUrl("policy") },
655
+ { name: "Action Manager", slug: "action-manager", localRoute: "/services/action-manager", externalUrl: platformUrl("actionmanager") },
638
656
  { name: "Safety Manager", slug: "safety-manager", localRoute: "/services/safety-manager" },
639
- { name: "Connect", slug: "connect", localRoute: "/services/connect", externalUrl: "https://connect.oplytics.digital" }
657
+ { name: "Connect", slug: "connect", localRoute: "/services/connect", externalUrl: platformUrl("connect") }
640
658
  ];
641
659
  function SharedFooter({
642
660
  services = FOOTER_SERVICES,
@@ -933,7 +951,7 @@ var oeeManagerConfig = {
933
951
  ],
934
952
  backLink: {
935
953
  label: "Back to Portal",
936
- path: "https://portal.oplytics.digital"
954
+ path: portalUrl()
937
955
  },
938
956
  showPageHeader: true,
939
957
  showReportingToolbar: true,
@@ -1058,4 +1076,4 @@ export {
1058
1076
  policyDeploymentConfig,
1059
1077
  SERVICE_CONFIGS
1060
1078
  };
1061
- //# sourceMappingURL=chunk-POOTKX4M.js.map
1079
+ //# sourceMappingURL=chunk-WMUCY5GV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/layout/SharedSidebar.tsx","../src/layout/DashboardLayout.tsx","../src/layout/CrossServiceNav.tsx","../src/layout/HealthFooter.tsx","../src/layout/EnterpriseDebugLabel.tsx","../src/layout/DashboardLayoutSkeleton.tsx","../src/layout/SharedPageHeader.tsx","../src/layout/SharedFooter.tsx","../src/layout/ServiceFooter.tsx","../src/layout/createServiceLayout.tsx","../src/layout/serviceConfigs.ts"],"sourcesContent":["/**\n * SharedSidebar — Config-driven sidebar component for all Oplytics subdomains.\n *\n * Replaces per-subdomain DashboardLayout sidebar implementations with a single\n * shared component. Each subdomain provides its own configuration (menu items,\n * service name, feature flags) and gets a consistent sidebar experience.\n *\n * Features:\n * - Resizable sidebar with localStorage persistence\n * - Collapsible icon mode with tooltips\n * - Service branding (icon + name) in header\n * - \"Back to Service Hub/Portal\" link (configurable)\n * - Grouped menu sections with optional section labels\n * - Admin-only menu items (role-gated)\n * - User avatar footer with optional logout dropdown\n * - Mobile-responsive with auto-collapse\n *\n * Usage:\n * import { createServiceLayout } from \"@shared/components/SharedSidebar\";\n * const MyLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n * // Then use <MyLayout>{children}</MyLayout> in your routes\n *\n * @module shared/components/SharedSidebar\n */\n\nimport React, { CSSProperties, useEffect, useRef, useState } from \"react\";\n\n/* ── Types ── */\n\nexport interface MenuItem {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n path: string;\n /** Optional badge text (e.g. count) */\n badge?: string | number;\n}\n\nexport interface MenuSection {\n /** Section label displayed above items */\n label?: string;\n /** Alias for label — used by DashboardLayout */\n title?: string;\n items: MenuItem[];\n adminOnly?: boolean;\n}\n\nexport interface SharedSidebarConfig {\n /** Display name shown in sidebar header */\n serviceName: string;\n /** Icon component rendered next to service name */\n serviceIcon: React.ReactNode;\n /** Primary navigation items */\n menuSections: MenuSection[];\n /** Show \"Back to Service Hub\" / \"Back to Portal\" link at top of nav */\n backLink?: {\n label: string;\n path: string;\n };\n /** localStorage key prefix for sidebar width persistence */\n storageKeyPrefix?: string;\n /** Default sidebar width in pixels (default: 260) */\n defaultWidth?: number;\n /** Minimum sidebar width in pixels (default: 200) */\n minWidth?: number;\n /** Maximum sidebar width in pixels (default: 480) */\n maxWidth?: number;\n}\n\nexport interface SharedSidebarProps {\n config: SharedSidebarConfig;\n /** Current route location */\n location: string;\n /** Navigate to a path */\n setLocation: (path: string) => void;\n /** Current user object (null if not authenticated) */\n user: { name?: string; email?: string; role?: string } | null;\n /** Logout handler */\n onLogout?: () => void;\n /** Whether sidebar is collapsed */\n isCollapsed: boolean;\n /** Toggle sidebar collapsed state */\n toggleSidebar: () => void;\n /** Whether user has admin role */\n isAdmin: boolean;\n /** Children rendered in SidebarInset main area */\n children: React.ReactNode;\n}\n\n/* ── Constants ── */\n\nconst ADMIN_ROLES = [\"enterprise_admin\", \"platform_admin\", \"admin\", \"superuser\"];\n\n/* ── Helpers ── */\n\nexport function isAdminRole(role?: string): boolean {\n return !!role && ADMIN_ROLES.includes(role);\n}\n\nexport function getInitials(name?: string): string {\n if (!name) return \"U\";\n return name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n}\n\n/**\n * Determines if a menu item is active based on current location.\n * Exact match for root paths, startsWith for nested paths.\n */\nexport function isMenuItemActive(itemPath: string, location: string, basePath?: string): boolean {\n if (basePath) {\n // For subdomain routes with a base path (e.g., /app/policy-deployment)\n if (itemPath === basePath) return location === basePath;\n return location.startsWith(itemPath);\n }\n // For root-level routes\n if (itemPath === \"/\") return location === \"/\";\n return location.startsWith(itemPath);\n}\n\n/* ── Resize Hook ── */\n\nexport function useSidebarResize(config: {\n storageKey: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}) {\n const [width, setWidth] = useState(() => {\n if (typeof window === \"undefined\") return config.defaultWidth;\n const saved = localStorage.getItem(config.storageKey);\n return saved ? parseInt(saved, 10) : config.defaultWidth;\n });\n const [isResizing, setIsResizing] = useState(false);\n const startX = useRef(0);\n const startWidth = useRef(config.defaultWidth);\n\n useEffect(() => {\n localStorage.setItem(config.storageKey, String(width));\n }, [width, config.storageKey]);\n\n useEffect(() => {\n if (!isResizing) return;\n const onMove = (e: MouseEvent) => {\n const newWidth = Math.min(\n config.maxWidth,\n Math.max(config.minWidth, startWidth.current + (e.clientX - startX.current))\n );\n setWidth(newWidth);\n };\n const onUp = () => {\n setIsResizing(false);\n };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"col-resize\";\n document.body.style.userSelect = \"none\";\n return () => {\n document.removeEventListener(\"mousemove\", onMove);\n document.removeEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n };\n }, [isResizing, config.minWidth, config.maxWidth]);\n\n const startResize = (e: React.MouseEvent) => {\n setIsResizing(true);\n startX.current = e.clientX;\n startWidth.current = width;\n };\n\n return { width, isResizing, startResize };\n}\n\n/* ── Export config type for subdomain use ── */\nexport type { SharedSidebarConfig as SidebarConfig };\n","/**\n * DashboardLayout — the canonical sidebar + header shell for every Oplytics\n * subdomain (the \"Policy Deployment\" gold-standard layout, consolidated).\n *\n * Built on the shadcn `Sidebar` primitives shipped from `@pablo2410/shared-ui/primitives`.\n * It renders an identical chrome across services:\n * - Collapsible icon sidebar with a brand header (icon + service name)\n * - \"← Service Hub\" link pinned to the top of the nav\n * - Grouped, optionally admin-gated menu sections\n * - Settings footer\n * - Sticky top header with a hierarchy-breadcrumb slot, enterprise badge,\n * reporting-toolbar slot, and a user menu\n *\n * The component is intentionally app-agnostic: it imports no app hooks\n * (`useAuth`, `useHierarchy`, router). Apps pass identity, navigation state,\n * and their hierarchy navigator / reporting toolbar in as props/slots.\n *\n * Oplytics dark theme: #0D1220 header, #1E2738 border, #8C34E9 accent.\n */\nimport { type ReactNode } from \"react\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n useSidebar,\n Avatar,\n AvatarFallback,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../primitives\";\nimport { portalUrl as buildPortalUrl } from \"../services/platformDomain\";\nimport { useIsMobile } from \"../primitives/useMobile\";\nimport { ArrowLeft, ExternalLink, LogOut, PanelLeft, Settings } from \"lucide-react\";\nimport { cn } from \"../utils/cn\";\nimport { type MenuItem, type MenuSection, isMenuItemActive } from \"./SharedSidebar\";\nimport { CrossServiceNav } from \"./CrossServiceNav\";\nimport type { CrossServiceLink } from \"../services/crossNav\";\nimport { HealthFooter } from \"./HealthFooter\";\nimport { EnterpriseDebugLabel } from \"./EnterpriseDebugLabel\";\n\n/* ── Re-export sidebar types ── */\nexport type { MenuItem, MenuSection };\n\n/* ── Types ── */\n\nexport interface DashboardLayoutUser {\n name?: string;\n email?: string;\n role?: string;\n avatarUrl?: string;\n /** The user's stable identifier (typically the JWT `openId` claim).\n * Optional — only used by the HealthFooter diagnostic strip. */\n openId?: string;\n /** The enterprise id from the JWT (`enterpriseId` claim). May be null\n * for platform_admin users who haven't selected an active enterprise.\n * Optional — only used by the HealthFooter diagnostic strip. */\n enterpriseId?: number | null;\n}\n\nexport interface DashboardLayoutEnterprise {\n /** The enterprise id the org-hierarchy is currently scoped to. Optional\n * — only used by the HealthFooter diagnostic strip; the visible header\n * badge only needs `name` (and optionally `code`). */\n id?: number;\n name: string;\n code?: string;\n}\n\nexport interface DashboardLayoutProps {\n /** Service/module name shown in the sidebar brand header (e.g. \"OEE Manager\"). */\n serviceName: string;\n /** Service icon (a lucide icon element), shown in the sidebar brand + header. */\n serviceIcon?: ReactNode;\n /** Primary navigation sections. */\n menuSections: MenuSection[];\n /** Admin-only navigation sections, rendered when `isAdmin` is true. */\n adminSections?: MenuSection[];\n /** Whether the current user may see `adminSections`. */\n isAdmin?: boolean;\n /** Current route (the app's router supplies this). */\n activePath: string;\n /** Navigate handler (the app's router supplies this). */\n onNavigate: (path: string) => void;\n /** Optional override for active-item detection. */\n isActive?: (itemPath: string, activePath: string) => boolean;\n /** URL for the \"← Service Hub\" link at the top of the nav (full-page nav). */\n serviceHubUrl?: string;\n /** URL for the \"Portal\" link in the header (right of the enterprise badge).\n * Omit to hide the link. Defaults to the platform portal root when not\n * explicitly disabled — pass `null` to hide it. */\n portalUrl?: string | null;\n /** Direct links to sibling services, rendered under the Service Hub link.\n * Build with `buildCrossServiceLinks` (services/crossNav) so provisioning\n * gating and the ?oh= hierarchy carry-over are handled consistently. */\n crossServiceLinks?: CrossServiceLink[];\n /** Footer \"Settings\" route. Omit to hide the footer. */\n settingsPath?: string;\n /** Authenticated user (drives the header user menu). */\n user?: DashboardLayoutUser | null;\n /** Sign-out handler invoked from the user menu. */\n onLogout?: () => void;\n /** \"User Settings\" target in the user menu (full-page nav). */\n userSettingsUrl?: string;\n /** App-provided hierarchy navigator rendered in the header breadcrumb slot. */\n hierarchyNavigator?: ReactNode;\n /** Selected enterprise — drives the header enterprise badge. */\n enterprise?: DashboardLayoutEnterprise | null;\n /** App-provided reporting toolbar rendered on the right of the header. */\n reportingToolbar?: ReactNode;\n /** Whether the sidebar starts expanded. Defaults to `!isMobile`. */\n defaultOpen?: boolean;\n /** Extra footer rendered fixed at the bottom (e.g. a debug/health bar).\n * Coexists with `showHealthFooter` — pass both if you want both. */\n footer?: ReactNode;\n /** When true AND the current user has `role === \"platform_admin\"`, the\n * shell renders the standardised platform-admin diagnostic strip\n * (`HealthFooter`) fixed at the bottom of the viewport. Subdomains\n * opt in with one prop and inherit any future improvements to the\n * strip without code changes. */\n showHealthFooter?: boolean;\n /**\n * Deployed-build info shown in the HealthFooter diagnostic strip\n * (build: version · commit · date). The consuming app supplies these from\n * its own build-time constants (e.g. Vite `define` of __BUILD_DATE__ /\n * __COMMIT_HASH__) and passes them down — globals can't be read from inside\n * this pre-compiled package, so they must arrive as props.\n */\n buildDate?: string;\n commitHash?: string;\n buildVersion?: string;\n /** Main content. */\n children: ReactNode;\n}\n\n/* Defaults resolve the platform domain at render time (see platformDomain). */\nconst defaultServiceHubUrl = () => buildPortalUrl(\"/app\");\nconst defaultUserSettingsUrl = () => buildPortalUrl(\"/account\");\nconst defaultPortalUrl = () => buildPortalUrl(\"/\");\n\n/* ── Header: user menu ── */\n\nfunction UserMenu({\n user,\n onLogout,\n userSettingsUrl = defaultUserSettingsUrl(),\n enterprise,\n}: {\n user: DashboardLayoutUser;\n onLogout?: () => void;\n userSettingsUrl?: string;\n /** Active enterprise, used to render the TEMP diagnostic chip inside the menu. */\n enterprise?: { id?: number | null; name?: string | null } | null;\n}) {\n const initials = (user.name || \"U\")\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button className=\"flex items-center gap-2 rounded-md px-2 py-1 hover:bg-[#1E2738] transition-colors outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\">\n <Avatar className=\"h-7 w-7 border border-[#2A2A3E]\">\n <AvatarFallback className=\"text-[10px] font-medium bg-[#8C34E9]/20 text-[#C084FC]\">\n {initials}\n </AvatarFallback>\n </Avatar>\n <span className=\"hidden md:block text-xs text-[#E2E8F0] truncate max-w-[100px]\">\n {user.name || \"User\"}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-48 bg-[#0D1220] border-[#1E2738]\">\n <div className=\"px-3 py-2 border-b border-[#1E2738]\">\n <p className=\"text-xs font-medium text-[#E2E8F0] truncate\">{user.name}</p>\n <p className=\"text-[10px] text-[#596475] truncate\">{user.role}</p>\n </div>\n {/* TEMP DIAGNOSTIC — remove once subdomain enterprise-data issue is fixed.\n Lives inside the user menu (below the name, above User Settings) to keep\n it out of the header bar. */}\n <div className=\"px-2 py-2 border-b border-[#1E2738]\">\n <EnterpriseDebugLabel\n selectionEnterpriseId={enterprise?.id ?? null}\n cookieEnterpriseName={enterprise?.name ?? null}\n />\n </div>\n <DropdownMenuItem\n onClick={() => {\n window.location.href = userSettingsUrl;\n }}\n className=\"text-xs text-[#E2E8F0] hover:bg-[#1E2738] cursor-pointer focus:bg-[#1E2738]\"\n >\n <Settings className=\"h-3.5 w-3.5 mr-2 text-[#8890A0]\" />\n User Settings\n </DropdownMenuItem>\n <DropdownMenuSeparator className=\"bg-[#1E2738]\" />\n <DropdownMenuItem\n onClick={() => onLogout?.()}\n className=\"text-xs text-[#EF4444] hover:bg-[#EF4444]/10 cursor-pointer focus:bg-[#EF4444]/10 focus:text-[#EF4444]\"\n >\n <LogOut className=\"h-3.5 w-3.5 mr-2\" />\n Sign Out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ── Header: enterprise badge ── */\n\nfunction EnterpriseBadge({ enterprise }: { enterprise?: DashboardLayoutEnterprise | null }) {\n if (!enterprise) return null;\n return (\n <div className=\"hidden lg:flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#8C34E9]/10 border border-[#8C34E9]/20\">\n <div className=\"h-4 w-4 rounded-sm bg-[#8C34E9]/30 flex items-center justify-center\">\n <span className=\"text-[8px] font-bold text-[#C084FC]\">\n {enterprise.name.charAt(0).toUpperCase()}\n </span>\n </div>\n <span className=\"text-[10px] font-medium text-[#C084FC] truncate max-w-[80px]\">\n {enterprise.code || enterprise.name}\n </span>\n </div>\n );\n}\n\n/* ── Sidebar nav section ── */\n\nfunction NavSection({\n section,\n marginTop,\n isCollapsed,\n resolveActive,\n onNavigate,\n}: {\n section: MenuSection;\n marginTop?: boolean;\n isCollapsed: boolean;\n resolveActive: (path: string) => boolean;\n onNavigate: (path: string) => void;\n}) {\n const label = section.title ?? section.label;\n return (\n <>\n {label && (\n <div className={cn(\"px-4 py-2\", marginTop && \"mt-2\")}>\n {!isCollapsed && (\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {label}\n </span>\n )}\n </div>\n )}\n <SidebarMenu className=\"px-2 py-1\">\n {section.items.map((item: MenuItem) => {\n const active = resolveActive(item.path);\n const Icon = item.icon;\n return (\n <SidebarMenuItem key={item.path}>\n <SidebarMenuButton\n isActive={active}\n onClick={() => onNavigate(item.path)}\n tooltip={item.label}\n className=\"h-10 transition-all font-normal\"\n >\n {Icon && <Icon className={cn(\"h-4 w-4\", active && \"text-primary\")} />}\n <span>{item.label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n })}\n </SidebarMenu>\n </>\n );\n}\n\n/* ── Inner shell (runs inside SidebarProvider, so it can call useSidebar) ── */\n\nfunction DashboardShell({\n serviceName,\n serviceIcon,\n menuSections,\n adminSections,\n isAdmin = false,\n activePath,\n onNavigate,\n isActive,\n serviceHubUrl = defaultServiceHubUrl(),\n portalUrl = defaultPortalUrl(),\n crossServiceLinks,\n settingsPath,\n user,\n onLogout,\n userSettingsUrl,\n hierarchyNavigator,\n enterprise,\n reportingToolbar,\n footer,\n showHealthFooter,\n buildDate,\n commitHash,\n buildVersion,\n children,\n}: DashboardLayoutProps) {\n const { toggleSidebar, state } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n const navigate = onNavigate;\n const resolveActive = (path: string) =>\n isActive ? isActive(path, activePath) : isMenuItemActive(path, activePath);\n\n return (\n <>\n <Sidebar collapsible=\"icon\" className=\"border-r-0\">\n <SidebarHeader className=\"h-16 justify-center\">\n <div className=\"flex items-center gap-3 px-2 transition-all w-full\">\n <button\n onClick={toggleSidebar}\n className=\"h-8 w-8 flex items-center justify-center hover:bg-accent rounded-lg transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring shrink-0\"\n aria-label=\"Toggle navigation\"\n >\n <PanelLeft className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n {!isCollapsed ? (\n <div className=\"flex items-center gap-2 min-w-0\">\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0\">{serviceIcon}</span>\n )}\n <span className=\"font-semibold tracking-tight truncate\">\n {serviceName}\n </span>\n </div>\n ) : null}\n </div>\n </SidebarHeader>\n\n <SidebarContent className=\"gap-0\">\n {/* Service Hub — pinned to the top of the nav */}\n {serviceHubUrl && (\n <SidebarMenu className=\"px-2 pt-2 pb-1\">\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => {\n window.location.href = serviceHubUrl;\n }}\n tooltip=\"Back to Service Hub\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span>Service Hub</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n\n {/* Cross-service links — jump straight to a sibling service */}\n <CrossServiceNav links={crossServiceLinks ?? []} />\n\n {/* Primary navigation */}\n {menuSections.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `section-${i}`}\n section={section}\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n\n {/* Admin-only navigation */}\n {isAdmin &&\n adminSections?.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `admin-${i}`}\n section={section}\n marginTop\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n </SidebarContent>\n\n {settingsPath && (\n <SidebarFooter className=\"p-2\">\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => navigate(settingsPath)}\n tooltip=\"Settings\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <Settings className=\"h-4 w-4\" />\n <span>Settings</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n )}\n </Sidebar>\n\n <SidebarInset>\n <header\n className={cn(\n \"flex items-center justify-between h-14 px-4\",\n \"bg-[#0D1220]/95 border-b border-[#1E2738]\",\n \"backdrop-blur supports-[backdrop-filter]:backdrop-blur\",\n \"sticky top-0 z-40\"\n )}\n >\n {/* LEFT: mobile trigger + service icon + hierarchy breadcrumb */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <SidebarTrigger className=\"h-9 w-9 rounded-lg bg-background shrink-0 md:hidden\" />\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0 hidden sm:block\">{serviceIcon}</span>\n )}\n {hierarchyNavigator && (\n <div className=\"min-w-0 overflow-x-auto scrollbar-none\">{hierarchyNavigator}</div>\n )}\n </div>\n\n {/* RIGHT: reporting toolbar + enterprise badge + portal link + user menu */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {reportingToolbar}\n <EnterpriseBadge enterprise={enterprise} />\n {portalUrl && (\n <a\n href={portalUrl}\n className=\"hidden md:flex items-center gap-1 px-2 py-1 rounded-md text-[10px] text-[#8890A0] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors\"\n title=\"Back to Portal\"\n >\n <ExternalLink className=\"h-3 w-3\" />\n <span>Portal</span>\n </a>\n )}\n <div className=\"w-px h-6 bg-[#1E2738] hidden md:block\" />\n {user && (\n <UserMenu\n user={user}\n onLogout={onLogout}\n userSettingsUrl={userSettingsUrl}\n enterprise={enterprise}\n />\n )}\n </div>\n </header>\n\n <main className=\"flex-1 p-4 md:p-6 pb-14\">{children}</main>\n </SidebarInset>\n\n {footer}\n\n {/* Standardised platform-admin diagnostic strip — opt-in via prop. */}\n {showHealthFooter && user?.role === \"platform_admin\" && (\n <HealthFooter\n user={user}\n enterprise={enterprise}\n buildDate={buildDate}\n commitHash={commitHash}\n buildVersion={buildVersion}\n />\n )}\n </>\n );\n}\n\n/* ── Public component ── */\n\nexport function DashboardLayout(props: DashboardLayoutProps) {\n const isMobile = useIsMobile();\n return (\n <SidebarProvider defaultOpen={props.defaultOpen ?? !isMobile}>\n <DashboardShell {...props} />\n </SidebarProvider>\n );\n}\n","/**\n * CrossServiceNav — sidebar block of direct links to sibling Oplytics\n * services, rendered directly under the \"← Service Hub\" back link.\n *\n * Built on the same Sidebar primitives as the rest of the nav so it works\n * both inside the shared DashboardLayout shell and in bespoke sidebars\n * (e.g. Policy Deployment) — any consumer inside a <SidebarProvider>.\n *\n * Link entries come from `buildCrossServiceLinks` (services/crossNav), which\n * owns provisioning/gating and appends the ?oh= hierarchy selection param.\n * Navigation is a full page load, matching the Service Hub link.\n */\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"../primitives\";\nimport {\n ClipboardCheck,\n Gauge,\n LayoutGrid,\n Target,\n ExternalLink,\n type LucideIcon,\n} from \"lucide-react\";\nimport type { CrossServiceLink } from \"../services/crossNav\";\n\n/** Catalog icon names → lucide components for the live core services. */\nconst ICONS: Record<string, LucideIcon> = {\n Target,\n LayoutGrid,\n Gauge,\n ClipboardCheck,\n};\n\nexport interface CrossServiceNavProps {\n /** Display-ready links from buildCrossServiceLinks. Renders nothing when empty. */\n links: CrossServiceLink[];\n /** Section label above the links (hidden when the sidebar is collapsed). */\n label?: string;\n}\n\nexport function CrossServiceNav({\n links,\n label = \"Services\",\n}: CrossServiceNavProps) {\n const { state } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n if (links.length === 0) return null;\n\n return (\n <>\n {!isCollapsed && (\n <div className=\"px-4 pt-2\">\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {label}\n </span>\n </div>\n )}\n <SidebarMenu className=\"px-2 py-1\">\n {links.map((link) => {\n const Icon = ICONS[link.icon] ?? ExternalLink;\n return (\n <SidebarMenuItem key={link.slug}>\n <SidebarMenuButton\n onClick={() => {\n window.location.href = link.url;\n }}\n tooltip={link.name}\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <Icon className=\"h-4 w-4\" />\n <span>{link.name}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n })}\n </SidebarMenu>\n </>\n );\n}\n","/**\n * HealthFooter — thin fixed strip at the bottom of the viewport that prints\n * the current platform-admin diagnostic context. Used to verify the\n * end-to-end auth + hierarchy resolution: JWT role, JWT-stamped enterprise id,\n * the enterprise the hierarchy actually selected, and the openId of the\n * signed-in user.\n *\n * Ported from oplytics-policy-deployment/client/src/components/PolicyDeploymentLayout.tsx\n * so every subdomain that adopts the canonical shell gets the same strip\n * automatically. Use `DashboardLayout` / `OplyticsServiceShell`'s\n * `showHealthFooter` prop to auto-render this for platform_admin only —\n * or render it manually via the existing `footer` slot for full control.\n *\n * role: platform_admin jwt.eid: 30002 hierarchy.eid: 30002\n * enterprise: Lincoln Electric user: KxULoAniQat2Vm7Z9Ds32m\n */\nimport type { CSSProperties } from \"react\";\n\n// Build stamp — injected at each app's build time via Vite `define`\n// (see the app's vite.config.ts). Lets the diagnostic strip show exactly which\n// build/commit is deployed and when it was built — so you can tell at a glance\n// whether the live site is running the latest code.\ndeclare const __BUILD_DATE__: string;\ndeclare const __COMMIT_HASH__: string;\ndeclare const __APP_VERSION__: string;\n\n/** Format an ISO build timestamp as a compact \"9 Jul 07:37Z\" (UTC). */\nfunction formatBuildDate(iso?: string): string | undefined {\n if (!iso) return undefined;\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return undefined;\n const day = d.getUTCDate();\n const month = d.toLocaleString(\"en-GB\", { month: \"short\", timeZone: \"UTC\" });\n const hh = String(d.getUTCHours()).padStart(2, \"0\");\n const mm = String(d.getUTCMinutes()).padStart(2, \"0\");\n return `${day} ${month} ${hh}:${mm}Z`;\n}\n\nexport interface HealthFooterUser {\n /** JWT role string (e.g. \"platform_admin\"). */\n role?: string;\n /** The user's stable identifier (typically the JWT `openId` claim). */\n openId?: string;\n /** The enterprise id from the JWT (`enterpriseId` claim). May be null\n * for platform_admin users who haven't selected an active enterprise. */\n enterpriseId?: number | null;\n}\n\nexport interface HealthFooterEnterprise {\n /** The enterprise id the org-hierarchy is currently scoped to. */\n id?: number;\n /** Human-readable name of the scoped enterprise. */\n name?: string;\n}\n\nexport interface HealthFooterProps {\n user?: HealthFooterUser | null;\n enterprise?: HealthFooterEnterprise | null;\n /** Optional extra CSS class for the container. */\n className?: string;\n /** Optional inline style override (z-index, etc.). */\n style?: CSSProperties;\n /** Override the build version (default: build-time __APP_VERSION__). */\n buildVersion?: string;\n /** Override the build/deploy timestamp, ISO (default: build-time __BUILD_DATE__). */\n buildDate?: string;\n /** Override the deployed commit hash (default: build-time __COMMIT_HASH__). */\n commitHash?: string;\n}\n\nexport function HealthFooter({\n user,\n enterprise,\n className,\n style,\n buildVersion,\n buildDate,\n commitHash,\n}: HealthFooterProps) {\n const version =\n buildVersion ?? (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit =\n commitHash ?? (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ?? (typeof __BUILD_DATE__ !== \"undefined\" ? __BUILD_DATE__ : undefined),\n );\n const buildStamp = [version && `v${version}`, commit, builtAt].filter(Boolean).join(\" · \");\n\n return (\n <div\n className={\n \"fixed bottom-0 left-0 right-0 h-8 bg-zinc-900 border-t border-zinc-800 \" +\n \"flex items-center px-4 gap-6 text-[11px] text-zinc-400 z-50 font-mono\" +\n (className ? ` ${className}` : \"\")\n }\n style={style}\n data-slot=\"health-footer\"\n >\n <span>\n role: <span className=\"text-zinc-200\">{user?.role ?? \"–\"}</span>\n </span>\n <span>\n jwt.eid:{\" \"}\n <span className=\"text-zinc-200\">{user?.enterpriseId ?? \"null\"}</span>\n </span>\n <span>\n hierarchy.eid:{\" \"}\n <span className=\"text-zinc-200\">{enterprise?.id ?? \"–\"}</span>\n </span>\n <span>\n enterprise:{\" \"}\n <span className=\"text-zinc-200\">{enterprise?.name ?? \"–\"}</span>\n </span>\n <span>\n user: <span className=\"text-zinc-200\">{user?.openId ?? \"–\"}</span>\n </span>\n {buildStamp && (\n <span className=\"ml-auto text-zinc-500\" title=\"Deployed build (version · commit · build date UTC)\">\n build: <span className=\"text-zinc-300\">{buildStamp}</span>\n </span>\n )}\n </div>\n );\n}\n","/**\n * EnterpriseDebugLabel — TEMPORARY DIAGNOSTIC chip.\n *\n * ⚠️ This is a problem-fixing aid, NOT permanent UI. Remove once the\n * subdomain \"wrong enterprise data\" issue is resolved.\n *\n * Purpose: make it plainly visible, on every page (portal + all subdomains),\n * WHICH enterprise the loaded data actually belongs to — and surface the\n * desync that causes subdomains to show the wrong enterprise's data.\n *\n * It compares two ids that are supposed to agree:\n * • cookie — the active-enterprise cookie (ACTIVE_ENTERPRISE_COOKIE), the\n * single cross-subdomain source of truth that the backend uses\n * to scope ALL data (via applyEnterpriseOverride).\n * • sel — the UI hierarchy selection's enterprise id.\n * When they diverge, the chip turns red and shows \"≠\" — that's the bug.\n *\n * Self-contained: reads the cookie directly and reads the hierarchy selection\n * defensively, so it can render even on pages mounted outside a hierarchy\n * provider (it just omits the selection comparison in that case).\n */\nimport { useEffect, useState } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { readActiveEnterpriseCookie } from \"../hierarchy\";\n\nexport interface EnterpriseDebugLabelProps {\n /**\n * The selection's enterprise id, if the host app can supply it (e.g. from\n * useOrgHierarchy().selection.enterprise?.id). Optional — when omitted, only\n * the cookie id is shown. Passing it enables the cookie-vs-selection mismatch\n * check, which is the most useful diagnostic.\n */\n selectionEnterpriseId?: number | null;\n /**\n * Optional enterprise name to show alongside the cookie id for readability.\n */\n cookieEnterpriseName?: string | null;\n className?: string;\n}\n\nexport function EnterpriseDebugLabel({\n selectionEnterpriseId,\n cookieEnterpriseName,\n className,\n}: EnterpriseDebugLabelProps) {\n // Read the cookie on the client. Re-read on focus/visibility so a switch made\n // in another tab/subdomain is reflected without a manual reload.\n const [cookieId, setCookieId] = useState<string | null>(null);\n useEffect(() => {\n const read = () => setCookieId(readActiveEnterpriseCookie());\n read();\n window.addEventListener(\"focus\", read);\n document.addEventListener(\"visibilitychange\", read);\n return () => {\n window.removeEventListener(\"focus\", read);\n document.removeEventListener(\"visibilitychange\", read);\n };\n }, []);\n\n const cookieNum = cookieId !== null && cookieId !== \"\" ? Number(cookieId) : null;\n const hasSelection = selectionEnterpriseId !== undefined && selectionEnterpriseId !== null;\n const mismatch = hasSelection && cookieNum !== null && cookieNum !== selectionEnterpriseId;\n const noActive = cookieNum === null;\n\n return (\n <div\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md border font-mono text-[10px] leading-none whitespace-nowrap\",\n mismatch\n ? \"bg-[#EF4444]/15 border-[#EF4444]/40 text-[#FCA5A5]\"\n : noActive\n ? \"bg-[#F59E0B]/15 border-[#F59E0B]/40 text-[#FCD34D]\"\n : \"bg-[#10B981]/10 border-[#10B981]/30 text-[#6EE7B7]\",\n className\n )}\n title=\"TEMP DIAGNOSTIC: enterprise the loaded data is scoped to (cookie) vs. the UI hierarchy selection. Remove once subdomain data issue is fixed.\"\n >\n <span aria-hidden>🔍</span>\n {noActive ? (\n <span>no active enterprise (cookie unset)</span>\n ) : (\n <>\n <span>\n data ENT #{cookieNum}\n {cookieEnterpriseName ? ` · ${cookieEnterpriseName}` : \"\"}\n </span>\n {hasSelection && (\n <span>\n {mismatch ? \" ⚠ ≠ sel #\" : \" · sel #\"}\n {selectionEnterpriseId}\n </span>\n )}\n </>\n )}\n </div>\n );\n}\n","/**\n * DashboardLayoutSkeleton — loading skeleton for DashboardLayout.\n *\n * Shows a sidebar + content skeleton while auth state is loading.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\n\nfunction Skeleton({ className }: { className?: string }) {\n return (\n <div\n className={cn(\n \"animate-pulse rounded-md bg-[#1E2738]\",\n className\n )}\n />\n );\n}\n\nexport interface DashboardLayoutSkeletonProps {\n className?: string;\n}\n\nexport function DashboardLayoutSkeleton({ className }: DashboardLayoutSkeletonProps) {\n return (\n <div className={cn(\"flex min-h-screen bg-[#0A0E1A]\", className)}>\n {/* Sidebar skeleton */}\n <div className=\"w-[260px] border-r border-[#1E2738] bg-[#0D1220] p-4 space-y-6\">\n {/* Logo area */}\n <div className=\"flex items-center gap-3 px-2\">\n <Skeleton className=\"h-8 w-8 rounded-md\" />\n <Skeleton className=\"h-4 w-24\" />\n </div>\n\n {/* Menu items */}\n <div className=\"space-y-2 px-2\">\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n </div>\n\n {/* Section divider */}\n <div className=\"px-2\">\n <Skeleton className=\"h-3 w-16 mb-3\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg mt-2\" />\n </div>\n\n {/* User profile area at bottom */}\n <div className=\"absolute bottom-4 left-4 right-4\">\n <div className=\"flex items-center gap-3 px-1\">\n <Skeleton className=\"h-8 w-8 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"h-2 w-32\" />\n </div>\n </div>\n </div>\n </div>\n\n {/* Main content skeleton */}\n <div className=\"flex-1 p-6 space-y-4\">\n {/* Page header */}\n <div className=\"flex items-center justify-between\">\n <Skeleton className=\"h-8 w-48 rounded-lg\" />\n <Skeleton className=\"h-9 w-24 rounded-lg\" />\n </div>\n\n {/* Stats cards */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n </div>\n\n {/* Main content area */}\n <Skeleton className=\"h-64 rounded-xl\" />\n\n {/* Table-like area */}\n <div className=\"space-y-2\">\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n </div>\n </div>\n </div>\n );\n}\n","/**\n * SharedPageHeader — standard page header for all Oplytics subdomains.\n *\n * Displays: page title, optional subtitle, optional breadcrumbs,\n * optional action buttons, and optional HierarchyNavigator.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent, #E2E8F0 text, #8890A0 muted\n */\nimport { cn } from \"../utils/cn\";\nimport { ChevronLeft } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\nexport interface SharedPageHeaderProps {\n /** Page title */\n title: string;\n /** Optional subtitle / description */\n subtitle?: string;\n /** Optional icon to display before the title */\n icon?: ReactNode;\n /** Optional back button handler — shows a back arrow when provided */\n onBack?: () => void;\n /** Optional action buttons to render on the right side */\n actions?: ReactNode;\n /** Optional breadcrumb trail (e.g. HierarchyNavigator) */\n breadcrumbs?: ReactNode;\n /** Whether to show a bottom border (default: true) */\n showBorder?: boolean;\n /** Compact mode — smaller spacing and text (default: false) */\n compact?: boolean;\n /** Additional CSS classes */\n className?: string;\n /** Children rendered below the header row */\n children?: ReactNode;\n}\n\nexport function SharedPageHeader({\n title,\n subtitle,\n icon,\n onBack,\n actions,\n breadcrumbs,\n showBorder = true,\n compact = false,\n className,\n children,\n}: SharedPageHeaderProps) {\n return (\n <div\n className={cn(\n \"w-full\",\n showBorder && \"border-b border-[#1E2738]\",\n compact ? \"px-4 py-3\" : \"px-6 py-4\",\n className\n )}\n >\n {/* Breadcrumbs row */}\n {breadcrumbs && (\n <div className={cn(\"mb-2\", compact && \"mb-1\")}>\n {breadcrumbs}\n </div>\n )}\n\n {/* Main header row */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {/* Back button */}\n {onBack && (\n <button\n onClick={onBack}\n className={cn(\n \"shrink-0 p-1.5 rounded-md\",\n \"text-[#8890A0] hover:text-[#E2E8F0] hover:bg-[#1E2738]\",\n \"transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n aria-label=\"Go back\"\n >\n <ChevronLeft className={compact ? \"h-4 w-4\" : \"h-5 w-5\"} />\n </button>\n )}\n\n {/* Icon */}\n {icon && (\n <div className=\"shrink-0 text-[#8C34E9]\">\n {icon}\n </div>\n )}\n\n {/* Title + subtitle */}\n <div className=\"min-w-0\">\n <h1\n className={cn(\n \"font-semibold text-[#E2E8F0] truncate\",\n \"font-[Montserrat,sans-serif]\",\n compact ? \"text-lg\" : \"text-xl\"\n )}\n >\n {title}\n </h1>\n {subtitle && (\n <p\n className={cn(\n \"text-[#8890A0] truncate mt-0.5\",\n \"font-[Space_Grotesk,sans-serif]\",\n compact ? \"text-xs\" : \"text-sm\"\n )}\n >\n {subtitle}\n </p>\n )}\n </div>\n </div>\n\n {/* Action buttons */}\n {actions && (\n <div className=\"shrink-0 flex items-center gap-2\">\n {actions}\n </div>\n )}\n </div>\n\n {/* Optional children below header */}\n {children && (\n <div className={cn(\"mt-3\", compact && \"mt-2\")}>\n {children}\n </div>\n )}\n </div>\n );\n}\n","/**\n * SharedFooter — standard footer for all Oplytics subdomains.\n *\n * Displays: Oplytics branding, service links, legal links, copyright.\n * Consistent across all subdomains.\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #8890A0 text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport { platformUrl } from \"../services/platformDomain\";\nimport type { ReactNode } from \"react\";\n\ndeclare const __APP_VERSION__: string;\n\n/* ── Service definitions ── */\n\nexport interface FooterService {\n name: string;\n slug: string;\n /** Local route within the portal (e.g. /services/sqdcp) */\n localRoute: string;\n /** External URL for the subdomain (e.g. https://sqdcp.oplyticsdigital.net) */\n externalUrl?: string;\n}\n\n/** Standard Oplytics service list */\nexport const FOOTER_SERVICES: FooterService[] = [\n { name: \"SQDCP\", slug: \"sqdcp\", localRoute: \"/services/sqdcp\", externalUrl: platformUrl(\"sqdcp\") },\n { name: \"OEE Manager\", slug: \"oee-manager\", localRoute: \"/services/oee-manager\", externalUrl: platformUrl(\"oeemanager\") },\n { name: \"Policy Deployment\", slug: \"policy-deployment\", localRoute: \"/services/policy-deployment\", externalUrl: platformUrl(\"policy\") },\n { name: \"Action Manager\", slug: \"action-manager\", localRoute: \"/services/action-manager\", externalUrl: platformUrl(\"actionmanager\") },\n { name: \"Safety Manager\", slug: \"safety-manager\", localRoute: \"/services/safety-manager\" },\n { name: \"Connect\", slug: \"connect\", localRoute: \"/services/connect\", externalUrl: platformUrl(\"connect\") },\n];\n\n/* ── Footer props ── */\n\nexport interface SharedFooterProps {\n /** Override the default service list */\n services?: FooterService[];\n /** Show pricing link (default: true) */\n showPricing?: boolean;\n /** Show sign-in link (default: true) */\n showSignIn?: boolean;\n /** Handler for service link clicks — receives the service */\n onServiceClick?: (service: FooterService, e: React.MouseEvent) => void;\n /** Handler for pricing link click */\n onPricingClick?: (e: React.MouseEvent) => void;\n /** Handler for sign-in link click */\n onSignInClick?: (e: React.MouseEvent) => void;\n /** Additional content to render in the footer */\n children?: ReactNode;\n /** CSS class for the footer container */\n className?: string;\n /** Whether to use external URLs (subdomain links) or local routes */\n useExternalUrls?: boolean;\n /** Current year override (default: auto) */\n year?: number;\n}\n\n/* ── Component ── */\n\nexport function SharedFooter({\n services = FOOTER_SERVICES,\n showPricing = true,\n showSignIn = true,\n onServiceClick,\n onPricingClick,\n onSignInClick,\n children,\n className,\n useExternalUrls = false,\n year,\n}: SharedFooterProps) {\n const currentYear = year ?? new Date().getFullYear();\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738]\",\n \"bg-[#0D1220] text-[#8890A0]\",\n \"font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n <div className=\"max-w-7xl mx-auto px-6 py-10\">\n {/* Top section: Logo + columns */}\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-8 mb-8\">\n {/* Brand column */}\n <div className=\"md:col-span-1\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div\n className=\"w-7 h-7 rounded-full flex items-center justify-center\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold text-xs\">O</span>\n </div>\n <span className=\"text-[#E2E8F0] font-semibold text-sm font-[Montserrat,sans-serif]\">\n Oplytics.digital\n </span>\n </div>\n <p className=\"text-xs text-[#596475] leading-relaxed\">\n Operational intelligence platform for manufacturing excellence.\n </p>\n </div>\n\n {/* Services column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Services\n </h4>\n <ul className=\"space-y-2\">\n {services.map((service) => (\n <li key={service.slug}>\n <a\n href={useExternalUrls && service.externalUrl ? service.externalUrl : service.localRoute}\n onClick={onServiceClick ? (e) => onServiceClick(service, e) : undefined}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n {service.name}\n </a>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Company column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Company\n </h4>\n <ul className=\"space-y-2\">\n {showPricing && (\n <li>\n <a\n href=\"/pricing\"\n onClick={onPricingClick}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Pricing\n </a>\n </li>\n )}\n <li>\n <a\n href=\"/about\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n About\n </a>\n </li>\n <li>\n <a\n href=\"/contact\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Contact\n </a>\n </li>\n </ul>\n </div>\n\n {/* Legal column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Legal\n </h4>\n <ul className=\"space-y-2\">\n <li>\n <a\n href=\"/privacy\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Privacy Policy\n </a>\n </li>\n <li>\n <a\n href=\"/terms\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Terms of Service\n </a>\n </li>\n </ul>\n {showSignIn && (\n <div className=\"mt-4\">\n <a\n href=\"/login\"\n onClick={onSignInClick}\n className=\"text-xs text-[#8C34E9] hover:text-[#A855F7] transition-colors font-medium\"\n >\n Sign In\n </a>\n </div>\n )}\n </div>\n </div>\n\n {/* Custom content */}\n {children}\n\n {/* Bottom bar */}\n <div className=\"pt-6 border-t border-[#1E2738] flex flex-col sm:flex-row items-center justify-between gap-3\">\n <p className=\"text-xs text-[#596475]\">\n &copy; {currentYear} Oplytics.digital. All rights reserved.\n </p>\n <p className=\"text-xs text-[#3E4A5C]\">\n Built for manufacturing excellence\n </p>\n {typeof __APP_VERSION__ !== \"undefined\" && (\n <p className=\"text-xs text-[#3E4A5C]\">\n Platform v{__APP_VERSION__}\n </p>\n )}\n </div>\n </div>\n </footer>\n );\n}\n","/**\n * ServiceFooter — slim status bar for all Oplytics subdomain services.\n *\n * ~36px horizontal strip: [Logo + Tagline] [Health Monitor] [Copyright + Version]\n * Health monitor is visible only when showHealthMonitor is true (typically platform_admin).\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #596475/#3E4A5C text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\n\ndeclare const __APP_VERSION__: string;\ndeclare const __BUILD_DATE__: string;\ndeclare const __COMMIT_HASH__: string;\n\nexport interface ServiceFooterProps {\n /** Enterprise ID for health monitor */\n enterpriseId?: number | null;\n /** Enterprise display name */\n enterpriseName?: string;\n /** Current user's role */\n userRole?: string;\n /** Current user's email */\n userEmail?: string;\n /** Deployment environment */\n environment?: \"production\" | \"staging\" | \"development\";\n /** Build version / commit hash */\n buildVersion?: string;\n /**\n * Build/deploy timestamp — when the currently-served bundle was built.\n * ISO string (e.g. \"2026-07-09T07:37:31Z\"). Falls back to the build-time\n * __BUILD_DATE__ constant. Rendered as a short date+time so you can tell at\n * a glance whether the deployed code is the latest (root-cause aid).\n */\n buildDate?: string;\n /** Short commit hash of the deployed build (e.g. \"c3af23f\"). Falls back to __COMMIT_HASH__. */\n commitHash?: string;\n /** Show health monitor strip (default: false) */\n showHealthMonitor?: boolean;\n /** CSS class for the footer container */\n className?: string;\n}\n\n/** Format an ISO build timestamp as a compact \"9 Jul 07:37\" (UTC) for the footer. */\nfunction formatBuildDate(iso?: string): string | undefined {\n if (!iso) return undefined;\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return undefined;\n const day = d.getUTCDate();\n const month = d.toLocaleString(\"en-GB\", { month: \"short\", timeZone: \"UTC\" });\n const hh = String(d.getUTCHours()).padStart(2, \"0\");\n const mm = String(d.getUTCMinutes()).padStart(2, \"0\");\n return `${day} ${month} ${hh}:${mm}Z`;\n}\n\nconst ENV_LABELS: Record<string, { label: string; color: string }> = {\n production: { label: \"prod\", color: \"#10b981\" },\n staging: { label: \"stg\", color: \"#f59e0b\" },\n development: { label: \"dev\", color: \"#8C34E9\" },\n};\n\nexport function ServiceFooter({\n enterpriseId,\n enterpriseName,\n userRole,\n userEmail,\n environment,\n buildVersion,\n buildDate,\n commitHash,\n showHealthMonitor = false,\n className,\n}: ServiceFooterProps) {\n const currentYear = new Date().getFullYear();\n const env = environment ? ENV_LABELS[environment] : null;\n const version = buildVersion ?? (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit = commitHash ?? (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ?? (typeof __BUILD_DATE__ !== \"undefined\" ? __BUILD_DATE__ : undefined),\n );\n // A single \"build stamp\" combining version + commit + build date, e.g.\n // \"v.0.9.2 · c3af23f · 9 Jul 07:37Z\". Any missing piece is simply omitted.\n const buildStamp = [version && `v.${version}`, commit, builtAt]\n .filter(Boolean)\n .join(\" · \");\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738] bg-[#0D1220]\",\n \"flex items-center justify-between px-4\",\n \"h-9 min-h-[36px] shrink-0\",\n \"font-[Montserrat,sans-serif]\",\n className,\n )}\n >\n {/* Left: Logo + Tagline */}\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"w-5 h-5 rounded-full flex items-center justify-center shrink-0\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold\" style={{ fontSize: 9 }}>O</span>\n </div>\n <span className=\"text-[10px] text-[#596475] whitespace-nowrap hidden sm:inline\">\n Operational Excellence. One Digital Platform.\n </span>\n </div>\n\n {/* Center: Health Monitor (admin only) */}\n {showHealthMonitor && (\n <div className=\"flex items-center gap-1.5 text-[10px] text-[#3E4A5C] min-w-0 overflow-hidden\">\n {enterpriseName && (\n <>\n <span className=\"whitespace-nowrap\">\n <span className=\"text-[#596475]\">{enterpriseName}</span>\n {enterpriseId != null && (\n <span className=\"text-[#3E4A5C]\"> ({enterpriseId})</span>\n )}\n </span>\n <Separator />\n </>\n )}\n {userEmail && (\n <>\n <span className=\"whitespace-nowrap text-[#596475]\">{userEmail}</span>\n {userRole && (\n <span className=\"text-[#3E4A5C]\"> ({userRole})</span>\n )}\n <Separator />\n </>\n )}\n {env && (\n <>\n <span className=\"whitespace-nowrap\">\n ENV:{\" \"}\n <span style={{ color: env.color }} className=\"font-semibold\">\n {env.label}\n </span>\n </span>\n <Separator />\n </>\n )}\n {buildStamp && (\n <span className=\"whitespace-nowrap text-[#3E4A5C] font-mono\" title=\"Deployed build\">\n {buildStamp}\n </span>\n )}\n </div>\n )}\n\n {/* Right: Copyright + build stamp (when health monitor hidden) */}\n <div className=\"flex items-center gap-2 text-[10px] text-[#3E4A5C] whitespace-nowrap shrink-0\">\n <span>&copy; {currentYear} Oplytics.digital</span>\n {!showHealthMonitor && buildStamp && (\n <>\n <Separator />\n <span className=\"font-mono\" title=\"Deployed build\">{buildStamp}</span>\n </>\n )}\n </div>\n </footer>\n );\n}\n\nfunction Separator() {\n return (\n <span className=\"text-[#1E2738] select-none\" aria-hidden>\n |\n </span>\n );\n}\n","/**\n * createServiceLayout — Factory function that generates a complete DashboardLayout\n * for any Oplytics subdomain from a SharedSidebarConfig.\n *\n * This is the primary API for subdomains. Instead of each subdomain maintaining\n * its own 200-350 line DashboardLayout, they call:\n *\n * const SQDCPLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n *\n * And get a fully-featured layout with:\n * - Resizable sidebar with all standard features\n * - SharedPageHeader integration\n * - ReportingToolbar integration (optional)\n * - HierarchyProvider wrapping (optional)\n * - Auth gate with loading skeleton + sign-in fallback (optional)\n * - User footer with logout dropdown\n *\n * @module shared/components/createServiceLayout\n */\n\nimport React from \"react\";\nimport type { SharedSidebarConfig, MenuSection } from \"./SharedSidebar\";\nimport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize } from \"./SharedSidebar\";\n\n/* ── Service Layout Configuration ── */\n\nexport interface ServiceLayoutConfig extends SharedSidebarConfig {\n /** Show SharedPageHeader above content (default: true) */\n showPageHeader?: boolean;\n /** SharedPageHeader props — passed through when showPageHeader is true */\n pageHeaderProps?: {\n showHierarchy?: boolean;\n hierarchyMaxDepth?: number;\n showSidebarTrigger?: boolean;\n };\n /** Show ReportingToolbar in the header (default: false) */\n showReportingToolbar?: boolean;\n /** Module name passed to ReportingToolbar */\n reportingModuleName?: string;\n /** Wrap layout in HierarchyProvider (default: false) */\n wrapHierarchyProvider?: boolean;\n /** Show auth gate — loading skeleton + sign-in fallback (default: true) */\n showAuthGate?: boolean;\n /** Report submit handler */\n onReportSubmit?: (report: any) => Promise<void> | void;\n /** Feedback submit handler */\n onFeedbackSubmit?: (feedback: any) => Promise<void> | void;\n}\n\n/**\n * Creates a service-specific layout component from configuration.\n *\n * NOTE: This is a blueprint/factory specification. The actual React component\n * is assembled by each subdomain using their local UI primitives (Sidebar,\n * SidebarProvider, etc.) since these are not shared across subdomains yet.\n *\n * When subdomain consolidation (#311) is complete, this factory will directly\n * render the shared UI primitives. Until then, subdomains use this config\n * to standardise their DashboardLayout implementations.\n *\n * @example\n * ```tsx\n * // In SQDCP's DashboardLayout.tsx:\n * import { sqdcpLayoutConfig } from \"./sqdcp-config\";\n * // Use sqdcpLayoutConfig.menuSections, .backLink, etc. to build the layout\n * ```\n */\nexport function defineServiceLayout(config: ServiceLayoutConfig): ServiceLayoutConfig {\n return {\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n showPageHeader: true,\n showReportingToolbar: false,\n wrapHierarchyProvider: false,\n showAuthGate: true,\n ...config,\n storageKeyPrefix: config.storageKeyPrefix || config.serviceName.toLowerCase().replace(/\\s+/g, \"-\"),\n };\n}\n\n/* ── Pre-built Service Configurations ── */\n\n// These are the canonical configurations for each subdomain.\n// Subdomains should import and use these instead of defining their own.\n\nexport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize };\n","/**\n * Canonical Service Layout Configurations for all Oplytics subdomains.\n *\n * Each subdomain imports its config from here and uses it to build its\n * DashboardLayout. This ensures consistency across all services while\n * allowing per-service customisation (menu items, features, etc.).\n *\n * When subdomain consolidation (#311) is complete, these configs will\n * drive the shared createServiceLayout factory directly.\n *\n * @module shared/components/serviceConfigs\n */\n\nimport type { ServiceLayoutConfig } from \"./createServiceLayout\";\nimport { portalUrl } from \"../services/platformDomain\";\n\n/* ── Icon placeholder types ── */\n// Icons are imported locally by each subdomain from lucide-react.\n// These configs use string identifiers that map to icon components.\n\nexport interface MenuItemConfig {\n iconName: string;\n label: string;\n path: string;\n}\n\nexport interface MenuSectionConfig {\n label?: string;\n items: MenuItemConfig[];\n adminOnly?: boolean;\n}\n\nexport interface ServiceConfig {\n serviceName: string;\n serviceAbbreviation: string;\n serviceIconName: string;\n menuSections: MenuSectionConfig[];\n backLink?: {\n label: string;\n path: string;\n };\n showPageHeader: boolean;\n showReportingToolbar: boolean;\n reportingModuleName?: string;\n wrapHierarchyProvider: boolean;\n showAuthGate: boolean;\n storageKeyPrefix: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}\n\n/* ── SQDCP ── */\n\nexport const sqdcpConfig: ServiceConfig = {\n serviceName: \"SQDCP\",\n serviceAbbreviation: \"S\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"PenLine\", label: \"Data Entry\", path: \"/entry\" },\n { iconName: \"ClipboardList\", label: \"Action Tracker\", path: \"/actions\" },\n { iconName: \"FileText\", label: \"Reports\", path: \"/reports\" },\n { iconName: \"Settings\", label: \"Admin\", path: \"/admin\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"SQDCP\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"sqdcp-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── OEE Manager ── */\n\nexport const oeeManagerConfig: ServiceConfig = {\n serviceName: \"OEE Manager\",\n serviceAbbreviation: \"o\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"BarChart3\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Database\", label: \"Data Input\", path: \"/data-input\" },\n { iconName: \"Clock\", label: \"Downtime\", path: \"/downtime\" },\n { iconName: \"Settings\", label: \"Settings\", path: \"/settings\" },\n ],\n },\n ],\n backLink: {\n label: \"Back to Portal\",\n path: portalUrl(),\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"OEE Enterprise Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"oee-sidebar\",\n defaultWidth: 240,\n minWidth: 200,\n maxWidth: 400,\n};\n\n/* ── Action Manager ── */\n\nexport const actionManagerConfig: ServiceConfig = {\n serviceName: \"Action Manager\",\n serviceAbbreviation: \"A\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Teams\", path: \"/teams\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Action Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"actionmanager-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Business Hub ── */\n\nexport const businessHubConfig: ServiceConfig = {\n serviceName: \"Business Hub\",\n serviceAbbreviation: \"B\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Analytics\", path: \"/analytics\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Business Hub\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"businesshub-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Policy Deployment ── */\n\nexport const policyDeploymentConfig: ServiceConfig = {\n serviceName: \"Policy Deployment\",\n serviceAbbreviation: \"PD\",\n serviceIconName: \"FileStack\",\n menuSections: [\n {\n label: \"Analysis\",\n items: [\n { iconName: \"LayoutGrid\", label: \"Dashboard\", path: \"/app/policy-deployment\" },\n { iconName: \"Grid3X3\", label: \"X-Matrix\", path: \"/app/policy-deployment/xmatrix\" },\n { iconName: \"BarChart3\", label: \"Bowling Chart\", path: \"/app/policy-deployment/bowling\" },\n { iconName: \"FolderKanban\", label: \"Action Plans\", path: \"/app/policy-deployment/actions\" },\n { iconName: \"GitBranchPlus\", label: \"Catchball\", path: \"/app/policy-deployment/catchball\" },\n { iconName: \"Target\", label: \"Deployments\", path: \"/app/policy-deployment/deployments\" },\n ],\n },\n {\n label: \"Administration\",\n adminOnly: true,\n items: [\n { iconName: \"Settings\", label: \"Manage Policy\", path: \"/app/policy-deployment/manage\" },\n { iconName: \"Plug\", label: \"Integrations\", path: \"/app/policy-deployment/integrations\" },\n ],\n },\n ],\n backLink: {\n label: \"Service Hub\",\n path: \"/app\",\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"Policy Deployment\",\n wrapHierarchyProvider: true,\n showAuthGate: true,\n storageKeyPrefix: \"policy-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── All configs indexed by service key ── */\n\nexport const SERVICE_CONFIGS = {\n sqdcp: sqdcpConfig,\n oeeManager: oeeManagerConfig,\n actionManager: actionManagerConfig,\n businessHub: businessHubConfig,\n policyDeployment: policyDeploymentConfig,\n} as const;\n\nexport type ServiceKey = keyof typeof SERVICE_CONFIGS;\n"],"mappings":"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","names":["ExternalLink","jsx","jsxs","useEffect","useState","Fragment","jsx","jsxs","useState","useEffect","Fragment","jsx","jsxs","portalUrl","ExternalLink","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs","formatBuildDate"]}
@@ -1,5 +1,5 @@
1
- import { S as SharedSidebarConfig } from '../HealthFooter-wAa7a7WR.js';
2
- export { C as CrossServiceNav, d as CrossServiceNavProps, D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection, i as SharedSidebarProps, j as getInitials, k as isAdminRole, l as isMenuItemActive, u as useSidebarResize } from '../HealthFooter-wAa7a7WR.js';
1
+ import { S as SharedSidebarConfig } from '../HealthFooter-BA781r9p.js';
2
+ export { C as CrossServiceNav, d as CrossServiceNavProps, D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection, i as SharedSidebarProps, j as getInitials, k as isAdminRole, l as isMenuItemActive, u as useSidebarResize } from '../HealthFooter-BA781r9p.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode } from 'react';
5
5
  import '../crossNav-Bd8-0qoi.js';
@@ -55,7 +55,7 @@ interface FooterService {
55
55
  slug: string;
56
56
  /** Local route within the portal (e.g. /services/sqdcp) */
57
57
  localRoute: string;
58
- /** External URL for the subdomain (e.g. https://sqdcp.oplytics.digital) */
58
+ /** External URL for the subdomain (e.g. https://sqdcp.oplyticsdigital.net) */
59
59
  externalUrl?: string;
60
60
  }
61
61
  /** Standard Oplytics service list */
@@ -19,7 +19,8 @@ import {
19
19
  policyDeploymentConfig,
20
20
  sqdcpConfig,
21
21
  useSidebarResize
22
- } from "../chunk-POOTKX4M.js";
22
+ } from "../chunk-WMUCY5GV.js";
23
+ import "../chunk-4QJMLQSH.js";
23
24
  import "../chunk-ZLOUWBQN.js";
24
25
  import "../chunk-3E3GLIQV.js";
25
26
  import "../chunk-3G43UG63.js";
@@ -1,6 +1,31 @@
1
1
  export { A as ADMIN_SEE_ALL_ROLES, B as BuildCrossServiceLinksOptions, C as CrossServiceLink, S as SLUG_ALIASES, b as buildCrossServiceLinks, n as normalizeServiceSlug } from '../crossNav-Bd8-0qoi.js';
2
2
  import '../orgHierarchy-BLW4dt8Q.js';
3
3
 
4
+ /**
5
+ * Platform domain resolution — single source of truth for building
6
+ * cross-service URLs (Service Hub cards, sidebar cross-nav, footers,
7
+ * "Back to Portal" links).
8
+ *
9
+ * Every service runs on a sibling subdomain of one parent domain
10
+ * (portal.oplyticsdigital.net, sqdcp.oplyticsdigital.net, …), so the parent
11
+ * domain is derived at runtime from the current hostname: strip the service
12
+ * label, keep the rest. Links therefore stay on whichever domain the app is
13
+ * served from — no per-app build args, and a future domain move needs no
14
+ * code change here.
15
+ *
16
+ * Outside a browser (SSR, tests) or on hosts with no parent domain
17
+ * (localhost, raw IPs) the canonical production domain is used.
18
+ */
19
+ declare const DEFAULT_PLATFORM_DOMAIN = "oplyticsdigital.net";
20
+ declare function getPlatformDomain(): string;
21
+ /**
22
+ * Absolute https URL for a service subdomain label on the current platform
23
+ * domain, e.g. `platformUrl("sqdcp")` → `https://sqdcp.oplyticsdigital.net`.
24
+ */
25
+ declare function platformUrl(subdomainLabel: string, path?: string): string;
26
+ /** Portal URL builder — the portal always lives at the `portal` label. */
27
+ declare function portalUrl(path?: string): string;
28
+
4
29
  /**
5
30
  * Shared Service Catalog — Single source of truth for all Oplytics.digital services.
6
31
  *
@@ -31,8 +56,12 @@ interface ServiceDefinition {
31
56
  status: ServiceStatus;
32
57
  /** Core = standalone product, Hub = extension/add-on module */
33
58
  category: ServiceCategory;
34
- /** Subdomain hostname (without protocol) — null if hosted on portal */
35
- subdomain: string | null;
59
+ /**
60
+ * Subdomain label on the platform domain (e.g. "sqdcp") — null if hosted
61
+ * on portal. Full URLs are built via getServiceUrl(), which resolves the
62
+ * platform domain at runtime (see ./platformDomain).
63
+ */
64
+ subdomainLabel: string | null;
36
65
  }
37
66
  declare const SERVICE_CATALOG: ServiceDefinition[];
38
67
  declare const coreServices: ServiceDefinition[];
@@ -43,4 +72,4 @@ declare const comingSoonServices: ServiceDefinition[];
43
72
  declare function getServiceBySlug(slug: string): ServiceDefinition | undefined;
44
73
  declare function getServiceUrl(service: ServiceDefinition): string | null;
45
74
 
46
- export { SERVICE_CATALOG, type ServiceCategory, type ServiceDefinition, type ServiceStatus, comingSoonServices, coreServices, getServiceBySlug, getServiceUrl, hubServices, inDevServices, liveServices };
75
+ export { DEFAULT_PLATFORM_DOMAIN, SERVICE_CATALOG, type ServiceCategory, type ServiceDefinition, type ServiceStatus, comingSoonServices, coreServices, getPlatformDomain, getServiceBySlug, getServiceUrl, hubServices, inDevServices, liveServices, platformUrl, portalUrl };
@@ -11,21 +11,31 @@ import {
11
11
  inDevServices,
12
12
  liveServices,
13
13
  normalizeServiceSlug
14
- } from "../chunk-HNOROBPK.js";
14
+ } from "../chunk-PLS7NDDR.js";
15
+ import {
16
+ DEFAULT_PLATFORM_DOMAIN,
17
+ getPlatformDomain,
18
+ platformUrl,
19
+ portalUrl
20
+ } from "../chunk-4QJMLQSH.js";
15
21
  import "../chunk-3E3GLIQV.js";
16
22
  import "../chunk-PS4K7FKB.js";
17
23
  export {
18
24
  ADMIN_SEE_ALL_ROLES,
25
+ DEFAULT_PLATFORM_DOMAIN,
19
26
  SERVICE_CATALOG,
20
27
  SLUG_ALIASES,
21
28
  buildCrossServiceLinks,
22
29
  comingSoonServices,
23
30
  coreServices,
31
+ getPlatformDomain,
24
32
  getServiceBySlug,
25
33
  getServiceUrl,
26
34
  hubServices,
27
35
  inDevServices,
28
36
  liveServices,
29
- normalizeServiceSlug
37
+ normalizeServiceSlug,
38
+ platformUrl,
39
+ portalUrl
30
40
  };
31
41
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
- import { D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser } from '../HealthFooter-wAa7a7WR.js';
2
- export { C as CrossServiceNav, d as CrossServiceNavProps, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection } from '../HealthFooter-wAa7a7WR.js';
1
+ import { D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser } from '../HealthFooter-BA781r9p.js';
2
+ export { C as CrossServiceNav, d as CrossServiceNavProps, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection } from '../HealthFooter-BA781r9p.js';
3
3
  export { C as CrossServiceLink, b as buildCrossServiceLinks } from '../crossNav-Bd8-0qoi.js';
4
4
  import 'react/jsx-runtime';
5
5
  import 'react';
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  buildCrossServiceLinks
3
- } from "../chunk-HNOROBPK.js";
3
+ } from "../chunk-PLS7NDDR.js";
4
4
  import {
5
5
  CrossServiceNav,
6
6
  DashboardLayout,
7
7
  HealthFooter
8
- } from "../chunk-POOTKX4M.js";
8
+ } from "../chunk-WMUCY5GV.js";
9
+ import "../chunk-4QJMLQSH.js";
9
10
  import "../chunk-ZLOUWBQN.js";
10
11
  import "../chunk-3E3GLIQV.js";
11
12
  import "../chunk-3G43UG63.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/index.ts"],"sourcesContent":["/**\n * @pablo2410/shared-ui/shell — Oplytics Service Shell\n *\n * The single composed chrome (sidebar + header + breadcrumb + reporting +\n * footer + user menu) used by every Oplytics subdomain. Subdomains become\n * a ~50-line adapter that supplies *data and identity*, nothing visual.\n * One PR to shared-ui + a coordinated version bump propagates a chrome\n * change to every service.\n *\n * Composition: re-exports the existing `DashboardLayout` from\n * `@pablo2410/shared-ui/layout` under the clearer canonical name\n * `OplyticsServiceShell`. The two are the same component; consumers\n * should prefer `/shell` going forward — `/layout` will be marked\n * `@deprecated` once every subdomain has migrated and removed in\n * shared-ui v1.0 (see oplytics-subdomains tracking epic).\n *\n * CSS: ship `dist/shell/oplytics-shell.css` containing every Tailwind v4\n * utility this shell uses (built by `pnpm build:shell-css`). Consumers\n * `@import` it once in their own `index.css` and never need an\n * `@source` directive.\n *\n * ```css\n * @import \"tailwindcss\";\n * @import \"@pablo2410/shared-ui/theme/oplytics-theme.css\";\n * @import \"@pablo2410/shared-ui/shell/oplytics-shell.css\";\n * ```\n *\n * Usage:\n *\n * ```tsx\n * import { OplyticsServiceShell } from \"@pablo2410/shared-ui/shell\";\n *\n * <OplyticsServiceShell\n * serviceName=\"Connect\"\n * serviceIcon={<Zap />}\n * menuSections={menuSections}\n * adminSections={adminSections}\n * isAdmin={user?.role === \"platform_admin\"}\n * activePath={location}\n * onNavigate={setLocation}\n * user={user}\n * onLogout={logout}\n * enterprise={selection.enterprise}\n * hierarchyNavigator={<HierarchyNavigator hierarchy={hierarchy} />}\n * reportingToolbar={<ReportingToolbar … />}\n * >\n * <Routes />\n * </OplyticsServiceShell>\n * ```\n */\n\nimport {\n DashboardLayout,\n type DashboardLayoutProps,\n type DashboardLayoutUser,\n type DashboardLayoutEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n} from \"../layout\";\nimport {\n buildCrossServiceLinks,\n type CrossServiceLink,\n} from \"../services/crossNav\";\n\n/**\n * The canonical composed Oplytics chrome.\n *\n * This is the same React component as `DashboardLayout` in\n * `@pablo2410/shared-ui/layout`, re-exported under a clearer name.\n * Consumers should prefer `OplyticsServiceShell` going forward.\n */\nconst OplyticsServiceShell = DashboardLayout;\n\n/**\n * Props accepted by `OplyticsServiceShell`.\n * (Alias of `DashboardLayoutProps`.)\n */\ntype ServiceShellProps = DashboardLayoutProps;\n\n/**\n * Authenticated user passed to the shell. Small enough to fit both\n * stateless-JWT subdomains (Connect, Policy Deployment) and DB-backed\n * ones (OEE Manager).\n */\ntype ServiceShellUser = DashboardLayoutUser;\n\n/**\n * Currently-scoped enterprise — drives the header enterprise badge.\n */\ntype ServiceShellEnterprise = DashboardLayoutEnterprise;\n\nexport {\n OplyticsServiceShell,\n type ServiceShellProps,\n type ServiceShellUser,\n type ServiceShellEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n buildCrossServiceLinks,\n type CrossServiceLink,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA6EA,IAAM,uBAAuB;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/index.ts"],"sourcesContent":["/**\n * @pablo2410/shared-ui/shell — Oplytics Service Shell\n *\n * The single composed chrome (sidebar + header + breadcrumb + reporting +\n * footer + user menu) used by every Oplytics subdomain. Subdomains become\n * a ~50-line adapter that supplies *data and identity*, nothing visual.\n * One PR to shared-ui + a coordinated version bump propagates a chrome\n * change to every service.\n *\n * Composition: re-exports the existing `DashboardLayout` from\n * `@pablo2410/shared-ui/layout` under the clearer canonical name\n * `OplyticsServiceShell`. The two are the same component; consumers\n * should prefer `/shell` going forward — `/layout` will be marked\n * `@deprecated` once every subdomain has migrated and removed in\n * shared-ui v1.0 (see oplytics-subdomains tracking epic).\n *\n * CSS: ship `dist/shell/oplytics-shell.css` containing every Tailwind v4\n * utility this shell uses (built by `pnpm build:shell-css`). Consumers\n * `@import` it once in their own `index.css` and never need an\n * `@source` directive.\n *\n * ```css\n * @import \"tailwindcss\";\n * @import \"@pablo2410/shared-ui/theme/oplytics-theme.css\";\n * @import \"@pablo2410/shared-ui/shell/oplytics-shell.css\";\n * ```\n *\n * Usage:\n *\n * ```tsx\n * import { OplyticsServiceShell } from \"@pablo2410/shared-ui/shell\";\n *\n * <OplyticsServiceShell\n * serviceName=\"Connect\"\n * serviceIcon={<Zap />}\n * menuSections={menuSections}\n * adminSections={adminSections}\n * isAdmin={user?.role === \"platform_admin\"}\n * activePath={location}\n * onNavigate={setLocation}\n * user={user}\n * onLogout={logout}\n * enterprise={selection.enterprise}\n * hierarchyNavigator={<HierarchyNavigator hierarchy={hierarchy} />}\n * reportingToolbar={<ReportingToolbar … />}\n * >\n * <Routes />\n * </OplyticsServiceShell>\n * ```\n */\n\nimport {\n DashboardLayout,\n type DashboardLayoutProps,\n type DashboardLayoutUser,\n type DashboardLayoutEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n} from \"../layout\";\nimport {\n buildCrossServiceLinks,\n type CrossServiceLink,\n} from \"../services/crossNav\";\n\n/**\n * The canonical composed Oplytics chrome.\n *\n * This is the same React component as `DashboardLayout` in\n * `@pablo2410/shared-ui/layout`, re-exported under a clearer name.\n * Consumers should prefer `OplyticsServiceShell` going forward.\n */\nconst OplyticsServiceShell = DashboardLayout;\n\n/**\n * Props accepted by `OplyticsServiceShell`.\n * (Alias of `DashboardLayoutProps`.)\n */\ntype ServiceShellProps = DashboardLayoutProps;\n\n/**\n * Authenticated user passed to the shell. Small enough to fit both\n * stateless-JWT subdomains (Connect, Policy Deployment) and DB-backed\n * ones (OEE Manager).\n */\ntype ServiceShellUser = DashboardLayoutUser;\n\n/**\n * Currently-scoped enterprise — drives the header enterprise badge.\n */\ntype ServiceShellEnterprise = DashboardLayoutEnterprise;\n\nexport {\n OplyticsServiceShell,\n type ServiceShellProps,\n type ServiceShellUser,\n type ServiceShellEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n buildCrossServiceLinks,\n type CrossServiceLink,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6EA,IAAM,uBAAuB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablo2410/shared-ui",
3
- "version": "0.9.4",
3
+ "version": "0.11.0",
4
4
  "description": "Shared UI component library for all Oplytics.digital subdomains",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/catalog.ts","../src/services/crossNav.ts"],"sourcesContent":["/**\n * Shared Service Catalog — Single source of truth for all Oplytics.digital services.\n *\n * Consumed by:\n * - oplytics-subdomains (ServiceHub.tsx) — service cards, access gating\n * - oplytics-marketing-site (services.ts) — marketing pages, hero sections\n *\n * Each consumer extends with context-specific fields locally:\n * - Platform adds: route, external URL, React icon component\n * - Marketing adds: heroImage, demoImage, problem, howItWorks, results, aiFeatures\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ServiceStatus = \"live\" | \"in-development\" | \"coming-soon\";\nexport type ServiceCategory = \"core\" | \"hub\";\n\nexport interface ServiceDefinition {\n /** Unique identifier used for DB lookups, URL slugs, and cross-referencing */\n slug: string;\n /** Display name shown in cards, headers, and navigation */\n name: string;\n /** Short one-line value proposition */\n tagline: string;\n /** Longer description (1-2 sentences) for cards and summaries */\n description: string;\n /** Lucide icon name (string) — consumers resolve to component locally */\n icon: string;\n /** Hex accent color for cards, badges, and theming */\n accentColor: string;\n /** Current availability status */\n status: ServiceStatus;\n /** Core = standalone product, Hub = extension/add-on module */\n category: ServiceCategory;\n /** Subdomain hostname (without protocol) — null if hosted on portal */\n subdomain: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Catalog\n// ---------------------------------------------------------------------------\n\nexport const SERVICE_CATALOG: ServiceDefinition[] = [\n // ─── CORE SERVICES ───\n {\n slug: \"policy-deployment\",\n name: \"Policy Deployment\",\n tagline: \"Align strategy to execution\",\n description:\n \"Hoshin Kanri and X-matrix planning. Cascade strategic objectives through every level of your organisation with live tracking and bowling charts.\",\n icon: \"Target\",\n accentColor: \"#F59E0B\",\n status: \"live\",\n category: \"core\",\n subdomain: \"policydeployment.oplytics.digital\",\n },\n {\n slug: \"sqdcp\",\n name: \"SQDCP Dashboard\",\n tagline: \"Daily management boards, digitised\",\n description:\n \"Digital SQDCP boards that drive daily accountability. Safety, Quality, Delivery, Cost, and People metrics at a glance for every team.\",\n icon: \"LayoutGrid\",\n accentColor: \"#1DB8CE\",\n status: \"live\",\n category: \"core\",\n subdomain: \"sqdcp.oplytics.digital\",\n },\n {\n slug: \"oee-manager\",\n name: \"OEE Manager\",\n tagline: \"Real-time equipment effectiveness tracking\",\n description:\n \"Monitor Overall Equipment Effectiveness in real-time. Track availability, performance, and quality metrics across your entire production line.\",\n icon: \"Gauge\",\n accentColor: \"#8C34E9\",\n status: \"live\",\n category: \"core\",\n subdomain: \"oeemanager.oplytics.digital\",\n },\n {\n slug: \"action-manager\",\n name: \"Action Manager\",\n tagline: \"Track every action to closure\",\n description:\n \"Capture, assign, and track corrective and preventive actions from any source. Ensure nothing falls through the cracks.\",\n icon: \"ClipboardCheck\",\n accentColor: \"#22C55E\",\n status: \"live\",\n category: \"core\",\n subdomain: \"actionmanager.oplytics.digital\",\n },\n {\n slug: \"connect\",\n name: \"OplyticsConnect\",\n tagline: \"Integrate machines, sensors, and systems\",\n description:\n \"Industrial IoT connectivity layer. Connect PLCs, sensors, and legacy systems to the Oplytics platform with zero-code configuration.\",\n icon: \"Plug\",\n accentColor: \"#1DB8CE\",\n status: \"in-development\",\n category: \"core\",\n subdomain: \"connect.oplytics.digital\",\n },\n\n // ─── HUB SERVICES ───\n {\n slug: \"safety-manager\",\n name: \"Safety Manager\",\n tagline: \"Proactive safety culture, powered by data\",\n description:\n \"Incident reporting, hazard tracking, safety observations, and compliance management in one unified platform.\",\n icon: \"Shield\",\n accentColor: \"#EF4444\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomain: \"safetymanager.oplytics.digital\",\n },\n {\n slug: \"quality-manager\",\n name: \"Quality Manager\",\n tagline: \"End-to-end quality assurance\",\n description:\n \"Non-conformance tracking, CAPA management, audit scheduling, and quality metrics dashboards.\",\n icon: \"CheckCircle\",\n accentColor: \"#22C55E\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomain: null,\n },\n {\n slug: \"certification-manager\",\n name: \"Certification Manager\",\n tagline: \"Stay audit-ready, always\",\n description:\n \"Manage ISO, IATF, and other certification requirements. Document control, audit trails, and compliance tracking.\",\n icon: \"Award\",\n accentColor: \"#F97316\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomain: null,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Derived helpers\n// ---------------------------------------------------------------------------\n\nexport const coreServices = SERVICE_CATALOG.filter(\n (s) => s.category === \"core\",\n);\nexport const hubServices = SERVICE_CATALOG.filter((s) => s.category === \"hub\");\nexport const liveServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"live\",\n);\nexport const inDevServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"in-development\",\n);\nexport const comingSoonServices = SERVICE_CATALOG.filter(\n (s) => s.status === \"coming-soon\",\n);\n\nexport function getServiceBySlug(\n slug: string,\n): ServiceDefinition | undefined {\n return SERVICE_CATALOG.find((s) => s.slug === slug);\n}\n\nexport function getServiceUrl(service: ServiceDefinition): string | null {\n if (service.subdomain) {\n return `https://${service.subdomain}`;\n }\n return null;\n}\n","/**\n * crossNav — build the sidebar cross-service links (service → service jump\n * without going through the Portal Service Hub).\n *\n * Visibility mirrors the Portal ServiceHub rule: platform_admin and\n * enterprise_admin see every live service; everyone else only sees the\n * services granted in the portal `user_services` table. The current service\n * never links to itself. Each link URL carries the caller's current\n * org-hierarchy selection (?oh=…) so the breadcrumb arrives pre-aligned.\n */\nimport type { OrgHierarchySelection } from \"../hierarchy/orgHierarchy\";\nimport { appendSelectionParam } from \"../hierarchy/selectionParams\";\nimport { SERVICE_CATALOG, getServiceUrl } from \"./catalog\";\n\n/** Roles that see every live service regardless of individual grants. */\nexport const ADMIN_SEE_ALL_ROLES = [\"platform_admin\", \"enterprise_admin\"] as const;\n\n/**\n * Legacy grant slugs → canonical catalog slugs. Auto-provisioned accounts\n * were granted \"sqdcp-logic\" while the catalog slug is \"sqdcp\".\n */\nexport const SLUG_ALIASES: Record<string, string> = {\n \"sqdcp-logic\": \"sqdcp\",\n};\n\nexport function normalizeServiceSlug(slug: string): string {\n return SLUG_ALIASES[slug] ?? slug;\n}\n\n/** Display-ready cross-service link entry. */\nexport interface CrossServiceLink {\n slug: string;\n name: string;\n /** Lucide icon name from the service catalog (consumers resolve locally). */\n icon: string;\n accentColor: string;\n /** Absolute URL including the ?oh= hierarchy selection param. */\n url: string;\n}\n\nexport interface BuildCrossServiceLinksOptions {\n /** Catalog slug of the service doing the rendering (excluded from links). */\n currentSlug: string;\n /** Grant slugs from the portal (raw — aliases are normalized here). */\n grantedSlugs: string[];\n /** Portal role of the current user (drives the admin see-all rule). */\n role?: string | null;\n /** Current org-hierarchy selection to carry across (may be empty/null). */\n selection?: OrgHierarchySelection | null;\n}\n\nexport function buildCrossServiceLinks(\n options: BuildCrossServiceLinksOptions,\n): CrossServiceLink[] {\n const { currentSlug, grantedSlugs, role, selection } = options;\n\n const isAdmin =\n !!role && (ADMIN_SEE_ALL_ROLES as readonly string[]).includes(role);\n const granted = new Set(grantedSlugs.map(normalizeServiceSlug));\n const current = normalizeServiceSlug(currentSlug);\n\n return SERVICE_CATALOG.filter(\n (svc) =>\n svc.status === \"live\" &&\n svc.subdomain !== null &&\n svc.slug !== current &&\n (isAdmin || granted.has(svc.slug)),\n ).map((svc) => ({\n slug: svc.slug,\n name: svc.name,\n icon: svc.icon,\n accentColor: svc.accentColor,\n url: appendSelectionParam(getServiceUrl(svc)!, selection),\n }));\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/layout/SharedSidebar.tsx","../src/layout/DashboardLayout.tsx","../src/layout/CrossServiceNav.tsx","../src/layout/HealthFooter.tsx","../src/layout/EnterpriseDebugLabel.tsx","../src/layout/DashboardLayoutSkeleton.tsx","../src/layout/SharedPageHeader.tsx","../src/layout/SharedFooter.tsx","../src/layout/ServiceFooter.tsx","../src/layout/createServiceLayout.tsx","../src/layout/serviceConfigs.ts"],"sourcesContent":["/**\n * SharedSidebar — Config-driven sidebar component for all Oplytics subdomains.\n *\n * Replaces per-subdomain DashboardLayout sidebar implementations with a single\n * shared component. Each subdomain provides its own configuration (menu items,\n * service name, feature flags) and gets a consistent sidebar experience.\n *\n * Features:\n * - Resizable sidebar with localStorage persistence\n * - Collapsible icon mode with tooltips\n * - Service branding (icon + name) in header\n * - \"Back to Service Hub/Portal\" link (configurable)\n * - Grouped menu sections with optional section labels\n * - Admin-only menu items (role-gated)\n * - User avatar footer with optional logout dropdown\n * - Mobile-responsive with auto-collapse\n *\n * Usage:\n * import { createServiceLayout } from \"@shared/components/SharedSidebar\";\n * const MyLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n * // Then use <MyLayout>{children}</MyLayout> in your routes\n *\n * @module shared/components/SharedSidebar\n */\n\nimport React, { CSSProperties, useEffect, useRef, useState } from \"react\";\n\n/* ── Types ── */\n\nexport interface MenuItem {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n path: string;\n /** Optional badge text (e.g. count) */\n badge?: string | number;\n}\n\nexport interface MenuSection {\n /** Section label displayed above items */\n label?: string;\n /** Alias for label — used by DashboardLayout */\n title?: string;\n items: MenuItem[];\n adminOnly?: boolean;\n}\n\nexport interface SharedSidebarConfig {\n /** Display name shown in sidebar header */\n serviceName: string;\n /** Icon component rendered next to service name */\n serviceIcon: React.ReactNode;\n /** Primary navigation items */\n menuSections: MenuSection[];\n /** Show \"Back to Service Hub\" / \"Back to Portal\" link at top of nav */\n backLink?: {\n label: string;\n path: string;\n };\n /** localStorage key prefix for sidebar width persistence */\n storageKeyPrefix?: string;\n /** Default sidebar width in pixels (default: 260) */\n defaultWidth?: number;\n /** Minimum sidebar width in pixels (default: 200) */\n minWidth?: number;\n /** Maximum sidebar width in pixels (default: 480) */\n maxWidth?: number;\n}\n\nexport interface SharedSidebarProps {\n config: SharedSidebarConfig;\n /** Current route location */\n location: string;\n /** Navigate to a path */\n setLocation: (path: string) => void;\n /** Current user object (null if not authenticated) */\n user: { name?: string; email?: string; role?: string } | null;\n /** Logout handler */\n onLogout?: () => void;\n /** Whether sidebar is collapsed */\n isCollapsed: boolean;\n /** Toggle sidebar collapsed state */\n toggleSidebar: () => void;\n /** Whether user has admin role */\n isAdmin: boolean;\n /** Children rendered in SidebarInset main area */\n children: React.ReactNode;\n}\n\n/* ── Constants ── */\n\nconst ADMIN_ROLES = [\"enterprise_admin\", \"platform_admin\", \"admin\", \"superuser\"];\n\n/* ── Helpers ── */\n\nexport function isAdminRole(role?: string): boolean {\n return !!role && ADMIN_ROLES.includes(role);\n}\n\nexport function getInitials(name?: string): string {\n if (!name) return \"U\";\n return name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n}\n\n/**\n * Determines if a menu item is active based on current location.\n * Exact match for root paths, startsWith for nested paths.\n */\nexport function isMenuItemActive(itemPath: string, location: string, basePath?: string): boolean {\n if (basePath) {\n // For subdomain routes with a base path (e.g., /app/policy-deployment)\n if (itemPath === basePath) return location === basePath;\n return location.startsWith(itemPath);\n }\n // For root-level routes\n if (itemPath === \"/\") return location === \"/\";\n return location.startsWith(itemPath);\n}\n\n/* ── Resize Hook ── */\n\nexport function useSidebarResize(config: {\n storageKey: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}) {\n const [width, setWidth] = useState(() => {\n if (typeof window === \"undefined\") return config.defaultWidth;\n const saved = localStorage.getItem(config.storageKey);\n return saved ? parseInt(saved, 10) : config.defaultWidth;\n });\n const [isResizing, setIsResizing] = useState(false);\n const startX = useRef(0);\n const startWidth = useRef(config.defaultWidth);\n\n useEffect(() => {\n localStorage.setItem(config.storageKey, String(width));\n }, [width, config.storageKey]);\n\n useEffect(() => {\n if (!isResizing) return;\n const onMove = (e: MouseEvent) => {\n const newWidth = Math.min(\n config.maxWidth,\n Math.max(config.minWidth, startWidth.current + (e.clientX - startX.current))\n );\n setWidth(newWidth);\n };\n const onUp = () => {\n setIsResizing(false);\n };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"col-resize\";\n document.body.style.userSelect = \"none\";\n return () => {\n document.removeEventListener(\"mousemove\", onMove);\n document.removeEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n };\n }, [isResizing, config.minWidth, config.maxWidth]);\n\n const startResize = (e: React.MouseEvent) => {\n setIsResizing(true);\n startX.current = e.clientX;\n startWidth.current = width;\n };\n\n return { width, isResizing, startResize };\n}\n\n/* ── Export config type for subdomain use ── */\nexport type { SharedSidebarConfig as SidebarConfig };\n","/**\n * DashboardLayout — the canonical sidebar + header shell for every Oplytics\n * subdomain (the \"Policy Deployment\" gold-standard layout, consolidated).\n *\n * Built on the shadcn `Sidebar` primitives shipped from `@pablo2410/shared-ui/primitives`.\n * It renders an identical chrome across services:\n * - Collapsible icon sidebar with a brand header (icon + service name)\n * - \"← Service Hub\" link pinned to the top of the nav\n * - Grouped, optionally admin-gated menu sections\n * - Settings footer\n * - Sticky top header with a hierarchy-breadcrumb slot, enterprise badge,\n * reporting-toolbar slot, and a user menu\n *\n * The component is intentionally app-agnostic: it imports no app hooks\n * (`useAuth`, `useHierarchy`, router). Apps pass identity, navigation state,\n * and their hierarchy navigator / reporting toolbar in as props/slots.\n *\n * Oplytics dark theme: #0D1220 header, #1E2738 border, #8C34E9 accent.\n */\nimport { type ReactNode } from \"react\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n useSidebar,\n Avatar,\n AvatarFallback,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../primitives\";\nimport { useIsMobile } from \"../primitives/useMobile\";\nimport { ArrowLeft, ExternalLink, LogOut, PanelLeft, Settings } from \"lucide-react\";\nimport { cn } from \"../utils/cn\";\nimport { type MenuItem, type MenuSection, isMenuItemActive } from \"./SharedSidebar\";\nimport { CrossServiceNav } from \"./CrossServiceNav\";\nimport type { CrossServiceLink } from \"../services/crossNav\";\nimport { HealthFooter } from \"./HealthFooter\";\nimport { EnterpriseDebugLabel } from \"./EnterpriseDebugLabel\";\n\n/* ── Re-export sidebar types ── */\nexport type { MenuItem, MenuSection };\n\n/* ── Types ── */\n\nexport interface DashboardLayoutUser {\n name?: string;\n email?: string;\n role?: string;\n avatarUrl?: string;\n /** The user's stable identifier (typically the JWT `openId` claim).\n * Optional — only used by the HealthFooter diagnostic strip. */\n openId?: string;\n /** The enterprise id from the JWT (`enterpriseId` claim). May be null\n * for platform_admin users who haven't selected an active enterprise.\n * Optional — only used by the HealthFooter diagnostic strip. */\n enterpriseId?: number | null;\n}\n\nexport interface DashboardLayoutEnterprise {\n /** The enterprise id the org-hierarchy is currently scoped to. Optional\n * — only used by the HealthFooter diagnostic strip; the visible header\n * badge only needs `name` (and optionally `code`). */\n id?: number;\n name: string;\n code?: string;\n}\n\nexport interface DashboardLayoutProps {\n /** Service/module name shown in the sidebar brand header (e.g. \"OEE Manager\"). */\n serviceName: string;\n /** Service icon (a lucide icon element), shown in the sidebar brand + header. */\n serviceIcon?: ReactNode;\n /** Primary navigation sections. */\n menuSections: MenuSection[];\n /** Admin-only navigation sections, rendered when `isAdmin` is true. */\n adminSections?: MenuSection[];\n /** Whether the current user may see `adminSections`. */\n isAdmin?: boolean;\n /** Current route (the app's router supplies this). */\n activePath: string;\n /** Navigate handler (the app's router supplies this). */\n onNavigate: (path: string) => void;\n /** Optional override for active-item detection. */\n isActive?: (itemPath: string, activePath: string) => boolean;\n /** URL for the \"← Service Hub\" link at the top of the nav (full-page nav). */\n serviceHubUrl?: string;\n /** Direct links to sibling services, rendered under the Service Hub link.\n * Build with `buildCrossServiceLinks` (services/crossNav) so provisioning\n * gating and the ?oh= hierarchy carry-over are handled consistently. */\n crossServiceLinks?: CrossServiceLink[];\n /** Footer \"Settings\" route. Omit to hide the footer. */\n settingsPath?: string;\n /** Authenticated user (drives the header user menu). */\n user?: DashboardLayoutUser | null;\n /** Sign-out handler invoked from the user menu. */\n onLogout?: () => void;\n /** \"User Settings\" target in the user menu (full-page nav). */\n userSettingsUrl?: string;\n /** App-provided hierarchy navigator rendered in the header breadcrumb slot. */\n hierarchyNavigator?: ReactNode;\n /** Selected enterprise — drives the header enterprise badge. */\n enterprise?: DashboardLayoutEnterprise | null;\n /** App-provided reporting toolbar rendered on the right of the header. */\n reportingToolbar?: ReactNode;\n /** Whether the sidebar starts expanded. Defaults to `!isMobile`. */\n defaultOpen?: boolean;\n /** Extra footer rendered fixed at the bottom (e.g. a debug/health bar).\n * Coexists with `showHealthFooter` — pass both if you want both. */\n footer?: ReactNode;\n /** When true AND the current user has `role === \"platform_admin\"`, the\n * shell renders the standardised platform-admin diagnostic strip\n * (`HealthFooter`) fixed at the bottom of the viewport. Subdomains\n * opt in with one prop and inherit any future improvements to the\n * strip without code changes. */\n showHealthFooter?: boolean;\n /**\n * Deployed-build info shown in the HealthFooter diagnostic strip\n * (build: version · commit · date). The consuming app supplies these from\n * its own build-time constants (e.g. Vite `define` of __BUILD_DATE__ /\n * __COMMIT_HASH__) and passes them down — globals can't be read from inside\n * this pre-compiled package, so they must arrive as props.\n */\n buildDate?: string;\n commitHash?: string;\n buildVersion?: string;\n /** Main content. */\n children: ReactNode;\n}\n\nconst DEFAULT_SERVICE_HUB_URL = \"https://portal.oplytics.digital/app\";\nconst DEFAULT_USER_SETTINGS_URL = \"https://portal.oplytics.digital/account\";\n\n/* ── Header: user menu ── */\n\nfunction UserMenu({\n user,\n onLogout,\n userSettingsUrl = DEFAULT_USER_SETTINGS_URL,\n enterprise,\n}: {\n user: DashboardLayoutUser;\n onLogout?: () => void;\n userSettingsUrl?: string;\n /** Active enterprise, used to render the TEMP diagnostic chip inside the menu. */\n enterprise?: { id?: number | null; name?: string | null } | null;\n}) {\n const initials = (user.name || \"U\")\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button className=\"flex items-center gap-2 rounded-md px-2 py-1 hover:bg-[#1E2738] transition-colors outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\">\n <Avatar className=\"h-7 w-7 border border-[#2A2A3E]\">\n <AvatarFallback className=\"text-[10px] font-medium bg-[#8C34E9]/20 text-[#C084FC]\">\n {initials}\n </AvatarFallback>\n </Avatar>\n <span className=\"hidden md:block text-xs text-[#E2E8F0] truncate max-w-[100px]\">\n {user.name || \"User\"}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-48 bg-[#0D1220] border-[#1E2738]\">\n <div className=\"px-3 py-2 border-b border-[#1E2738]\">\n <p className=\"text-xs font-medium text-[#E2E8F0] truncate\">{user.name}</p>\n <p className=\"text-[10px] text-[#596475] truncate\">{user.role}</p>\n </div>\n {/* TEMP DIAGNOSTIC — remove once subdomain enterprise-data issue is fixed.\n Lives inside the user menu (below the name, above User Settings) to keep\n it out of the header bar. */}\n <div className=\"px-2 py-2 border-b border-[#1E2738]\">\n <EnterpriseDebugLabel\n selectionEnterpriseId={enterprise?.id ?? null}\n cookieEnterpriseName={enterprise?.name ?? null}\n />\n </div>\n <DropdownMenuItem\n onClick={() => {\n window.location.href = userSettingsUrl;\n }}\n className=\"text-xs text-[#E2E8F0] hover:bg-[#1E2738] cursor-pointer focus:bg-[#1E2738]\"\n >\n <Settings className=\"h-3.5 w-3.5 mr-2 text-[#8890A0]\" />\n User Settings\n </DropdownMenuItem>\n <DropdownMenuSeparator className=\"bg-[#1E2738]\" />\n <DropdownMenuItem\n onClick={() => onLogout?.()}\n className=\"text-xs text-[#EF4444] hover:bg-[#EF4444]/10 cursor-pointer focus:bg-[#EF4444]/10 focus:text-[#EF4444]\"\n >\n <LogOut className=\"h-3.5 w-3.5 mr-2\" />\n Sign Out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ── Header: enterprise badge ── */\n\nfunction EnterpriseBadge({ enterprise }: { enterprise?: DashboardLayoutEnterprise | null }) {\n if (!enterprise) return null;\n return (\n <div className=\"hidden lg:flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#8C34E9]/10 border border-[#8C34E9]/20\">\n <div className=\"h-4 w-4 rounded-sm bg-[#8C34E9]/30 flex items-center justify-center\">\n <span className=\"text-[8px] font-bold text-[#C084FC]\">\n {enterprise.name.charAt(0).toUpperCase()}\n </span>\n </div>\n <span className=\"text-[10px] font-medium text-[#C084FC] truncate max-w-[80px]\">\n {enterprise.code || enterprise.name}\n </span>\n </div>\n );\n}\n\n/* ── Sidebar nav section ── */\n\nfunction NavSection({\n section,\n marginTop,\n isCollapsed,\n resolveActive,\n onNavigate,\n}: {\n section: MenuSection;\n marginTop?: boolean;\n isCollapsed: boolean;\n resolveActive: (path: string) => boolean;\n onNavigate: (path: string) => void;\n}) {\n const label = section.title ?? section.label;\n return (\n <>\n {label && (\n <div className={cn(\"px-4 py-2\", marginTop && \"mt-2\")}>\n {!isCollapsed && (\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {label}\n </span>\n )}\n </div>\n )}\n <SidebarMenu className=\"px-2 py-1\">\n {section.items.map((item: MenuItem) => {\n const active = resolveActive(item.path);\n const Icon = item.icon;\n return (\n <SidebarMenuItem key={item.path}>\n <SidebarMenuButton\n isActive={active}\n onClick={() => onNavigate(item.path)}\n tooltip={item.label}\n className=\"h-10 transition-all font-normal\"\n >\n {Icon && <Icon className={cn(\"h-4 w-4\", active && \"text-primary\")} />}\n <span>{item.label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n })}\n </SidebarMenu>\n </>\n );\n}\n\n/* ── Inner shell (runs inside SidebarProvider, so it can call useSidebar) ── */\n\nfunction DashboardShell({\n serviceName,\n serviceIcon,\n menuSections,\n adminSections,\n isAdmin = false,\n activePath,\n onNavigate,\n isActive,\n serviceHubUrl = DEFAULT_SERVICE_HUB_URL,\n crossServiceLinks,\n settingsPath,\n user,\n onLogout,\n userSettingsUrl,\n hierarchyNavigator,\n enterprise,\n reportingToolbar,\n footer,\n showHealthFooter,\n buildDate,\n commitHash,\n buildVersion,\n children,\n}: DashboardLayoutProps) {\n const { toggleSidebar, state } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n const navigate = onNavigate;\n const resolveActive = (path: string) =>\n isActive ? isActive(path, activePath) : isMenuItemActive(path, activePath);\n\n return (\n <>\n <Sidebar collapsible=\"icon\" className=\"border-r-0\">\n <SidebarHeader className=\"h-16 justify-center\">\n <div className=\"flex items-center gap-3 px-2 transition-all w-full\">\n <button\n onClick={toggleSidebar}\n className=\"h-8 w-8 flex items-center justify-center hover:bg-accent rounded-lg transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring shrink-0\"\n aria-label=\"Toggle navigation\"\n >\n <PanelLeft className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n {!isCollapsed ? (\n <div className=\"flex items-center gap-2 min-w-0\">\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0\">{serviceIcon}</span>\n )}\n <span className=\"font-semibold tracking-tight truncate\">\n {serviceName}\n </span>\n </div>\n ) : null}\n </div>\n </SidebarHeader>\n\n <SidebarContent className=\"gap-0\">\n {/* Service Hub — pinned to the top of the nav */}\n {serviceHubUrl && (\n <SidebarMenu className=\"px-2 pt-2 pb-1\">\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => {\n window.location.href = serviceHubUrl;\n }}\n tooltip=\"Back to Service Hub\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span>Service Hub</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n\n {/* Cross-service links — jump straight to a sibling service */}\n <CrossServiceNav links={crossServiceLinks ?? []} />\n\n {/* Primary navigation */}\n {menuSections.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `section-${i}`}\n section={section}\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n\n {/* Admin-only navigation */}\n {isAdmin &&\n adminSections?.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `admin-${i}`}\n section={section}\n marginTop\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n </SidebarContent>\n\n {settingsPath && (\n <SidebarFooter className=\"p-2\">\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => navigate(settingsPath)}\n tooltip=\"Settings\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <Settings className=\"h-4 w-4\" />\n <span>Settings</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n )}\n </Sidebar>\n\n <SidebarInset>\n <header\n className={cn(\n \"flex items-center justify-between h-14 px-4\",\n \"bg-[#0D1220]/95 border-b border-[#1E2738]\",\n \"backdrop-blur supports-[backdrop-filter]:backdrop-blur\",\n \"sticky top-0 z-40\"\n )}\n >\n {/* LEFT: mobile trigger + service icon + hierarchy breadcrumb */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <SidebarTrigger className=\"h-9 w-9 rounded-lg bg-background shrink-0 md:hidden\" />\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0 hidden sm:block\">{serviceIcon}</span>\n )}\n {hierarchyNavigator && (\n <div className=\"min-w-0 overflow-x-auto scrollbar-none\">{hierarchyNavigator}</div>\n )}\n </div>\n\n {/* RIGHT: reporting toolbar + enterprise badge + user menu */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {reportingToolbar}\n <EnterpriseBadge enterprise={enterprise} />\n <div className=\"w-px h-6 bg-[#1E2738] hidden md:block\" />\n {user && (\n <UserMenu\n user={user}\n onLogout={onLogout}\n userSettingsUrl={userSettingsUrl}\n enterprise={enterprise}\n />\n )}\n </div>\n </header>\n\n <main className=\"flex-1 p-4 md:p-6 pb-14\">{children}</main>\n </SidebarInset>\n\n {footer}\n\n {/* Standardised platform-admin diagnostic strip — opt-in via prop. */}\n {showHealthFooter && user?.role === \"platform_admin\" && (\n <HealthFooter\n user={user}\n enterprise={enterprise}\n buildDate={buildDate}\n commitHash={commitHash}\n buildVersion={buildVersion}\n />\n )}\n </>\n );\n}\n\n/* ── Public component ── */\n\nexport function DashboardLayout(props: DashboardLayoutProps) {\n const isMobile = useIsMobile();\n return (\n <SidebarProvider defaultOpen={props.defaultOpen ?? !isMobile}>\n <DashboardShell {...props} />\n </SidebarProvider>\n );\n}\n","/**\n * CrossServiceNav — sidebar block of direct links to sibling Oplytics\n * services, rendered directly under the \"← Service Hub\" back link.\n *\n * Built on the same Sidebar primitives as the rest of the nav so it works\n * both inside the shared DashboardLayout shell and in bespoke sidebars\n * (e.g. Policy Deployment) — any consumer inside a <SidebarProvider>.\n *\n * Link entries come from `buildCrossServiceLinks` (services/crossNav), which\n * owns provisioning/gating and appends the ?oh= hierarchy selection param.\n * Navigation is a full page load, matching the Service Hub link.\n */\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"../primitives\";\nimport {\n ClipboardCheck,\n Gauge,\n LayoutGrid,\n Target,\n ExternalLink,\n type LucideIcon,\n} from \"lucide-react\";\nimport type { CrossServiceLink } from \"../services/crossNav\";\n\n/** Catalog icon names → lucide components for the live core services. */\nconst ICONS: Record<string, LucideIcon> = {\n Target,\n LayoutGrid,\n Gauge,\n ClipboardCheck,\n};\n\nexport interface CrossServiceNavProps {\n /** Display-ready links from buildCrossServiceLinks. Renders nothing when empty. */\n links: CrossServiceLink[];\n /** Section label above the links (hidden when the sidebar is collapsed). */\n label?: string;\n}\n\nexport function CrossServiceNav({\n links,\n label = \"Services\",\n}: CrossServiceNavProps) {\n const { state } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n if (links.length === 0) return null;\n\n return (\n <>\n {!isCollapsed && (\n <div className=\"px-4 pt-2\">\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {label}\n </span>\n </div>\n )}\n <SidebarMenu className=\"px-2 py-1\">\n {links.map((link) => {\n const Icon = ICONS[link.icon] ?? ExternalLink;\n return (\n <SidebarMenuItem key={link.slug}>\n <SidebarMenuButton\n onClick={() => {\n window.location.href = link.url;\n }}\n tooltip={link.name}\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <Icon className=\"h-4 w-4\" />\n <span>{link.name}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n })}\n </SidebarMenu>\n </>\n );\n}\n","/**\n * HealthFooter — thin fixed strip at the bottom of the viewport that prints\n * the current platform-admin diagnostic context. Used to verify the\n * end-to-end auth + hierarchy resolution: JWT role, JWT-stamped enterprise id,\n * the enterprise the hierarchy actually selected, and the openId of the\n * signed-in user.\n *\n * Ported from oplytics-policy-deployment/client/src/components/PolicyDeploymentLayout.tsx\n * so every subdomain that adopts the canonical shell gets the same strip\n * automatically. Use `DashboardLayout` / `OplyticsServiceShell`'s\n * `showHealthFooter` prop to auto-render this for platform_admin only —\n * or render it manually via the existing `footer` slot for full control.\n *\n * role: platform_admin jwt.eid: 30002 hierarchy.eid: 30002\n * enterprise: Lincoln Electric user: KxULoAniQat2Vm7Z9Ds32m\n */\nimport type { CSSProperties } from \"react\";\n\n// Build stamp — injected at each app's build time via Vite `define`\n// (see the app's vite.config.ts). Lets the diagnostic strip show exactly which\n// build/commit is deployed and when it was built — so you can tell at a glance\n// whether the live site is running the latest code.\ndeclare const __BUILD_DATE__: string;\ndeclare const __COMMIT_HASH__: string;\ndeclare const __APP_VERSION__: string;\n\n/** Format an ISO build timestamp as a compact \"9 Jul 07:37Z\" (UTC). */\nfunction formatBuildDate(iso?: string): string | undefined {\n if (!iso) return undefined;\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return undefined;\n const day = d.getUTCDate();\n const month = d.toLocaleString(\"en-GB\", { month: \"short\", timeZone: \"UTC\" });\n const hh = String(d.getUTCHours()).padStart(2, \"0\");\n const mm = String(d.getUTCMinutes()).padStart(2, \"0\");\n return `${day} ${month} ${hh}:${mm}Z`;\n}\n\nexport interface HealthFooterUser {\n /** JWT role string (e.g. \"platform_admin\"). */\n role?: string;\n /** The user's stable identifier (typically the JWT `openId` claim). */\n openId?: string;\n /** The enterprise id from the JWT (`enterpriseId` claim). May be null\n * for platform_admin users who haven't selected an active enterprise. */\n enterpriseId?: number | null;\n}\n\nexport interface HealthFooterEnterprise {\n /** The enterprise id the org-hierarchy is currently scoped to. */\n id?: number;\n /** Human-readable name of the scoped enterprise. */\n name?: string;\n}\n\nexport interface HealthFooterProps {\n user?: HealthFooterUser | null;\n enterprise?: HealthFooterEnterprise | null;\n /** Optional extra CSS class for the container. */\n className?: string;\n /** Optional inline style override (z-index, etc.). */\n style?: CSSProperties;\n /** Override the build version (default: build-time __APP_VERSION__). */\n buildVersion?: string;\n /** Override the build/deploy timestamp, ISO (default: build-time __BUILD_DATE__). */\n buildDate?: string;\n /** Override the deployed commit hash (default: build-time __COMMIT_HASH__). */\n commitHash?: string;\n}\n\nexport function HealthFooter({\n user,\n enterprise,\n className,\n style,\n buildVersion,\n buildDate,\n commitHash,\n}: HealthFooterProps) {\n const version =\n buildVersion ?? (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit =\n commitHash ?? (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ?? (typeof __BUILD_DATE__ !== \"undefined\" ? __BUILD_DATE__ : undefined),\n );\n const buildStamp = [version && `v${version}`, commit, builtAt].filter(Boolean).join(\" · \");\n\n return (\n <div\n className={\n \"fixed bottom-0 left-0 right-0 h-8 bg-zinc-900 border-t border-zinc-800 \" +\n \"flex items-center px-4 gap-6 text-[11px] text-zinc-400 z-50 font-mono\" +\n (className ? ` ${className}` : \"\")\n }\n style={style}\n data-slot=\"health-footer\"\n >\n <span>\n role: <span className=\"text-zinc-200\">{user?.role ?? \"–\"}</span>\n </span>\n <span>\n jwt.eid:{\" \"}\n <span className=\"text-zinc-200\">{user?.enterpriseId ?? \"null\"}</span>\n </span>\n <span>\n hierarchy.eid:{\" \"}\n <span className=\"text-zinc-200\">{enterprise?.id ?? \"–\"}</span>\n </span>\n <span>\n enterprise:{\" \"}\n <span className=\"text-zinc-200\">{enterprise?.name ?? \"–\"}</span>\n </span>\n <span>\n user: <span className=\"text-zinc-200\">{user?.openId ?? \"–\"}</span>\n </span>\n {buildStamp && (\n <span className=\"ml-auto text-zinc-500\" title=\"Deployed build (version · commit · build date UTC)\">\n build: <span className=\"text-zinc-300\">{buildStamp}</span>\n </span>\n )}\n </div>\n );\n}\n","/**\n * EnterpriseDebugLabel — TEMPORARY DIAGNOSTIC chip.\n *\n * ⚠️ This is a problem-fixing aid, NOT permanent UI. Remove once the\n * subdomain \"wrong enterprise data\" issue is resolved.\n *\n * Purpose: make it plainly visible, on every page (portal + all subdomains),\n * WHICH enterprise the loaded data actually belongs to — and surface the\n * desync that causes subdomains to show the wrong enterprise's data.\n *\n * It compares two ids that are supposed to agree:\n * • cookie — the active-enterprise cookie (ACTIVE_ENTERPRISE_COOKIE), the\n * single cross-subdomain source of truth that the backend uses\n * to scope ALL data (via applyEnterpriseOverride).\n * • sel — the UI hierarchy selection's enterprise id.\n * When they diverge, the chip turns red and shows \"≠\" — that's the bug.\n *\n * Self-contained: reads the cookie directly and reads the hierarchy selection\n * defensively, so it can render even on pages mounted outside a hierarchy\n * provider (it just omits the selection comparison in that case).\n */\nimport { useEffect, useState } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { readActiveEnterpriseCookie } from \"../hierarchy\";\n\nexport interface EnterpriseDebugLabelProps {\n /**\n * The selection's enterprise id, if the host app can supply it (e.g. from\n * useOrgHierarchy().selection.enterprise?.id). Optional — when omitted, only\n * the cookie id is shown. Passing it enables the cookie-vs-selection mismatch\n * check, which is the most useful diagnostic.\n */\n selectionEnterpriseId?: number | null;\n /**\n * Optional enterprise name to show alongside the cookie id for readability.\n */\n cookieEnterpriseName?: string | null;\n className?: string;\n}\n\nexport function EnterpriseDebugLabel({\n selectionEnterpriseId,\n cookieEnterpriseName,\n className,\n}: EnterpriseDebugLabelProps) {\n // Read the cookie on the client. Re-read on focus/visibility so a switch made\n // in another tab/subdomain is reflected without a manual reload.\n const [cookieId, setCookieId] = useState<string | null>(null);\n useEffect(() => {\n const read = () => setCookieId(readActiveEnterpriseCookie());\n read();\n window.addEventListener(\"focus\", read);\n document.addEventListener(\"visibilitychange\", read);\n return () => {\n window.removeEventListener(\"focus\", read);\n document.removeEventListener(\"visibilitychange\", read);\n };\n }, []);\n\n const cookieNum = cookieId !== null && cookieId !== \"\" ? Number(cookieId) : null;\n const hasSelection = selectionEnterpriseId !== undefined && selectionEnterpriseId !== null;\n const mismatch = hasSelection && cookieNum !== null && cookieNum !== selectionEnterpriseId;\n const noActive = cookieNum === null;\n\n return (\n <div\n className={cn(\n \"flex items-center gap-1.5 px-2 py-1 rounded-md border font-mono text-[10px] leading-none whitespace-nowrap\",\n mismatch\n ? \"bg-[#EF4444]/15 border-[#EF4444]/40 text-[#FCA5A5]\"\n : noActive\n ? \"bg-[#F59E0B]/15 border-[#F59E0B]/40 text-[#FCD34D]\"\n : \"bg-[#10B981]/10 border-[#10B981]/30 text-[#6EE7B7]\",\n className\n )}\n title=\"TEMP DIAGNOSTIC: enterprise the loaded data is scoped to (cookie) vs. the UI hierarchy selection. Remove once subdomain data issue is fixed.\"\n >\n <span aria-hidden>🔍</span>\n {noActive ? (\n <span>no active enterprise (cookie unset)</span>\n ) : (\n <>\n <span>\n data ENT #{cookieNum}\n {cookieEnterpriseName ? ` · ${cookieEnterpriseName}` : \"\"}\n </span>\n {hasSelection && (\n <span>\n {mismatch ? \" ⚠ ≠ sel #\" : \" · sel #\"}\n {selectionEnterpriseId}\n </span>\n )}\n </>\n )}\n </div>\n );\n}\n","/**\n * DashboardLayoutSkeleton — loading skeleton for DashboardLayout.\n *\n * Shows a sidebar + content skeleton while auth state is loading.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\n\nfunction Skeleton({ className }: { className?: string }) {\n return (\n <div\n className={cn(\n \"animate-pulse rounded-md bg-[#1E2738]\",\n className\n )}\n />\n );\n}\n\nexport interface DashboardLayoutSkeletonProps {\n className?: string;\n}\n\nexport function DashboardLayoutSkeleton({ className }: DashboardLayoutSkeletonProps) {\n return (\n <div className={cn(\"flex min-h-screen bg-[#0A0E1A]\", className)}>\n {/* Sidebar skeleton */}\n <div className=\"w-[260px] border-r border-[#1E2738] bg-[#0D1220] p-4 space-y-6\">\n {/* Logo area */}\n <div className=\"flex items-center gap-3 px-2\">\n <Skeleton className=\"h-8 w-8 rounded-md\" />\n <Skeleton className=\"h-4 w-24\" />\n </div>\n\n {/* Menu items */}\n <div className=\"space-y-2 px-2\">\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n </div>\n\n {/* Section divider */}\n <div className=\"px-2\">\n <Skeleton className=\"h-3 w-16 mb-3\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg mt-2\" />\n </div>\n\n {/* User profile area at bottom */}\n <div className=\"absolute bottom-4 left-4 right-4\">\n <div className=\"flex items-center gap-3 px-1\">\n <Skeleton className=\"h-8 w-8 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"h-2 w-32\" />\n </div>\n </div>\n </div>\n </div>\n\n {/* Main content skeleton */}\n <div className=\"flex-1 p-6 space-y-4\">\n {/* Page header */}\n <div className=\"flex items-center justify-between\">\n <Skeleton className=\"h-8 w-48 rounded-lg\" />\n <Skeleton className=\"h-9 w-24 rounded-lg\" />\n </div>\n\n {/* Stats cards */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n </div>\n\n {/* Main content area */}\n <Skeleton className=\"h-64 rounded-xl\" />\n\n {/* Table-like area */}\n <div className=\"space-y-2\">\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n </div>\n </div>\n </div>\n );\n}\n","/**\n * SharedPageHeader — standard page header for all Oplytics subdomains.\n *\n * Displays: page title, optional subtitle, optional breadcrumbs,\n * optional action buttons, and optional HierarchyNavigator.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent, #E2E8F0 text, #8890A0 muted\n */\nimport { cn } from \"../utils/cn\";\nimport { ChevronLeft } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\nexport interface SharedPageHeaderProps {\n /** Page title */\n title: string;\n /** Optional subtitle / description */\n subtitle?: string;\n /** Optional icon to display before the title */\n icon?: ReactNode;\n /** Optional back button handler — shows a back arrow when provided */\n onBack?: () => void;\n /** Optional action buttons to render on the right side */\n actions?: ReactNode;\n /** Optional breadcrumb trail (e.g. HierarchyNavigator) */\n breadcrumbs?: ReactNode;\n /** Whether to show a bottom border (default: true) */\n showBorder?: boolean;\n /** Compact mode — smaller spacing and text (default: false) */\n compact?: boolean;\n /** Additional CSS classes */\n className?: string;\n /** Children rendered below the header row */\n children?: ReactNode;\n}\n\nexport function SharedPageHeader({\n title,\n subtitle,\n icon,\n onBack,\n actions,\n breadcrumbs,\n showBorder = true,\n compact = false,\n className,\n children,\n}: SharedPageHeaderProps) {\n return (\n <div\n className={cn(\n \"w-full\",\n showBorder && \"border-b border-[#1E2738]\",\n compact ? \"px-4 py-3\" : \"px-6 py-4\",\n className\n )}\n >\n {/* Breadcrumbs row */}\n {breadcrumbs && (\n <div className={cn(\"mb-2\", compact && \"mb-1\")}>\n {breadcrumbs}\n </div>\n )}\n\n {/* Main header row */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {/* Back button */}\n {onBack && (\n <button\n onClick={onBack}\n className={cn(\n \"shrink-0 p-1.5 rounded-md\",\n \"text-[#8890A0] hover:text-[#E2E8F0] hover:bg-[#1E2738]\",\n \"transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n aria-label=\"Go back\"\n >\n <ChevronLeft className={compact ? \"h-4 w-4\" : \"h-5 w-5\"} />\n </button>\n )}\n\n {/* Icon */}\n {icon && (\n <div className=\"shrink-0 text-[#8C34E9]\">\n {icon}\n </div>\n )}\n\n {/* Title + subtitle */}\n <div className=\"min-w-0\">\n <h1\n className={cn(\n \"font-semibold text-[#E2E8F0] truncate\",\n \"font-[Montserrat,sans-serif]\",\n compact ? \"text-lg\" : \"text-xl\"\n )}\n >\n {title}\n </h1>\n {subtitle && (\n <p\n className={cn(\n \"text-[#8890A0] truncate mt-0.5\",\n \"font-[Space_Grotesk,sans-serif]\",\n compact ? \"text-xs\" : \"text-sm\"\n )}\n >\n {subtitle}\n </p>\n )}\n </div>\n </div>\n\n {/* Action buttons */}\n {actions && (\n <div className=\"shrink-0 flex items-center gap-2\">\n {actions}\n </div>\n )}\n </div>\n\n {/* Optional children below header */}\n {children && (\n <div className={cn(\"mt-3\", compact && \"mt-2\")}>\n {children}\n </div>\n )}\n </div>\n );\n}\n","/**\n * SharedFooter — standard footer for all Oplytics subdomains.\n *\n * Displays: Oplytics branding, service links, legal links, copyright.\n * Consistent across all subdomains.\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #8890A0 text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport type { ReactNode } from \"react\";\n\ndeclare const __APP_VERSION__: string;\n\n/* ── Service definitions ── */\n\nexport interface FooterService {\n name: string;\n slug: string;\n /** Local route within the portal (e.g. /services/sqdcp) */\n localRoute: string;\n /** External URL for the subdomain (e.g. https://sqdcp.oplytics.digital) */\n externalUrl?: string;\n}\n\n/** Standard Oplytics service list */\nexport const FOOTER_SERVICES: FooterService[] = [\n { name: \"SQDCP\", slug: \"sqdcp\", localRoute: \"/services/sqdcp\", externalUrl: \"https://sqdcp.oplytics.digital\" },\n { name: \"OEE Manager\", slug: \"oee-manager\", localRoute: \"/services/oee-manager\", externalUrl: \"https://oee.oplytics.digital\" },\n { name: \"Policy Deployment\", slug: \"policy-deployment\", localRoute: \"/services/policy-deployment\", externalUrl: \"https://policy.oplytics.digital\" },\n { name: \"Action Manager\", slug: \"action-manager\", localRoute: \"/services/action-manager\", externalUrl: \"https://actions.oplytics.digital\" },\n { name: \"Safety Manager\", slug: \"safety-manager\", localRoute: \"/services/safety-manager\" },\n { name: \"Connect\", slug: \"connect\", localRoute: \"/services/connect\", externalUrl: \"https://connect.oplytics.digital\" },\n];\n\n/* ── Footer props ── */\n\nexport interface SharedFooterProps {\n /** Override the default service list */\n services?: FooterService[];\n /** Show pricing link (default: true) */\n showPricing?: boolean;\n /** Show sign-in link (default: true) */\n showSignIn?: boolean;\n /** Handler for service link clicks — receives the service */\n onServiceClick?: (service: FooterService, e: React.MouseEvent) => void;\n /** Handler for pricing link click */\n onPricingClick?: (e: React.MouseEvent) => void;\n /** Handler for sign-in link click */\n onSignInClick?: (e: React.MouseEvent) => void;\n /** Additional content to render in the footer */\n children?: ReactNode;\n /** CSS class for the footer container */\n className?: string;\n /** Whether to use external URLs (subdomain links) or local routes */\n useExternalUrls?: boolean;\n /** Current year override (default: auto) */\n year?: number;\n}\n\n/* ── Component ── */\n\nexport function SharedFooter({\n services = FOOTER_SERVICES,\n showPricing = true,\n showSignIn = true,\n onServiceClick,\n onPricingClick,\n onSignInClick,\n children,\n className,\n useExternalUrls = false,\n year,\n}: SharedFooterProps) {\n const currentYear = year ?? new Date().getFullYear();\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738]\",\n \"bg-[#0D1220] text-[#8890A0]\",\n \"font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n <div className=\"max-w-7xl mx-auto px-6 py-10\">\n {/* Top section: Logo + columns */}\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-8 mb-8\">\n {/* Brand column */}\n <div className=\"md:col-span-1\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div\n className=\"w-7 h-7 rounded-full flex items-center justify-center\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold text-xs\">O</span>\n </div>\n <span className=\"text-[#E2E8F0] font-semibold text-sm font-[Montserrat,sans-serif]\">\n Oplytics.digital\n </span>\n </div>\n <p className=\"text-xs text-[#596475] leading-relaxed\">\n Operational intelligence platform for manufacturing excellence.\n </p>\n </div>\n\n {/* Services column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Services\n </h4>\n <ul className=\"space-y-2\">\n {services.map((service) => (\n <li key={service.slug}>\n <a\n href={useExternalUrls && service.externalUrl ? service.externalUrl : service.localRoute}\n onClick={onServiceClick ? (e) => onServiceClick(service, e) : undefined}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n {service.name}\n </a>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Company column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Company\n </h4>\n <ul className=\"space-y-2\">\n {showPricing && (\n <li>\n <a\n href=\"/pricing\"\n onClick={onPricingClick}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Pricing\n </a>\n </li>\n )}\n <li>\n <a\n href=\"/about\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n About\n </a>\n </li>\n <li>\n <a\n href=\"/contact\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Contact\n </a>\n </li>\n </ul>\n </div>\n\n {/* Legal column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Legal\n </h4>\n <ul className=\"space-y-2\">\n <li>\n <a\n href=\"/privacy\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Privacy Policy\n </a>\n </li>\n <li>\n <a\n href=\"/terms\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Terms of Service\n </a>\n </li>\n </ul>\n {showSignIn && (\n <div className=\"mt-4\">\n <a\n href=\"/login\"\n onClick={onSignInClick}\n className=\"text-xs text-[#8C34E9] hover:text-[#A855F7] transition-colors font-medium\"\n >\n Sign In\n </a>\n </div>\n )}\n </div>\n </div>\n\n {/* Custom content */}\n {children}\n\n {/* Bottom bar */}\n <div className=\"pt-6 border-t border-[#1E2738] flex flex-col sm:flex-row items-center justify-between gap-3\">\n <p className=\"text-xs text-[#596475]\">\n &copy; {currentYear} Oplytics.digital. All rights reserved.\n </p>\n <p className=\"text-xs text-[#3E4A5C]\">\n Built for manufacturing excellence\n </p>\n {typeof __APP_VERSION__ !== \"undefined\" && (\n <p className=\"text-xs text-[#3E4A5C]\">\n Platform v{__APP_VERSION__}\n </p>\n )}\n </div>\n </div>\n </footer>\n );\n}\n","/**\n * ServiceFooter — slim status bar for all Oplytics subdomain services.\n *\n * ~36px horizontal strip: [Logo + Tagline] [Health Monitor] [Copyright + Version]\n * Health monitor is visible only when showHealthMonitor is true (typically platform_admin).\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #596475/#3E4A5C text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\n\ndeclare const __APP_VERSION__: string;\ndeclare const __BUILD_DATE__: string;\ndeclare const __COMMIT_HASH__: string;\n\nexport interface ServiceFooterProps {\n /** Enterprise ID for health monitor */\n enterpriseId?: number | null;\n /** Enterprise display name */\n enterpriseName?: string;\n /** Current user's role */\n userRole?: string;\n /** Current user's email */\n userEmail?: string;\n /** Deployment environment */\n environment?: \"production\" | \"staging\" | \"development\";\n /** Build version / commit hash */\n buildVersion?: string;\n /**\n * Build/deploy timestamp — when the currently-served bundle was built.\n * ISO string (e.g. \"2026-07-09T07:37:31Z\"). Falls back to the build-time\n * __BUILD_DATE__ constant. Rendered as a short date+time so you can tell at\n * a glance whether the deployed code is the latest (root-cause aid).\n */\n buildDate?: string;\n /** Short commit hash of the deployed build (e.g. \"c3af23f\"). Falls back to __COMMIT_HASH__. */\n commitHash?: string;\n /** Show health monitor strip (default: false) */\n showHealthMonitor?: boolean;\n /** CSS class for the footer container */\n className?: string;\n}\n\n/** Format an ISO build timestamp as a compact \"9 Jul 07:37\" (UTC) for the footer. */\nfunction formatBuildDate(iso?: string): string | undefined {\n if (!iso) return undefined;\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return undefined;\n const day = d.getUTCDate();\n const month = d.toLocaleString(\"en-GB\", { month: \"short\", timeZone: \"UTC\" });\n const hh = String(d.getUTCHours()).padStart(2, \"0\");\n const mm = String(d.getUTCMinutes()).padStart(2, \"0\");\n return `${day} ${month} ${hh}:${mm}Z`;\n}\n\nconst ENV_LABELS: Record<string, { label: string; color: string }> = {\n production: { label: \"prod\", color: \"#10b981\" },\n staging: { label: \"stg\", color: \"#f59e0b\" },\n development: { label: \"dev\", color: \"#8C34E9\" },\n};\n\nexport function ServiceFooter({\n enterpriseId,\n enterpriseName,\n userRole,\n userEmail,\n environment,\n buildVersion,\n buildDate,\n commitHash,\n showHealthMonitor = false,\n className,\n}: ServiceFooterProps) {\n const currentYear = new Date().getFullYear();\n const env = environment ? ENV_LABELS[environment] : null;\n const version = buildVersion ?? (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit = commitHash ?? (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ?? (typeof __BUILD_DATE__ !== \"undefined\" ? __BUILD_DATE__ : undefined),\n );\n // A single \"build stamp\" combining version + commit + build date, e.g.\n // \"v.0.9.2 · c3af23f · 9 Jul 07:37Z\". Any missing piece is simply omitted.\n const buildStamp = [version && `v.${version}`, commit, builtAt]\n .filter(Boolean)\n .join(\" · \");\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738] bg-[#0D1220]\",\n \"flex items-center justify-between px-4\",\n \"h-9 min-h-[36px] shrink-0\",\n \"font-[Montserrat,sans-serif]\",\n className,\n )}\n >\n {/* Left: Logo + Tagline */}\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"w-5 h-5 rounded-full flex items-center justify-center shrink-0\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold\" style={{ fontSize: 9 }}>O</span>\n </div>\n <span className=\"text-[10px] text-[#596475] whitespace-nowrap hidden sm:inline\">\n Operational Excellence. One Digital Platform.\n </span>\n </div>\n\n {/* Center: Health Monitor (admin only) */}\n {showHealthMonitor && (\n <div className=\"flex items-center gap-1.5 text-[10px] text-[#3E4A5C] min-w-0 overflow-hidden\">\n {enterpriseName && (\n <>\n <span className=\"whitespace-nowrap\">\n <span className=\"text-[#596475]\">{enterpriseName}</span>\n {enterpriseId != null && (\n <span className=\"text-[#3E4A5C]\"> ({enterpriseId})</span>\n )}\n </span>\n <Separator />\n </>\n )}\n {userEmail && (\n <>\n <span className=\"whitespace-nowrap text-[#596475]\">{userEmail}</span>\n {userRole && (\n <span className=\"text-[#3E4A5C]\"> ({userRole})</span>\n )}\n <Separator />\n </>\n )}\n {env && (\n <>\n <span className=\"whitespace-nowrap\">\n ENV:{\" \"}\n <span style={{ color: env.color }} className=\"font-semibold\">\n {env.label}\n </span>\n </span>\n <Separator />\n </>\n )}\n {buildStamp && (\n <span className=\"whitespace-nowrap text-[#3E4A5C] font-mono\" title=\"Deployed build\">\n {buildStamp}\n </span>\n )}\n </div>\n )}\n\n {/* Right: Copyright + build stamp (when health monitor hidden) */}\n <div className=\"flex items-center gap-2 text-[10px] text-[#3E4A5C] whitespace-nowrap shrink-0\">\n <span>&copy; {currentYear} Oplytics.digital</span>\n {!showHealthMonitor && buildStamp && (\n <>\n <Separator />\n <span className=\"font-mono\" title=\"Deployed build\">{buildStamp}</span>\n </>\n )}\n </div>\n </footer>\n );\n}\n\nfunction Separator() {\n return (\n <span className=\"text-[#1E2738] select-none\" aria-hidden>\n |\n </span>\n );\n}\n","/**\n * createServiceLayout — Factory function that generates a complete DashboardLayout\n * for any Oplytics subdomain from a SharedSidebarConfig.\n *\n * This is the primary API for subdomains. Instead of each subdomain maintaining\n * its own 200-350 line DashboardLayout, they call:\n *\n * const SQDCPLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n *\n * And get a fully-featured layout with:\n * - Resizable sidebar with all standard features\n * - SharedPageHeader integration\n * - ReportingToolbar integration (optional)\n * - HierarchyProvider wrapping (optional)\n * - Auth gate with loading skeleton + sign-in fallback (optional)\n * - User footer with logout dropdown\n *\n * @module shared/components/createServiceLayout\n */\n\nimport React from \"react\";\nimport type { SharedSidebarConfig, MenuSection } from \"./SharedSidebar\";\nimport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize } from \"./SharedSidebar\";\n\n/* ── Service Layout Configuration ── */\n\nexport interface ServiceLayoutConfig extends SharedSidebarConfig {\n /** Show SharedPageHeader above content (default: true) */\n showPageHeader?: boolean;\n /** SharedPageHeader props — passed through when showPageHeader is true */\n pageHeaderProps?: {\n showHierarchy?: boolean;\n hierarchyMaxDepth?: number;\n showSidebarTrigger?: boolean;\n };\n /** Show ReportingToolbar in the header (default: false) */\n showReportingToolbar?: boolean;\n /** Module name passed to ReportingToolbar */\n reportingModuleName?: string;\n /** Wrap layout in HierarchyProvider (default: false) */\n wrapHierarchyProvider?: boolean;\n /** Show auth gate — loading skeleton + sign-in fallback (default: true) */\n showAuthGate?: boolean;\n /** Report submit handler */\n onReportSubmit?: (report: any) => Promise<void> | void;\n /** Feedback submit handler */\n onFeedbackSubmit?: (feedback: any) => Promise<void> | void;\n}\n\n/**\n * Creates a service-specific layout component from configuration.\n *\n * NOTE: This is a blueprint/factory specification. The actual React component\n * is assembled by each subdomain using their local UI primitives (Sidebar,\n * SidebarProvider, etc.) since these are not shared across subdomains yet.\n *\n * When subdomain consolidation (#311) is complete, this factory will directly\n * render the shared UI primitives. Until then, subdomains use this config\n * to standardise their DashboardLayout implementations.\n *\n * @example\n * ```tsx\n * // In SQDCP's DashboardLayout.tsx:\n * import { sqdcpLayoutConfig } from \"./sqdcp-config\";\n * // Use sqdcpLayoutConfig.menuSections, .backLink, etc. to build the layout\n * ```\n */\nexport function defineServiceLayout(config: ServiceLayoutConfig): ServiceLayoutConfig {\n return {\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n showPageHeader: true,\n showReportingToolbar: false,\n wrapHierarchyProvider: false,\n showAuthGate: true,\n ...config,\n storageKeyPrefix: config.storageKeyPrefix || config.serviceName.toLowerCase().replace(/\\s+/g, \"-\"),\n };\n}\n\n/* ── Pre-built Service Configurations ── */\n\n// These are the canonical configurations for each subdomain.\n// Subdomains should import and use these instead of defining their own.\n\nexport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize };\n","/**\n * Canonical Service Layout Configurations for all Oplytics subdomains.\n *\n * Each subdomain imports its config from here and uses it to build its\n * DashboardLayout. This ensures consistency across all services while\n * allowing per-service customisation (menu items, features, etc.).\n *\n * When subdomain consolidation (#311) is complete, these configs will\n * drive the shared createServiceLayout factory directly.\n *\n * @module shared/components/serviceConfigs\n */\n\nimport type { ServiceLayoutConfig } from \"./createServiceLayout\";\n\n/* ── Icon placeholder types ── */\n// Icons are imported locally by each subdomain from lucide-react.\n// These configs use string identifiers that map to icon components.\n\nexport interface MenuItemConfig {\n iconName: string;\n label: string;\n path: string;\n}\n\nexport interface MenuSectionConfig {\n label?: string;\n items: MenuItemConfig[];\n adminOnly?: boolean;\n}\n\nexport interface ServiceConfig {\n serviceName: string;\n serviceAbbreviation: string;\n serviceIconName: string;\n menuSections: MenuSectionConfig[];\n backLink?: {\n label: string;\n path: string;\n };\n showPageHeader: boolean;\n showReportingToolbar: boolean;\n reportingModuleName?: string;\n wrapHierarchyProvider: boolean;\n showAuthGate: boolean;\n storageKeyPrefix: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}\n\n/* ── SQDCP ── */\n\nexport const sqdcpConfig: ServiceConfig = {\n serviceName: \"SQDCP\",\n serviceAbbreviation: \"S\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"PenLine\", label: \"Data Entry\", path: \"/entry\" },\n { iconName: \"ClipboardList\", label: \"Action Tracker\", path: \"/actions\" },\n { iconName: \"FileText\", label: \"Reports\", path: \"/reports\" },\n { iconName: \"Settings\", label: \"Admin\", path: \"/admin\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"SQDCP\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"sqdcp-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── OEE Manager ── */\n\nexport const oeeManagerConfig: ServiceConfig = {\n serviceName: \"OEE Manager\",\n serviceAbbreviation: \"o\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"BarChart3\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Database\", label: \"Data Input\", path: \"/data-input\" },\n { iconName: \"Clock\", label: \"Downtime\", path: \"/downtime\" },\n { iconName: \"Settings\", label: \"Settings\", path: \"/settings\" },\n ],\n },\n ],\n backLink: {\n label: \"Back to Portal\",\n path: \"https://portal.oplytics.digital\",\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"OEE Enterprise Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"oee-sidebar\",\n defaultWidth: 240,\n minWidth: 200,\n maxWidth: 400,\n};\n\n/* ── Action Manager ── */\n\nexport const actionManagerConfig: ServiceConfig = {\n serviceName: \"Action Manager\",\n serviceAbbreviation: \"A\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Teams\", path: \"/teams\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Action Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"actionmanager-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Business Hub ── */\n\nexport const businessHubConfig: ServiceConfig = {\n serviceName: \"Business Hub\",\n serviceAbbreviation: \"B\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Analytics\", path: \"/analytics\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Business Hub\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"businesshub-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Policy Deployment ── */\n\nexport const policyDeploymentConfig: ServiceConfig = {\n serviceName: \"Policy Deployment\",\n serviceAbbreviation: \"PD\",\n serviceIconName: \"FileStack\",\n menuSections: [\n {\n label: \"Analysis\",\n items: [\n { iconName: \"LayoutGrid\", label: \"Dashboard\", path: \"/app/policy-deployment\" },\n { iconName: \"Grid3X3\", label: \"X-Matrix\", path: \"/app/policy-deployment/xmatrix\" },\n { iconName: \"BarChart3\", label: \"Bowling Chart\", path: \"/app/policy-deployment/bowling\" },\n { iconName: \"FolderKanban\", label: \"Action Plans\", path: \"/app/policy-deployment/actions\" },\n { iconName: \"GitBranchPlus\", label: \"Catchball\", path: \"/app/policy-deployment/catchball\" },\n { iconName: \"Target\", label: \"Deployments\", path: \"/app/policy-deployment/deployments\" },\n ],\n },\n {\n label: \"Administration\",\n adminOnly: true,\n items: [\n { iconName: \"Settings\", label: \"Manage Policy\", path: \"/app/policy-deployment/manage\" },\n { iconName: \"Plug\", label: \"Integrations\", path: \"/app/policy-deployment/integrations\" },\n ],\n },\n ],\n backLink: {\n label: \"Service Hub\",\n path: \"/app\",\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"Policy Deployment\",\n wrapHierarchyProvider: true,\n showAuthGate: true,\n storageKeyPrefix: \"policy-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── All configs indexed by service key ── */\n\nexport const SERVICE_CONFIGS = {\n sqdcp: sqdcpConfig,\n oeeManager: oeeManagerConfig,\n actionManager: actionManagerConfig,\n businessHub: businessHubConfig,\n policyDeployment: policyDeploymentConfig,\n} as const;\n\nexport type ServiceKey = keyof typeof SERVICE_CONFIGS;\n"],"mappings":"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","names":["jsx","jsxs","useEffect","useState","Fragment","jsx","jsxs","useState","useEffect","Fragment","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs","formatBuildDate"]}