@pablo2410/shared-ui 0.25.0 → 0.26.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.
@@ -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 */
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  OpiTourHost,
3
3
  TakeTheTourButton
4
- } from "./chunk-T3W42HMO.js";
4
+ } from "./chunk-LUQAR6NB.js";
5
5
  import {
6
6
  platformUrl,
7
7
  portalUrl
@@ -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(
@@ -1259,4 +1260,4 @@ export {
1259
1260
  policyDeploymentConfig,
1260
1261
  SERVICE_CONFIGS
1261
1262
  };
1262
- //# sourceMappingURL=chunk-Z4P7V5SL.js.map
1263
+ //# sourceMappingURL=chunk-CIGJSBWS.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 name: \"Safety Manager\",\n slug: \"safety-manager\",\n localRoute: \"/services/safety-manager\",\n },\n {\n name: \"Connect\",\n slug: \"connect\",\n localRoute: \"/services/connect\",\n externalUrl: platformUrl(\"connect\"),\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"]}
@@ -559,7 +559,7 @@ function clamp(value, min, max) {
559
559
  }
560
560
 
561
561
  // src/tour/TourShell.tsx
562
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
562
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
563
563
  import {
564
564
  X,
565
565
  ChevronLeft,
@@ -578,6 +578,16 @@ var defaultSkin = {
578
578
  softText: "#8890a0"
579
579
  };
580
580
  var SPOTLIGHT_PANEL_SIZE = { width: 320, height: 280 };
581
+ function useHover() {
582
+ const [hovered, setHovered] = useState2(false);
583
+ return [
584
+ hovered,
585
+ {
586
+ onMouseEnter: () => setHovered(true),
587
+ onMouseLeave: () => setHovered(false)
588
+ }
589
+ ];
590
+ }
581
591
  function TourShell({
582
592
  step,
583
593
  index,
@@ -683,9 +693,10 @@ function TourShell({
683
693
  children: [
684
694
  /* @__PURE__ */ jsx3(TourShellHeader, { step, orb, onDismiss }),
685
695
  /* @__PURE__ */ jsx3(TourShellStepDots, { total, index, skin }),
686
- /* @__PURE__ */ jsx3("div", { className: "overflow-y-auto px-4 py-3 space-y-3", children: /* @__PURE__ */ jsx3(
696
+ /* @__PURE__ */ jsx3("div", { className: "overflow-y-auto", style: { padding: "12px 16px" }, children: /* @__PURE__ */ jsx3(
687
697
  TourShellBody,
688
698
  {
699
+ gap: 12,
689
700
  step,
690
701
  narration,
691
702
  narrationPending,
@@ -712,8 +723,8 @@ function TourShell({
712
723
  return /* @__PURE__ */ jsx3(
713
724
  "div",
714
725
  {
715
- className: "fixed inset-0 z-50 flex items-center justify-center p-4",
716
- style: { background: skin.overlayBg },
726
+ className: "fixed inset-0 z-50 flex items-center justify-center",
727
+ style: { background: skin.overlayBg, padding: 16 },
717
728
  onClick: (e) => {
718
729
  if (e.target === e.currentTarget) onDismiss();
719
730
  },
@@ -730,9 +741,10 @@ function TourShell({
730
741
  children: [
731
742
  /* @__PURE__ */ jsx3(TourShellHeader, { step, orb, onDismiss }),
732
743
  /* @__PURE__ */ jsx3(TourShellStepDots, { total, index, skin }),
733
- /* @__PURE__ */ jsx3("div", { className: "overflow-y-auto px-5 py-4 space-y-4", children: /* @__PURE__ */ jsx3(
744
+ /* @__PURE__ */ jsx3("div", { className: "overflow-y-auto", style: { padding: "16px 20px" }, children: /* @__PURE__ */ jsx3(
734
745
  TourShellBody,
735
746
  {
747
+ gap: 16,
736
748
  step,
737
749
  narration,
738
750
  narrationPending,
@@ -762,12 +774,14 @@ function TourShellHeader({
762
774
  orb,
763
775
  onDismiss
764
776
  }) {
777
+ const [closeHovered, closeHover] = useHover();
765
778
  return /* @__PURE__ */ jsxs2(
766
779
  "div",
767
780
  {
768
- className: "flex items-center justify-between gap-3 px-5 py-4 shrink-0",
781
+ className: "flex items-center justify-between gap-3 shrink-0",
769
782
  style: {
770
- background: "linear-gradient(135deg, #2a1450 0%, #1a0e33 100%)"
783
+ background: "linear-gradient(135deg, #2a1450 0%, #1a0e33 100%)",
784
+ padding: "16px 20px"
771
785
  },
772
786
  children: [
773
787
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5 min-w-0", children: [
@@ -778,7 +792,13 @@ function TourShellHeader({
778
792
  "button",
779
793
  {
780
794
  onClick: onDismiss,
781
- className: "p-1 rounded hover:bg-white/10 transition-colors shrink-0",
795
+ ...closeHover,
796
+ className: "transition-colors shrink-0",
797
+ style: {
798
+ padding: 4,
799
+ borderRadius: 4,
800
+ background: closeHovered ? "rgba(255,255,255,0.1)" : "transparent"
801
+ },
782
802
  title: "Skip tour",
783
803
  children: /* @__PURE__ */ jsx3(X, { size: 16, className: "text-white/70" })
784
804
  }
@@ -795,8 +815,8 @@ function TourShellStepDots({
795
815
  return /* @__PURE__ */ jsx3(
796
816
  "div",
797
817
  {
798
- className: "flex items-center gap-1.5 px-5 py-3 shrink-0",
799
- style: { borderBottom: skin.panelBorder },
818
+ className: "flex items-center gap-1.5 shrink-0",
819
+ style: { borderBottom: skin.panelBorder, padding: "12px 20px" },
800
820
  children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx3(
801
821
  "span",
802
822
  {
@@ -817,8 +837,16 @@ function TourShellBody({
817
837
  narration,
818
838
  narrationPending,
819
839
  skin,
820
- children
840
+ children,
841
+ gap
821
842
  }) {
843
+ const features = step.features ?? [];
844
+ const highlights = step.highlights ?? [];
845
+ const hasList = features.length > 0 || highlights.length > 0;
846
+ const hasAction = Boolean(step.action);
847
+ const hasChildren = Boolean(children);
848
+ const gapIf = (hasNext) => hasNext ? gap : 0;
849
+ const itemGap = (i, count, px) => i < count - 1 ? px : 0;
822
850
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
823
851
  /* @__PURE__ */ jsx3(
824
852
  "p",
@@ -826,22 +854,35 @@ function TourShellBody({
826
854
  className: "text-[13px] leading-relaxed transition-opacity",
827
855
  style: {
828
856
  color: skin.bodyText,
829
- opacity: narrationPending ? 0.7 : 1
857
+ opacity: narrationPending ? 0.7 : 1,
858
+ marginBottom: gapIf(hasList || hasAction || hasChildren)
830
859
  },
831
860
  children: narration
832
861
  }
833
862
  ),
834
- step.features && step.features.length > 0 ? /* @__PURE__ */ jsx3("ul", { className: "space-y-2", children: step.features.map((f, i) => /* @__PURE__ */ jsxs2("li", { className: "text-[12.5px] leading-relaxed", children: [
835
- /* @__PURE__ */ jsx3("span", { className: "font-semibold", style: { color: skin.accent }, children: f.name }),
836
- /* @__PURE__ */ jsxs2("span", { style: { color: skin.softText }, children: [
837
- " \u2014 ",
838
- f.detail
839
- ] })
840
- ] }, i)) }) : step.highlights && step.highlights.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "space-y-1.5", children: step.highlights.map((h, i) => /* @__PURE__ */ jsxs2(
863
+ features.length > 0 ? /* @__PURE__ */ jsx3("ul", { style: { marginBottom: gapIf(hasAction || hasChildren) }, children: features.map((f, i) => /* @__PURE__ */ jsxs2(
841
864
  "li",
842
865
  {
843
- className: "text-[12.5px] leading-relaxed pl-3 relative",
844
- style: { color: skin.softText },
866
+ className: "text-[12.5px] leading-relaxed",
867
+ style: { marginBottom: itemGap(i, features.length, 8) },
868
+ children: [
869
+ /* @__PURE__ */ jsx3("span", { className: "font-semibold", style: { color: skin.accent }, children: f.name }),
870
+ /* @__PURE__ */ jsxs2("span", { style: { color: skin.softText }, children: [
871
+ " \u2014 ",
872
+ f.detail
873
+ ] })
874
+ ]
875
+ },
876
+ i
877
+ )) }) : highlights.length > 0 && /* @__PURE__ */ jsx3("ul", { style: { marginBottom: gapIf(hasAction || hasChildren) }, children: highlights.map((h, i) => /* @__PURE__ */ jsxs2(
878
+ "li",
879
+ {
880
+ className: "text-[12.5px] leading-relaxed relative",
881
+ style: {
882
+ color: skin.softText,
883
+ paddingLeft: 12,
884
+ marginBottom: itemGap(i, highlights.length, 6)
885
+ },
845
886
  children: [
846
887
  /* @__PURE__ */ jsx3(
847
888
  "span",
@@ -862,8 +903,13 @@ function TourShellBody({
862
903
  href: step.action.href,
863
904
  target: step.action.external ? "_blank" : void 0,
864
905
  rel: step.action.external ? "noopener noreferrer" : void 0,
865
- className: "inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-[13px] font-semibold transition-opacity hover:opacity-90",
866
- style: { background: `${skin.accent}20`, color: skin.accent },
906
+ className: "inline-flex items-center gap-1.5 rounded-lg text-[13px] font-semibold transition-opacity hover:opacity-90",
907
+ style: {
908
+ background: `${skin.accent}20`,
909
+ color: skin.accent,
910
+ padding: "8px 14px",
911
+ marginBottom: gapIf(hasChildren)
912
+ },
867
913
  children: [
868
914
  step.action.label,
869
915
  step.action.external ? /* @__PURE__ */ jsx3(ExternalLink, { size: 13 }) : /* @__PURE__ */ jsx3(ArrowRight, { size: 13 })
@@ -881,18 +927,19 @@ function TourShellFooter({
881
927
  onDismiss,
882
928
  skin
883
929
  }) {
930
+ const [nextHovered, nextHover] = useHover();
884
931
  return /* @__PURE__ */ jsxs2(
885
932
  "div",
886
933
  {
887
- className: "flex items-center justify-between gap-2 px-5 py-3.5 shrink-0",
888
- style: { borderTop: skin.panelBorder },
934
+ className: "flex items-center justify-between gap-2 shrink-0",
935
+ style: { borderTop: skin.panelBorder, padding: "14px 20px" },
889
936
  children: [
890
937
  /* @__PURE__ */ jsx3(
891
938
  "button",
892
939
  {
893
940
  onClick: onDismiss,
894
- className: "text-[12px] font-medium transition-colors hover:underline",
895
- style: { color: skin.mutedText },
941
+ className: "transition-colors hover:underline",
942
+ style: { color: skin.mutedText, fontSize: 12, fontWeight: 500 },
896
943
  children: "Skip tour"
897
944
  }
898
945
  ),
@@ -901,8 +948,15 @@ function TourShellFooter({
901
948
  "button",
902
949
  {
903
950
  onClick: onBack,
904
- className: "flex items-center gap-1 px-3 py-1.5 rounded-md text-[12.5px] font-medium transition-colors",
905
- style: { color: skin.bodyText, border: skin.panelBorder },
951
+ className: "flex items-center gap-1 transition-colors",
952
+ style: {
953
+ color: skin.bodyText,
954
+ border: skin.panelBorder,
955
+ padding: "6px 12px",
956
+ borderRadius: 6,
957
+ fontSize: 12.5,
958
+ fontWeight: 500
959
+ },
906
960
  children: [
907
961
  /* @__PURE__ */ jsx3(ChevronLeft, { size: 13 }),
908
962
  "Back"
@@ -913,9 +967,16 @@ function TourShellFooter({
913
967
  "button",
914
968
  {
915
969
  onClick: onNext,
916
- className: "flex items-center gap-1 px-3.5 py-1.5 rounded-md text-[12.5px] font-bold text-white transition-opacity hover:opacity-90",
970
+ ...nextHover,
971
+ className: "flex items-center gap-1 transition-opacity",
917
972
  style: {
918
- background: "linear-gradient(135deg, #8C34E9 0%, #5B1FA6 100%)"
973
+ background: "linear-gradient(135deg, #8C34E9 0%, #5B1FA6 100%)",
974
+ color: "#fff",
975
+ padding: "6px 14px",
976
+ borderRadius: 6,
977
+ fontSize: 12.5,
978
+ fontWeight: 700,
979
+ opacity: nextHovered ? 0.9 : 1
919
980
  },
920
981
  children: [
921
982
  isLast ? "Done" : "Next",
@@ -930,7 +991,7 @@ function TourShellFooter({
930
991
  }
931
992
 
932
993
  // src/tour/useTour.ts
933
- import { useCallback, useState as useState2 } from "react";
994
+ import { useCallback, useState as useState3 } from "react";
934
995
 
935
996
  // src/tour/tourMachine.ts
936
997
  function resolveInitialIndex(steps, initialStepId) {
@@ -957,7 +1018,7 @@ function useTour(steps, opts = {}) {
957
1018
  if (steps.length === 0) {
958
1019
  throw new Error("useTour requires at least one step");
959
1020
  }
960
- const [index, setIndex] = useState2(
1021
+ const [index, setIndex] = useState3(
961
1022
  () => resolveInitialIndex(steps, opts.initialStepId)
962
1023
  );
963
1024
  const next = useCallback(() => {
@@ -1001,7 +1062,7 @@ function useTour(steps, opts = {}) {
1001
1062
  }
1002
1063
 
1003
1064
  // src/tour/OpiTourHost.tsx
1004
- import { useEffect as useEffect4, useRef as useRef3, useState as useState3 } from "react";
1065
+ import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
1005
1066
  import { Sparkles as Sparkles2 } from "lucide-react";
1006
1067
  import { jsx as jsx4 } from "react/jsx-runtime";
1007
1068
  function OpiTourHost(props) {
@@ -1058,8 +1119,8 @@ function TourRunner({
1058
1119
  }) {
1059
1120
  const { close } = useTourLauncher();
1060
1121
  const narrate = useTourNarrator();
1061
- const [narrationByStep, setNarrationByStep] = useState3({});
1062
- const [pendingStepId, setPendingStepId] = useState3(null);
1122
+ const [narrationByStep, setNarrationByStep] = useState4({});
1123
+ const [pendingStepId, setPendingStepId] = useState4(null);
1063
1124
  const requested = useRef3(/* @__PURE__ */ new Set());
1064
1125
  const currentStepId = useRef3(
1065
1126
  steps.find((s) => s.id === initialStepId)?.id ?? steps[0].id
@@ -1142,4 +1203,4 @@ export {
1142
1203
  useTour,
1143
1204
  OpiTourHost
1144
1205
  };
1145
- //# sourceMappingURL=chunk-T3W42HMO.js.map
1206
+ //# sourceMappingURL=chunk-LUQAR6NB.js.map