@pablo2410/shared-ui 0.25.1 → 0.27.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.
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode, CSSProperties } from 'react';
3
- import { C as CrossServiceLink } from './crossNav-Bz0RXU_F.js';
3
+ import { C as CrossServiceLink } from './crossNav-DBX4Eyx7.js';
4
4
 
5
5
  /**
6
6
  * SharedSidebar — Config-driven sidebar component for all Oplytics subdomains.
@@ -41,6 +41,15 @@ interface MenuItem {
41
41
  * Omit for the default muted/active-primary treatment every other item uses.
42
42
  */
43
43
  accentClassName?: string;
44
+ /**
45
+ * Optional tour spotlight target, rendered as `data-tour-target` on this
46
+ * item's sidebar button so a tour step whose `spotlight.selector` matches it
47
+ * highlights the item. The sidebar is on every page, so such a step works
48
+ * wherever the user starts the tour. On mobile the sidebar only exists while
49
+ * its drawer is open, so the step falls back to the centred panel there.
50
+ * Omit for no attribute.
51
+ */
52
+ tourTarget?: string;
44
53
  }
45
54
  interface MenuSection {
46
55
  /** Section label displayed above items */
@@ -15,7 +15,6 @@ var SERVICE_CATALOG = [
15
15
  description: "Hoshin Kanri and X-matrix planning. Cascade strategic objectives through every level of your organisation with live tracking and bowling charts.",
16
16
  icon: "Target",
17
17
  accentColor: "#F59E0B",
18
- status: "live",
19
18
  category: "core",
20
19
  subdomainLabel: "policy",
21
20
  onboarding: {
@@ -50,7 +49,6 @@ var SERVICE_CATALOG = [
50
49
  description: "Digital SQDCP boards that drive daily accountability. Safety, Quality, Delivery, Cost, and People metrics at a glance for every team.",
51
50
  icon: "LayoutGrid",
52
51
  accentColor: "#1DB8CE",
53
- status: "live",
54
52
  category: "core",
55
53
  subdomainLabel: "sqdcp",
56
54
  onboarding: {
@@ -58,7 +56,6 @@ var SERVICE_CATALOG = [
58
56
  highlights: [
59
57
  "Rooms \u2014 one per team, tiered (Team Huddle \u2192 Area/Plant Review \u2192 Monthly Review), each feeding the one above.",
60
58
  "Data Entry \u2014 where actuals get logged against each pillar's metrics.",
61
- "Safety Manager / Quality Manager tabs \u2014 the deeper pillar-specific views inside SQDCP.",
62
59
  "If AI Facilitator is on, Opi runs the huddle itself \u2014 drafts the line for each red pillar, opens the action review by name."
63
60
  ],
64
61
  features: [
@@ -69,10 +66,6 @@ var SERVICE_CATALOG = [
69
66
  {
70
67
  name: "Data Entry",
71
68
  detail: "Where actuals get logged against each pillar's metrics \u2014 this is the one habit that makes everything else here true; skip it and the board just goes stale, not red."
72
- },
73
- {
74
- name: "Safety & Quality Manager",
75
- detail: "The deeper pillar-specific views inside SQDCP \u2014 ISO-aligned gap analysis for Safety and Quality, one level below the daily board for when a red needs more than a glance."
76
69
  }
77
70
  ],
78
71
  firstAction: { label: "Open Rooms", path: "/" },
@@ -86,7 +79,6 @@ var SERVICE_CATALOG = [
86
79
  description: "Monitor Overall Equipment Effectiveness in real-time. Track availability, performance, and quality metrics across your entire production line.",
87
80
  icon: "Gauge",
88
81
  accentColor: "#8C34E9",
89
- status: "live",
90
82
  category: "core",
91
83
  subdomainLabel: "oeemanager"
92
84
  // No `onboarding` block yet — held back for a later cohort (2026-09-11 decision).
@@ -98,7 +90,6 @@ var SERVICE_CATALOG = [
98
90
  description: "Capture, assign, and track corrective and preventive actions from any source. Ensure nothing falls through the cracks.",
99
91
  icon: "ClipboardCheck",
100
92
  accentColor: "#22C55E",
101
- status: "live",
102
93
  category: "core",
103
94
  subdomainLabel: "actionmanager",
104
95
  onboarding: {
@@ -125,62 +116,11 @@ var SERVICE_CATALOG = [
125
116
  firstAction: { label: "Open Board", path: "/board" },
126
117
  emptyStateHint: "Zero actions yet \u2014 say this is normal on day one, actions will start showing up as SQDCP and Policy Deployment generate them, rather than describing an empty board as broken."
127
118
  }
128
- },
129
- {
130
- slug: "connect",
131
- name: "OplyticsConnect",
132
- tagline: "Integrate machines, sensors, and systems",
133
- description: "Industrial IoT connectivity layer. Connect PLCs, sensors, and legacy systems to the Oplytics platform with zero-code configuration.",
134
- icon: "Plug",
135
- accentColor: "#1DB8CE",
136
- status: "in-development",
137
- category: "core",
138
- subdomainLabel: "connect"
139
- },
140
- // ─── HUB SERVICES ───
141
- {
142
- slug: "safety-manager",
143
- name: "Safety Manager",
144
- tagline: "Proactive safety culture, powered by data",
145
- description: "Incident reporting, hazard tracking, safety observations, and compliance management in one unified platform.",
146
- icon: "Shield",
147
- accentColor: "#EF4444",
148
- status: "coming-soon",
149
- category: "hub",
150
- subdomainLabel: "safetymanager"
151
- },
152
- {
153
- slug: "quality-manager",
154
- name: "Quality Manager",
155
- tagline: "End-to-end quality assurance",
156
- description: "Non-conformance tracking, CAPA management, audit scheduling, and quality metrics dashboards.",
157
- icon: "CheckCircle",
158
- accentColor: "#22C55E",
159
- status: "coming-soon",
160
- category: "hub",
161
- subdomainLabel: null
162
- },
163
- {
164
- slug: "certification-manager",
165
- name: "Certification Manager",
166
- tagline: "Stay audit-ready, always",
167
- description: "Manage ISO, IATF, and other certification requirements. Document control, audit trails, and compliance tracking.",
168
- icon: "Award",
169
- accentColor: "#F97316",
170
- status: "coming-soon",
171
- category: "hub",
172
- subdomainLabel: null
173
119
  }
174
120
  ];
175
121
  var coreServices = SERVICE_CATALOG.filter((s) => s.category === "core");
176
122
  var hubServices = SERVICE_CATALOG.filter((s) => s.category === "hub");
177
- var liveServices = SERVICE_CATALOG.filter((s) => s.status === "live");
178
- var inDevServices = SERVICE_CATALOG.filter(
179
- (s) => s.status === "in-development"
180
- );
181
- var comingSoonServices = SERVICE_CATALOG.filter(
182
- (s) => s.status === "coming-soon"
183
- );
123
+ var liveServices = SERVICE_CATALOG;
184
124
  var onboardingTourServices = SERVICE_CATALOG.filter(
185
125
  (s) => s.onboarding !== void 0
186
126
  );
@@ -211,7 +151,7 @@ function buildCrossServiceLinks(options) {
211
151
  const granted = new Set(grantedSlugs.map(normalizeServiceSlug));
212
152
  const current = normalizeServiceSlug(currentSlug);
213
153
  return SERVICE_CATALOG.filter(
214
- (svc) => svc.status === "live" && svc.subdomainLabel !== null && svc.slug !== current && (isAdmin || granted.has(svc.slug))
154
+ (svc) => svc.subdomainLabel !== null && svc.slug !== current && (isAdmin || granted.has(svc.slug))
215
155
  ).map((svc) => ({
216
156
  slug: svc.slug,
217
157
  name: svc.name,
@@ -226,8 +166,6 @@ export {
226
166
  coreServices,
227
167
  hubServices,
228
168
  liveServices,
229
- inDevServices,
230
- comingSoonServices,
231
169
  onboardingTourServices,
232
170
  getServiceBySlug,
233
171
  getServiceUrl,
@@ -236,4 +174,4 @@ export {
236
174
  normalizeServiceSlug,
237
175
  buildCrossServiceLinks
238
176
  };
239
- //# sourceMappingURL=chunk-MGTOQY6O.js.map
177
+ //# sourceMappingURL=chunk-45ZQDLPA.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 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 /** 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 * Scripted content for the Opi onboarding tour (see @pablo2410/shared-ui/tour).\n * Optional and deliberately sparse — only services with a real, verified\n * first-run path should have this filled in. A service with no `onboarding`\n * block is skipped by the tour entirely rather than shown with placeholder\n * copy. Marketing/other consumers ignore this field.\n */\n onboarding?: ServiceOnboardingContent;\n}\n\nexport interface ServiceOnboardingContent {\n /** Opi's scripted opening line for this step — the always-available fallback if the live LLM narration call fails or AI is gated off. */\n hook: string;\n /** 2-4 short bullets: what's actually here, in plain language. Superseded\n * by `features` for display when both are present — kept for back-compat\n * and as the fallback shape for any service not yet upgraded. */\n highlights: string[];\n /**\n * Named feature call-outs, richer than a flat bullet — one sentence each\n * on what it does AND why the user should care, not just what it's\n * called. Convention (not enforced): aim for 3 per service. Each `name`\n * doubles as the natural spotlight-target label a later in-app tour\n * (inside the service's own subdomain, once the tour follows the user\n * in) can reuse — this is deliberately the same data written once.\n */\n features?: ServiceOnboardingFeature[];\n /** The one thing the step ends on. */\n firstAction: {\n label: string;\n /** Path relative to the service's own root (e.g. \"/xmatrix\"). */\n path: string;\n };\n /** What Opi should say instead when the service has no data yet for this enterprise (empty-state framing), rather than pointing at the normal firstAction. */\n emptyStateHint?: string;\n}\n\nexport interface ServiceOnboardingFeature {\n /** Short feature name, e.g. \"X-Matrix\". */\n name: string;\n /** One deeper sentence — what it does and why the user should care. */\n detail: string;\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 category: \"core\",\n subdomainLabel: \"policy\",\n onboarding: {\n hook: \"This is where your strategy becomes a plan everyone can see. Long-term goals break into this year's objectives, objectives break into projects, and projects carry the metrics that prove it's working.\",\n highlights: [\n \"X-Matrix — the rollup view: goals, objectives, and projects, with dots showing which project moves which goal.\",\n \"Catchball — where the plan actually gets built: negotiate objectives and projects up and down the org, add a metric to a project.\",\n \"Actions — every task that comes out of the plan, tracked to closure.\",\n ],\n features: [\n {\n name: \"X-Matrix\",\n detail:\n \"The whole plan at a glance — goals, objectives, and projects on one grid, with dots showing exactly which project moves which goal, so nobody's guessing why a project exists.\",\n },\n {\n name: \"Catchball\",\n detail:\n \"Where the plan actually gets negotiated — objectives and projects get pitched up and down the org until everyone agrees, and that's also where a project picks up the metric it'll be measured on.\",\n },\n {\n name: \"Actions\",\n detail:\n \"Every task the plan generates, tracked to closure by pillar — so a strategic project never quietly stalls because nobody was assigned the next step.\",\n },\n ],\n firstAction: { label: \"Open X-Matrix\", path: \"/xmatrix\" },\n emptyStateHint:\n \"No plan yet for this enterprise — say so plainly and point at Catchball's \\\"Create your first plan\\\" instead of X-Matrix's empty state.\",\n },\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 category: \"core\",\n subdomainLabel: \"sqdcp\",\n onboarding: {\n hook: \"This is your daily board — Safety, Quality, Delivery, Cost, People, live. Every team has a room; the huddle happens here, and the numbers you log in Data Entry are what turn the board green or red.\",\n highlights: [\n \"Rooms — one per team, tiered (Team Huddle → Area/Plant Review → Monthly Review), each feeding the one above.\",\n \"Data Entry — where actuals get logged against each pillar's metrics.\",\n \"If AI Facilitator is on, Opi runs the huddle itself — drafts the line for each red pillar, opens the action review by name.\",\n ],\n features: [\n {\n name: \"Rooms\",\n detail:\n \"One room per team, tiered from Team Huddle up to Area/Plant Review to Monthly Review — each level feeds the one above, so a problem raised on the floor is visible all the way up without anyone re-typing it into a slide deck.\",\n },\n {\n name: \"Data Entry\",\n detail:\n \"Where actuals get logged against each pillar's metrics — this is the one habit that makes everything else here true; skip it and the board just goes stale, not red.\",\n },\n ],\n firstAction: { label: \"Open Rooms\", path: \"/\" },\n emptyStateHint:\n \"No rooms configured yet — this needs an admin to set up rooms first; point at Admin rather than Rooms.\",\n },\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 category: \"core\",\n subdomainLabel: \"oeemanager\",\n // No `onboarding` block yet — held back for a later cohort (2026-09-11 decision).\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 category: \"core\",\n subdomainLabel: \"actionmanager\",\n onboarding: {\n hook: \"Every action from every service lands here — SQDCP huddles, Policy Deployment projects, safety reports, all in one place, tracked until someone closes it out.\",\n highlights: [\n \"Board — kanban view, the default way most people work actions day to day.\",\n \"List and Calendar — the same actions, sorted or scheduled instead.\",\n \"Improvement (A3) — structured 7-box root-cause work for the actions that need more than a quick fix.\",\n ],\n features: [\n {\n name: \"Board\",\n detail:\n \"The default working view — a kanban of every open action, so a team can see what's moving and what's stuck without opening a single report.\",\n },\n {\n name: \"List & Calendar\",\n detail:\n \"The same actions, just sorted or scheduled instead of columned — List for triaging a long backlog, Calendar for seeing what's due this week before it becomes overdue.\",\n },\n {\n name: \"Improvement (A3)\",\n detail:\n \"Structured 7-box root-cause work for the actions that keep coming back — Background through Follow-up, one page, so a real fix gets the same rigor a quick fix doesn't need.\",\n },\n ],\n firstAction: { label: \"Open Board\", path: \"/board\" },\n emptyStateHint:\n \"Zero actions yet — say this is normal on day one, actions will start showing up as SQDCP and Policy Deployment generate them, rather than describing an empty board as broken.\",\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Derived helpers\n// ---------------------------------------------------------------------------\n\nexport const coreServices = SERVICE_CATALOG.filter(s => s.category === \"core\");\nexport const hubServices = SERVICE_CATALOG.filter(s => s.category === \"hub\");\n/** Every service in the catalog is live: an entry is only added once it ships. */\nexport const liveServices = SERVICE_CATALOG;\n/** Services with a scripted onboarding tour step, in catalog order. */\nexport const onboardingTourServices = SERVICE_CATALOG.filter(\n s => s.onboarding !== undefined\n);\n\nexport function getServiceBySlug(slug: string): 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 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 service regardless of individual grants. */\nexport const ADMIN_SEE_ALL_ROLES = [\n \"platform_admin\",\n \"enterprise_admin\",\n] 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.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":"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","names":[]}
@@ -371,6 +371,7 @@ function NavSection({
371
371
  isActive: active,
372
372
  onClick: () => onNavigate(item.path),
373
373
  tooltip: item.label,
374
+ "data-tour-target": item.tourTarget,
374
375
  className: "h-10 transition-all font-normal",
375
376
  children: [
376
377
  Icon && /* @__PURE__ */ jsx4(
@@ -721,17 +722,6 @@ var FOOTER_SERVICES = [
721
722
  slug: "action-manager",
722
723
  localRoute: "/services/action-manager",
723
724
  externalUrl: platformUrl("actionmanager")
724
- },
725
- {
726
- name: "Safety Manager",
727
- slug: "safety-manager",
728
- localRoute: "/services/safety-manager"
729
- },
730
- {
731
- name: "Connect",
732
- slug: "connect",
733
- localRoute: "/services/connect",
734
- externalUrl: platformUrl("connect")
735
725
  }
736
726
  ];
737
727
  function SharedFooter({
@@ -1259,4 +1249,4 @@ export {
1259
1249
  policyDeploymentConfig,
1260
1250
  SERVICE_CONFIGS
1261
1251
  };
1262
- //# sourceMappingURL=chunk-UQUMDBCZ.js.map
1252
+ //# sourceMappingURL=chunk-YCTIC6XW.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 * Optional accent classes applied to this item's icon + label at all times\n * (active or not) — e.g. \"text-purple-400\" to flag a premium/upsell item.\n * Omit for the default muted/active-primary treatment every other item uses.\n */\n accentClassName?: string;\n /**\n * Optional tour spotlight target, rendered as `data-tour-target` on this\n * item's sidebar button so a tour step whose `spotlight.selector` matches it\n * highlights the item. The sidebar is on every page, so such a step works\n * wherever the user starts the tour. On mobile the sidebar only exists while\n * its drawer is open, so the step falls back to the centred panel there.\n * Omit for no attribute.\n */\n tourTarget?: string;\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 * Optional accent classes applied to the section label text — e.g.\n * \"text-purple-400\" for a \"Premium Service\" heading. Omit for the\n * default muted-foreground treatment.\n */\n labelClassName?: string;\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 = [\n \"enterprise_admin\",\n \"platform_admin\",\n \"admin\",\n \"superuser\",\n];\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(\n itemPath: string,\n location: string,\n basePath?: string\n): 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(\n config.minWidth,\n startWidth.current + (e.clientX - startX.current)\n )\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, reporting-toolbar\n * slot, a \"Take the tour\" trigger (only when the module registers a\n * tour — see @pablo2410/shared-ui/tour), 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 {\n ArrowLeft,\n ExternalLink,\n LogOut,\n PanelLeft,\n Settings,\n} from \"lucide-react\";\nimport { cn } from \"../utils/cn\";\nimport {\n type MenuItem,\n type MenuSection,\n isMenuItemActive,\n} from \"./SharedSidebar\";\nimport { CrossServiceNav } from \"./CrossServiceNav\";\nimport type { CrossServiceLink } from \"../services/crossNav\";\nimport { HealthFooter } from \"./HealthFooter\";\nimport { EnterpriseDebugLabel } from \"./EnterpriseDebugLabel\";\nimport { TakeTheTourButton } from \"../tour/TakeTheTourButton\";\nimport { OpiTourHost } from \"../tour/OpiTourHost\";\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 * Stays constant across navigation — for a per-route secondary label\n * (e.g. the active section name), pass `sectionLabel` instead of changing\n * this on navigate. */\n serviceName: string;\n /** Optional secondary label shown next to `serviceName`, separated by a\n * \"›\" (e.g. serviceName=\"Admin Controls\", sectionLabel=\"Users\" renders\n * \"Admin Controls › Users\"). Typically the active nav item's label —\n * update this per route instead of swapping `serviceName` itself, so the\n * primary brand name stays stable while navigating. */\n sectionLabel?: 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 reporting toolbar).\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 — passed to the user menu's diagnostic label\n * (EnterpriseDebugLabel) and, where a hierarchy navigator is supplied,\n * already visible via the breadcrumb; no longer rendered as a separate\n * header badge (removed as redundant with both). */\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=\"shell-hidden shell-md:block text-xs text-[#E2E8F0] truncate max-w-[100px]\">\n {user.name || \"User\"}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"w-48 bg-[#0D1220] border-[#1E2738]\"\n >\n <div className=\"px-3 py-2 border-b border-[#1E2738]\">\n <p className=\"text-xs font-medium text-[#E2E8F0] truncate\">\n {user.name}\n </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/* ── 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\n className={cn(\n \"text-xs font-medium uppercase tracking-wider\",\n section.labelClassName ?? \"text-muted-foreground\"\n )}\n >\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 data-tour-target={item.tourTarget}\n className=\"h-10 transition-all font-normal\"\n >\n {Icon && (\n <Icon\n className={cn(\n \"h-4 w-4\",\n item.accentClassName ?? (active && \"text-primary\")\n )}\n />\n )}\n <span className={cn(item.accentClassName)}>{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 sectionLabel,\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\n className={cn(\n sectionLabel && !isCollapsed ? \"h-16\" : \"h-16 justify-center\"\n )}\n >\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=\"flex flex-col min-w-0 leading-tight\">\n <span className=\"font-semibold tracking-tight truncate\">\n {serviceName}\n </span>\n {sectionLabel && (\n <span className=\"text-xs text-[#8890A0] truncate\">\n {sectionLabel}\n </span>\n )}\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 shell-md:hidden\" />\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0 shell-hidden shell-sm:block\">\n {serviceIcon}\n </span>\n )}\n {hierarchyNavigator && (\n <div className=\"min-w-0 overflow-x-auto scrollbar-none\">\n {hierarchyNavigator}\n </div>\n )}\n </div>\n\n {/* RIGHT: tour trigger + reporting toolbar + portal link + user menu */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {/* Renders nothing unless this module registered a tour\n (useRegisterTour from @pablo2410/shared-ui/tour). */}\n <TakeTheTourButton />\n {reportingToolbar}\n {portalUrl && (\n <a\n href={portalUrl}\n className=\"shell-hidden shell-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] shell-hidden shell-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 {/* The tour overlay for the module's registered tour, if any. */}\n <OpiTourHost />\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 ??\n (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit =\n commitHash ??\n (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ??\n (typeof __BUILD_DATE__ !== \"undefined\" ? __BUILD_DATE__ : undefined)\n );\n const buildStamp = [version && `v${version}`, commit, builtAt]\n .filter(Boolean)\n .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\n className=\"ml-auto text-zinc-500\"\n title=\"Deployed build (version · commit · build date UTC)\"\n >\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 =\n cookieId !== null && cookieId !== \"\" ? Number(cookieId) : null;\n const hasSelection =\n selectionEnterpriseId !== undefined && selectionEnterpriseId !== null;\n const mismatch =\n 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 className={cn(\"animate-pulse rounded-md bg-[#1E2738]\", className)} />\n );\n}\n\nexport interface DashboardLayoutSkeletonProps {\n className?: string;\n}\n\nexport function DashboardLayoutSkeleton({\n className,\n}: 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\")}>{breadcrumbs}</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 && <div className=\"shrink-0 text-[#8C34E9]\">{icon}</div>}\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\">{actions}</div>\n )}\n </div>\n\n {/* Optional children below header */}\n {children && (\n <div className={cn(\"mt-3\", compact && \"mt-2\")}>{children}</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 {\n name: \"SQDCP\",\n slug: \"sqdcp\",\n localRoute: \"/services/sqdcp\",\n externalUrl: platformUrl(\"sqdcp\"),\n },\n {\n name: \"OEE Manager\",\n slug: \"oee-manager\",\n localRoute: \"/services/oee-manager\",\n externalUrl: platformUrl(\"oeemanager\"),\n },\n {\n name: \"Policy Deployment\",\n slug: \"policy-deployment\",\n localRoute: \"/services/policy-deployment\",\n externalUrl: platformUrl(\"policy\"),\n },\n {\n name: \"Action Manager\",\n slug: \"action-manager\",\n localRoute: \"/services/action-manager\",\n externalUrl: platformUrl(\"actionmanager\"),\n },\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 <svg\n className=\"w-7 h-7 shrink-0\"\n viewBox=\"0 0 120 120\"\n role=\"img\"\n aria-label=\"Oplytics\"\n >\n <defs>\n <linearGradient\n id=\"oplytics-footer-mark\"\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n >\n <stop offset=\"0\" stopColor=\"#8C34E9\" />\n <stop offset=\"1\" stopColor=\"#5B1FA6\" />\n </linearGradient>\n </defs>\n <circle\n cx=\"60\"\n cy=\"60\"\n r=\"58\"\n fill=\"url(#oplytics-footer-mark)\"\n />\n <g fill=\"none\" stroke=\"#0A0E1A\" strokeWidth=\"8\">\n <circle cx=\"60\" cy=\"60\" r=\"34\" />\n <circle cx=\"60\" cy=\"60\" r=\"17\" />\n </g>\n <circle cx=\"77\" cy=\"43\" r=\"9\" fill=\"#1DB8CE\" />\n </svg>\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={\n useExternalUrls && service.externalUrl\n ? service.externalUrl\n : service.localRoute\n }\n onClick={\n onServiceClick\n ? e => onServiceClick(service, e)\n : undefined\n }\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 =\n buildVersion ??\n (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n const commit =\n commitHash ??\n (typeof __COMMIT_HASH__ !== \"undefined\" ? __COMMIT_HASH__ : undefined);\n const builtAt = formatBuildDate(\n buildDate ??\n (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 <svg\n className=\"w-5 h-5 shrink-0\"\n viewBox=\"0 0 120 120\"\n role=\"img\"\n aria-label=\"Oplytics\"\n >\n <defs>\n <linearGradient\n id=\"oplytics-service-footer-mark\"\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n >\n <stop offset=\"0\" stopColor=\"#8C34E9\" />\n <stop offset=\"1\" stopColor=\"#5B1FA6\" />\n </linearGradient>\n </defs>\n <circle\n cx=\"60\"\n cy=\"60\"\n r=\"58\"\n fill=\"url(#oplytics-service-footer-mark)\"\n />\n <g fill=\"none\" stroke=\"#0A0E1A\" strokeWidth=\"9\">\n <circle cx=\"60\" cy=\"60\" r=\"33\" />\n <circle cx=\"60\" cy=\"60\" r=\"14\" />\n </g>\n <circle cx=\"79\" cy=\"41\" r=\"11\" fill=\"#1DB8CE\" />\n </svg>\n <span className=\"text-[10px] text-[#596475] whitespace-nowrap shell-hidden shell-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]\">\n {userEmail}\n </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\n className=\"whitespace-nowrap text-[#3E4A5C] font-mono\"\n title=\"Deployed build\"\n >\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\">\n {buildStamp}\n </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 {\n isAdminRole,\n getInitials,\n isMenuItemActive,\n useSidebarResize,\n} 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(\n config: ServiceLayoutConfig\n): 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:\n config.storageKeyPrefix ||\n 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 {\n iconName: \"ClipboardList\",\n label: \"Action Tracker\",\n path: \"/actions\",\n },\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 {\n iconName: \"LayoutGrid\",\n label: \"Dashboard\",\n path: \"/app/policy-deployment\",\n },\n {\n iconName: \"Grid3X3\",\n label: \"X-Matrix\",\n path: \"/app/policy-deployment/xmatrix\",\n },\n {\n iconName: \"BarChart3\",\n label: \"Bowling Chart\",\n path: \"/app/policy-deployment/bowling\",\n },\n {\n iconName: \"FolderKanban\",\n label: \"Action Plans\",\n path: \"/app/policy-deployment/actions\",\n },\n {\n iconName: \"GitBranchPlus\",\n label: \"Catchball\",\n path: \"/app/policy-deployment/catchball\",\n },\n {\n iconName: \"Target\",\n label: \"Deployments\",\n path: \"/app/policy-deployment/deployments\",\n },\n ],\n },\n {\n label: \"Administration\",\n adminOnly: true,\n items: [\n {\n iconName: \"Settings\",\n label: \"Manage Policy\",\n path: \"/app/policy-deployment/manage\",\n },\n {\n iconName: \"Plug\",\n label: \"Integrations\",\n path: \"/app/policy-deployment/integrations\",\n },\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"]}
@@ -5,13 +5,13 @@ import { O as OrgHierarchySelection } from './orgHierarchy-Buky69YP.js';
5
5
  * without going through the Portal Service Hub).
6
6
  *
7
7
  * Visibility mirrors the Portal ServiceHub rule: platform_admin and
8
- * enterprise_admin see every live service; everyone else only sees the
8
+ * enterprise_admin see every service; everyone else only sees the
9
9
  * services granted in the portal `user_services` table. The current service
10
10
  * never links to itself. Each link URL carries the caller's current
11
11
  * org-hierarchy selection (?oh=…) so the breadcrumb arrives pre-aligned.
12
12
  */
13
13
 
14
- /** Roles that see every live service regardless of individual grants. */
14
+ /** Roles that see every service regardless of individual grants. */
15
15
  declare const ADMIN_SEE_ALL_ROLES: readonly ["platform_admin", "enterprise_admin"];
16
16
  /**
17
17
  * Legacy grant slugs → canonical catalog slugs. Auto-provisioned accounts
@@ -1,8 +1,8 @@
1
- import { S as SharedSidebarConfig } from '../HealthFooter-BoOozbWG.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-BoOozbWG.js';
1
+ import { S as SharedSidebarConfig } from '../HealthFooter-Cepg3mRC.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-Cepg3mRC.js';
3
3
  import * as React$1 from 'react';
4
4
  import { ReactNode } from 'react';
5
- import '../crossNav-Bz0RXU_F.js';
5
+ import '../crossNav-DBX4Eyx7.js';
6
6
  import '../orgHierarchy-Buky69YP.js';
7
7
 
8
8
  interface DashboardLayoutSkeletonProps {
@@ -19,7 +19,7 @@ import {
19
19
  policyDeploymentConfig,
20
20
  sqdcpConfig,
21
21
  useSidebarResize
22
- } from "../chunk-UQUMDBCZ.js";
22
+ } from "../chunk-YCTIC6XW.js";
23
23
  import "../chunk-LUQAR6NB.js";
24
24
  import "../chunk-7LI4Y6WT.js";
25
25
  import "../chunk-RSFGV7CG.js";
@@ -876,7 +876,6 @@ var OPLYTICS_SERVICES = [
876
876
  { slug: "oee-manager", label: "OEE Manager" },
877
877
  { slug: "action-manager", label: "Action Manager" },
878
878
  { slug: "policy-deployment", label: "Policy Deployment" },
879
- { slug: "connect", label: "Connect" },
880
879
  { slug: "portal", label: "Portal" }
881
880
  ];
882
881
  var REPORT_BUTTONS = [