@pablo2410/shared-ui 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/layout/SharedSidebar.tsx","../src/layout/DashboardLayout.tsx","../src/layout/HealthFooter.tsx","../src/layout/DashboardLayoutSkeleton.tsx","../src/layout/SharedPageHeader.tsx","../src/layout/SharedFooter.tsx","../src/layout/ServiceFooter.tsx","../src/layout/createServiceLayout.tsx","../src/layout/serviceConfigs.ts"],"sourcesContent":["/**\n * SharedSidebar — Config-driven sidebar component for all Oplytics subdomains.\n *\n * Replaces per-subdomain DashboardLayout sidebar implementations with a single\n * shared component. Each subdomain provides its own configuration (menu items,\n * service name, feature flags) and gets a consistent sidebar experience.\n *\n * Features:\n * - Resizable sidebar with localStorage persistence\n * - Collapsible icon mode with tooltips\n * - Service branding (icon + name) in header\n * - \"Back to Service Hub/Portal\" link (configurable)\n * - Grouped menu sections with optional section labels\n * - Admin-only menu items (role-gated)\n * - User avatar footer with optional logout dropdown\n * - Mobile-responsive with auto-collapse\n *\n * Usage:\n * import { createServiceLayout } from \"@shared/components/SharedSidebar\";\n * const MyLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n * // Then use <MyLayout>{children}</MyLayout> in your routes\n *\n * @module shared/components/SharedSidebar\n */\n\nimport React, { CSSProperties, useEffect, useRef, useState } from \"react\";\n\n/* ── Types ── */\n\nexport interface MenuItem {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n path: string;\n /** Optional badge text (e.g. count) */\n badge?: string | number;\n}\n\nexport interface MenuSection {\n /** Section label displayed above items */\n label?: string;\n /** Alias for label — used by DashboardLayout */\n title?: string;\n items: MenuItem[];\n adminOnly?: boolean;\n}\n\nexport interface SharedSidebarConfig {\n /** Display name shown in sidebar header */\n serviceName: string;\n /** Icon component rendered next to service name */\n serviceIcon: React.ReactNode;\n /** Primary navigation items */\n menuSections: MenuSection[];\n /** Show \"Back to Service Hub\" / \"Back to Portal\" link at top of nav */\n backLink?: {\n label: string;\n path: string;\n };\n /** localStorage key prefix for sidebar width persistence */\n storageKeyPrefix?: string;\n /** Default sidebar width in pixels (default: 260) */\n defaultWidth?: number;\n /** Minimum sidebar width in pixels (default: 200) */\n minWidth?: number;\n /** Maximum sidebar width in pixels (default: 480) */\n maxWidth?: number;\n}\n\nexport interface SharedSidebarProps {\n config: SharedSidebarConfig;\n /** Current route location */\n location: string;\n /** Navigate to a path */\n setLocation: (path: string) => void;\n /** Current user object (null if not authenticated) */\n user: { name?: string; email?: string; role?: string } | null;\n /** Logout handler */\n onLogout?: () => void;\n /** Whether sidebar is collapsed */\n isCollapsed: boolean;\n /** Toggle sidebar collapsed state */\n toggleSidebar: () => void;\n /** Whether user has admin role */\n isAdmin: boolean;\n /** Children rendered in SidebarInset main area */\n children: React.ReactNode;\n}\n\n/* ── Constants ── */\n\nconst ADMIN_ROLES = [\"enterprise_admin\", \"platform_admin\", \"admin\", \"superuser\"];\n\n/* ── Helpers ── */\n\nexport function isAdminRole(role?: string): boolean {\n return !!role && ADMIN_ROLES.includes(role);\n}\n\nexport function getInitials(name?: string): string {\n if (!name) return \"U\";\n return name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n}\n\n/**\n * Determines if a menu item is active based on current location.\n * Exact match for root paths, startsWith for nested paths.\n */\nexport function isMenuItemActive(itemPath: string, location: string, basePath?: string): boolean {\n if (basePath) {\n // For subdomain routes with a base path (e.g., /app/policy-deployment)\n if (itemPath === basePath) return location === basePath;\n return location.startsWith(itemPath);\n }\n // For root-level routes\n if (itemPath === \"/\") return location === \"/\";\n return location.startsWith(itemPath);\n}\n\n/* ── Resize Hook ── */\n\nexport function useSidebarResize(config: {\n storageKey: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}) {\n const [width, setWidth] = useState(() => {\n if (typeof window === \"undefined\") return config.defaultWidth;\n const saved = localStorage.getItem(config.storageKey);\n return saved ? parseInt(saved, 10) : config.defaultWidth;\n });\n const [isResizing, setIsResizing] = useState(false);\n const startX = useRef(0);\n const startWidth = useRef(config.defaultWidth);\n\n useEffect(() => {\n localStorage.setItem(config.storageKey, String(width));\n }, [width, config.storageKey]);\n\n useEffect(() => {\n if (!isResizing) return;\n const onMove = (e: MouseEvent) => {\n const newWidth = Math.min(\n config.maxWidth,\n Math.max(config.minWidth, startWidth.current + (e.clientX - startX.current))\n );\n setWidth(newWidth);\n };\n const onUp = () => {\n setIsResizing(false);\n };\n document.addEventListener(\"mousemove\", onMove);\n document.addEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"col-resize\";\n document.body.style.userSelect = \"none\";\n return () => {\n document.removeEventListener(\"mousemove\", onMove);\n document.removeEventListener(\"mouseup\", onUp);\n document.body.style.cursor = \"\";\n document.body.style.userSelect = \"\";\n };\n }, [isResizing, config.minWidth, config.maxWidth]);\n\n const startResize = (e: React.MouseEvent) => {\n setIsResizing(true);\n startX.current = e.clientX;\n startWidth.current = width;\n };\n\n return { width, isResizing, startResize };\n}\n\n/* ── Export config type for subdomain use ── */\nexport type { SharedSidebarConfig as SidebarConfig };\n","/**\n * DashboardLayout — the canonical sidebar + header shell for every Oplytics\n * subdomain (the \"Policy Deployment\" gold-standard layout, consolidated).\n *\n * Built on the shadcn `Sidebar` primitives shipped from `@pablo2410/shared-ui/primitives`.\n * It renders an identical chrome across services:\n * - Collapsible icon sidebar with a brand header (icon + service name)\n * - \"← Service Hub\" link pinned to the top of the nav\n * - Grouped, optionally admin-gated menu sections\n * - Settings footer\n * - Sticky top header with a hierarchy-breadcrumb slot, enterprise badge,\n * reporting-toolbar slot, and a user menu\n *\n * The component is intentionally app-agnostic: it imports no app hooks\n * (`useAuth`, `useHierarchy`, router). Apps pass identity, navigation state,\n * and their hierarchy navigator / reporting toolbar in as props/slots.\n *\n * Oplytics dark theme: #0D1220 header, #1E2738 border, #8C34E9 accent.\n */\nimport { type ReactNode } from \"react\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n useSidebar,\n Avatar,\n AvatarFallback,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../primitives\";\nimport { useIsMobile } from \"../primitives/useMobile\";\nimport { ArrowLeft, ExternalLink, LogOut, PanelLeft, Settings } from \"lucide-react\";\nimport { cn } from \"../utils/cn\";\nimport { type MenuItem, type MenuSection, isMenuItemActive } from \"./SharedSidebar\";\nimport { HealthFooter } from \"./HealthFooter\";\n\n/* ── Re-export sidebar types ── */\nexport type { MenuItem, MenuSection };\n\n/* ── Types ── */\n\nexport interface DashboardLayoutUser {\n name?: string;\n email?: string;\n role?: string;\n avatarUrl?: string;\n /** The user's stable identifier (typically the JWT `openId` claim).\n * Optional — only used by the HealthFooter diagnostic strip. */\n openId?: string;\n /** The enterprise id from the JWT (`enterpriseId` claim). May be null\n * for platform_admin users who haven't selected an active enterprise.\n * Optional — only used by the HealthFooter diagnostic strip. */\n enterpriseId?: number | null;\n}\n\nexport interface DashboardLayoutEnterprise {\n /** The enterprise id the org-hierarchy is currently scoped to. Optional\n * — only used by the HealthFooter diagnostic strip; the visible header\n * badge only needs `name` (and optionally `code`). */\n id?: number;\n name: string;\n code?: string;\n}\n\nexport interface DashboardLayoutProps {\n /** Service/module name shown in the sidebar brand header (e.g. \"OEE Manager\"). */\n serviceName: string;\n /** Service icon (a lucide icon element), shown in the sidebar brand + header. */\n serviceIcon?: ReactNode;\n /** Primary navigation sections. */\n menuSections: MenuSection[];\n /** Admin-only navigation sections, rendered when `isAdmin` is true. */\n adminSections?: MenuSection[];\n /** Whether the current user may see `adminSections`. */\n isAdmin?: boolean;\n /** Current route (the app's router supplies this). */\n activePath: string;\n /** Navigate handler (the app's router supplies this). */\n onNavigate: (path: string) => void;\n /** Optional override for active-item detection. */\n isActive?: (itemPath: string, activePath: string) => boolean;\n /** URL for the \"← Service Hub\" link at the top of the nav (full-page nav). */\n serviceHubUrl?: string;\n /** Footer \"Settings\" route. Omit to hide the footer. */\n settingsPath?: string;\n /** Authenticated user (drives the header user menu). */\n user?: DashboardLayoutUser | null;\n /** Sign-out handler invoked from the user menu. */\n onLogout?: () => void;\n /** \"User Settings\" target in the user menu (full-page nav). */\n userSettingsUrl?: string;\n /** App-provided hierarchy navigator rendered in the header breadcrumb slot. */\n hierarchyNavigator?: ReactNode;\n /** Selected enterprise — drives the header enterprise badge. */\n enterprise?: DashboardLayoutEnterprise | null;\n /** App-provided reporting toolbar rendered on the right of the header. */\n reportingToolbar?: ReactNode;\n /** Whether the sidebar starts expanded. Defaults to `!isMobile`. */\n defaultOpen?: boolean;\n /** Extra footer rendered fixed at the bottom (e.g. a debug/health bar).\n * Coexists with `showHealthFooter` — pass both if you want both. */\n footer?: ReactNode;\n /** When true AND the current user has `role === \"platform_admin\"`, the\n * shell renders the standardised platform-admin diagnostic strip\n * (`HealthFooter`) fixed at the bottom of the viewport. Subdomains\n * opt in with one prop and inherit any future improvements to the\n * strip without code changes. */\n showHealthFooter?: boolean;\n /** Main content. */\n children: ReactNode;\n}\n\nconst DEFAULT_SERVICE_HUB_URL = \"https://portal.oplytics.digital/app\";\nconst DEFAULT_USER_SETTINGS_URL = \"https://portal.oplytics.digital/account\";\n\n/* ── Header: user menu ── */\n\nfunction UserMenu({\n user,\n onLogout,\n userSettingsUrl = DEFAULT_USER_SETTINGS_URL,\n}: {\n user: DashboardLayoutUser;\n onLogout?: () => void;\n userSettingsUrl?: string;\n}) {\n const initials = (user.name || \"U\")\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")\n .toUpperCase()\n .slice(0, 2);\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button className=\"flex items-center gap-2 rounded-md px-2 py-1 hover:bg-[#1E2738] transition-colors outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\">\n <Avatar className=\"h-7 w-7 border border-[#2A2A3E]\">\n <AvatarFallback className=\"text-[10px] font-medium bg-[#8C34E9]/20 text-[#C084FC]\">\n {initials}\n </AvatarFallback>\n </Avatar>\n <span className=\"hidden md:block text-xs text-[#E2E8F0] truncate max-w-[100px]\">\n {user.name || \"User\"}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-48 bg-[#0D1220] border-[#1E2738]\">\n <div className=\"px-3 py-2 border-b border-[#1E2738]\">\n <p className=\"text-xs font-medium text-[#E2E8F0] truncate\">{user.name}</p>\n <p className=\"text-[10px] text-[#596475] truncate\">{user.role}</p>\n </div>\n <DropdownMenuItem\n onClick={() => {\n window.location.href = userSettingsUrl;\n }}\n className=\"text-xs text-[#E2E8F0] hover:bg-[#1E2738] cursor-pointer focus:bg-[#1E2738]\"\n >\n <Settings className=\"h-3.5 w-3.5 mr-2 text-[#8890A0]\" />\n User Settings\n </DropdownMenuItem>\n <DropdownMenuSeparator className=\"bg-[#1E2738]\" />\n <DropdownMenuItem\n onClick={() => onLogout?.()}\n className=\"text-xs text-[#EF4444] hover:bg-[#EF4444]/10 cursor-pointer focus:bg-[#EF4444]/10 focus:text-[#EF4444]\"\n >\n <LogOut className=\"h-3.5 w-3.5 mr-2\" />\n Sign Out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/* ── Header: enterprise badge ── */\n\nfunction EnterpriseBadge({ enterprise }: { enterprise?: DashboardLayoutEnterprise | null }) {\n if (!enterprise) return null;\n return (\n <div className=\"hidden lg:flex items-center gap-1.5 px-2 py-1 rounded-md bg-[#8C34E9]/10 border border-[#8C34E9]/20\">\n <div className=\"h-4 w-4 rounded-sm bg-[#8C34E9]/30 flex items-center justify-center\">\n <span className=\"text-[8px] font-bold text-[#C084FC]\">\n {enterprise.name.charAt(0).toUpperCase()}\n </span>\n </div>\n <span className=\"text-[10px] font-medium text-[#C084FC] truncate max-w-[80px]\">\n {enterprise.code || enterprise.name}\n </span>\n </div>\n );\n}\n\n/* ── Sidebar nav section ── */\n\nfunction NavSection({\n section,\n marginTop,\n isCollapsed,\n resolveActive,\n onNavigate,\n}: {\n section: MenuSection;\n marginTop?: boolean;\n isCollapsed: boolean;\n resolveActive: (path: string) => boolean;\n onNavigate: (path: string) => void;\n}) {\n const label = section.title ?? section.label;\n return (\n <>\n {label && (\n <div className={cn(\"px-4 py-2\", marginTop && \"mt-2\")}>\n {!isCollapsed && (\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {label}\n </span>\n )}\n </div>\n )}\n <SidebarMenu className=\"px-2 py-1\">\n {section.items.map((item: MenuItem) => {\n const active = resolveActive(item.path);\n const Icon = item.icon;\n return (\n <SidebarMenuItem key={item.path}>\n <SidebarMenuButton\n isActive={active}\n onClick={() => onNavigate(item.path)}\n tooltip={item.label}\n className=\"h-10 transition-all font-normal\"\n >\n {Icon && <Icon className={cn(\"h-4 w-4\", active && \"text-primary\")} />}\n <span>{item.label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n })}\n </SidebarMenu>\n </>\n );\n}\n\n/* ── Inner shell (runs inside SidebarProvider, so it can call useSidebar) ── */\n\nfunction DashboardShell({\n serviceName,\n serviceIcon,\n menuSections,\n adminSections,\n isAdmin = false,\n activePath,\n onNavigate,\n isActive,\n serviceHubUrl = DEFAULT_SERVICE_HUB_URL,\n settingsPath,\n user,\n onLogout,\n userSettingsUrl,\n hierarchyNavigator,\n enterprise,\n reportingToolbar,\n footer,\n showHealthFooter,\n children,\n}: DashboardLayoutProps) {\n const { toggleSidebar, state } = useSidebar();\n const isCollapsed = state === \"collapsed\";\n\n const navigate = onNavigate;\n const resolveActive = (path: string) =>\n isActive ? isActive(path, activePath) : isMenuItemActive(path, activePath);\n\n return (\n <>\n <Sidebar collapsible=\"icon\" className=\"border-r-0\">\n <SidebarHeader className=\"h-16 justify-center\">\n <div className=\"flex items-center gap-3 px-2 transition-all w-full\">\n <button\n onClick={toggleSidebar}\n className=\"h-8 w-8 flex items-center justify-center hover:bg-accent rounded-lg transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring shrink-0\"\n aria-label=\"Toggle navigation\"\n >\n <PanelLeft className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n {!isCollapsed ? (\n <div className=\"flex items-center gap-2 min-w-0\">\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0\">{serviceIcon}</span>\n )}\n <span className=\"font-semibold tracking-tight truncate\">\n {serviceName}\n </span>\n </div>\n ) : null}\n </div>\n </SidebarHeader>\n\n <SidebarContent className=\"gap-0\">\n {/* Service Hub — pinned to the top of the nav */}\n {serviceHubUrl && (\n <SidebarMenu className=\"px-2 pt-2 pb-1\">\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => {\n window.location.href = serviceHubUrl;\n }}\n tooltip=\"Back to Service Hub\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span>Service Hub</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n\n {/* Primary navigation */}\n {menuSections.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `section-${i}`}\n section={section}\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n\n {/* Admin-only navigation */}\n {isAdmin &&\n adminSections?.map((section, i) => (\n <NavSection\n key={section.title ?? section.label ?? `admin-${i}`}\n section={section}\n marginTop\n isCollapsed={isCollapsed}\n resolveActive={resolveActive}\n onNavigate={navigate}\n />\n ))}\n </SidebarContent>\n\n {settingsPath && (\n <SidebarFooter className=\"p-2\">\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => navigate(settingsPath)}\n tooltip=\"Settings\"\n className=\"h-10 transition-all font-normal text-muted-foreground hover:text-foreground\"\n >\n <Settings className=\"h-4 w-4\" />\n <span>Settings</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n )}\n </Sidebar>\n\n <SidebarInset>\n <header\n className={cn(\n \"flex items-center justify-between h-14 px-4\",\n \"bg-[#0D1220]/95 border-b border-[#1E2738]\",\n \"backdrop-blur supports-[backdrop-filter]:backdrop-blur\",\n \"sticky top-0 z-40\"\n )}\n >\n {/* LEFT: mobile trigger + service icon + hierarchy breadcrumb */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <SidebarTrigger className=\"h-9 w-9 rounded-lg bg-background shrink-0 md:hidden\" />\n {serviceIcon && (\n <span className=\"text-[#8C34E9] shrink-0 hidden sm:block\">{serviceIcon}</span>\n )}\n {hierarchyNavigator && (\n <div className=\"min-w-0 overflow-x-auto scrollbar-none\">{hierarchyNavigator}</div>\n )}\n </div>\n\n {/* RIGHT: reporting toolbar + enterprise badge + user menu */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {reportingToolbar}\n <EnterpriseBadge enterprise={enterprise} />\n <div className=\"w-px h-6 bg-[#1E2738] hidden md:block\" />\n {user && (\n <UserMenu user={user} onLogout={onLogout} userSettingsUrl={userSettingsUrl} />\n )}\n </div>\n </header>\n\n <main className=\"flex-1 p-4 md:p-6 pb-14\">{children}</main>\n </SidebarInset>\n\n {footer}\n\n {/* Standardised platform-admin diagnostic strip — opt-in via prop. */}\n {showHealthFooter && user?.role === \"platform_admin\" && (\n <HealthFooter user={user} enterprise={enterprise} />\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 * 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\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}\n\nexport function HealthFooter({\n user,\n enterprise,\n className,\n style,\n}: HealthFooterProps) {\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 </div>\n );\n}\n","/**\n * DashboardLayoutSkeleton — loading skeleton for DashboardLayout.\n *\n * Shows a sidebar + content skeleton while auth state is loading.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\n\nfunction Skeleton({ className }: { className?: string }) {\n return (\n <div\n className={cn(\n \"animate-pulse rounded-md bg-[#1E2738]\",\n className\n )}\n />\n );\n}\n\nexport interface DashboardLayoutSkeletonProps {\n className?: string;\n}\n\nexport function DashboardLayoutSkeleton({ className }: DashboardLayoutSkeletonProps) {\n return (\n <div className={cn(\"flex min-h-screen bg-[#0A0E1A]\", className)}>\n {/* Sidebar skeleton */}\n <div className=\"w-[260px] border-r border-[#1E2738] bg-[#0D1220] p-4 space-y-6\">\n {/* Logo area */}\n <div className=\"flex items-center gap-3 px-2\">\n <Skeleton className=\"h-8 w-8 rounded-md\" />\n <Skeleton className=\"h-4 w-24\" />\n </div>\n\n {/* Menu items */}\n <div className=\"space-y-2 px-2\">\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n </div>\n\n {/* Section divider */}\n <div className=\"px-2\">\n <Skeleton className=\"h-3 w-16 mb-3\" />\n <Skeleton className=\"h-9 w-full rounded-lg\" />\n <Skeleton className=\"h-9 w-full rounded-lg mt-2\" />\n </div>\n\n {/* User profile area at bottom */}\n <div className=\"absolute bottom-4 left-4 right-4\">\n <div className=\"flex items-center gap-3 px-1\">\n <Skeleton className=\"h-8 w-8 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"h-2 w-32\" />\n </div>\n </div>\n </div>\n </div>\n\n {/* Main content skeleton */}\n <div className=\"flex-1 p-6 space-y-4\">\n {/* Page header */}\n <div className=\"flex items-center justify-between\">\n <Skeleton className=\"h-8 w-48 rounded-lg\" />\n <Skeleton className=\"h-9 w-24 rounded-lg\" />\n </div>\n\n {/* Stats cards */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n <Skeleton className=\"h-24 rounded-xl\" />\n </div>\n\n {/* Main content area */}\n <Skeleton className=\"h-64 rounded-xl\" />\n\n {/* Table-like area */}\n <div className=\"space-y-2\">\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n <Skeleton className=\"h-10 w-full rounded-lg\" />\n </div>\n </div>\n </div>\n );\n}\n","/**\n * SharedPageHeader — standard page header for all Oplytics subdomains.\n *\n * Displays: page title, optional subtitle, optional breadcrumbs,\n * optional action buttons, and optional HierarchyNavigator.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent, #E2E8F0 text, #8890A0 muted\n */\nimport { cn } from \"../utils/cn\";\nimport { ChevronLeft } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n\nexport interface SharedPageHeaderProps {\n /** Page title */\n title: string;\n /** Optional subtitle / description */\n subtitle?: string;\n /** Optional icon to display before the title */\n icon?: ReactNode;\n /** Optional back button handler — shows a back arrow when provided */\n onBack?: () => void;\n /** Optional action buttons to render on the right side */\n actions?: ReactNode;\n /** Optional breadcrumb trail (e.g. HierarchyNavigator) */\n breadcrumbs?: ReactNode;\n /** Whether to show a bottom border (default: true) */\n showBorder?: boolean;\n /** Compact mode — smaller spacing and text (default: false) */\n compact?: boolean;\n /** Additional CSS classes */\n className?: string;\n /** Children rendered below the header row */\n children?: ReactNode;\n}\n\nexport function SharedPageHeader({\n title,\n subtitle,\n icon,\n onBack,\n actions,\n breadcrumbs,\n showBorder = true,\n compact = false,\n className,\n children,\n}: SharedPageHeaderProps) {\n return (\n <div\n className={cn(\n \"w-full\",\n showBorder && \"border-b border-[#1E2738]\",\n compact ? \"px-4 py-3\" : \"px-6 py-4\",\n className\n )}\n >\n {/* Breadcrumbs row */}\n {breadcrumbs && (\n <div className={cn(\"mb-2\", compact && \"mb-1\")}>\n {breadcrumbs}\n </div>\n )}\n\n {/* Main header row */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {/* Back button */}\n {onBack && (\n <button\n onClick={onBack}\n className={cn(\n \"shrink-0 p-1.5 rounded-md\",\n \"text-[#8890A0] hover:text-[#E2E8F0] hover:bg-[#1E2738]\",\n \"transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n aria-label=\"Go back\"\n >\n <ChevronLeft className={compact ? \"h-4 w-4\" : \"h-5 w-5\"} />\n </button>\n )}\n\n {/* Icon */}\n {icon && (\n <div className=\"shrink-0 text-[#8C34E9]\">\n {icon}\n </div>\n )}\n\n {/* Title + subtitle */}\n <div className=\"min-w-0\">\n <h1\n className={cn(\n \"font-semibold text-[#E2E8F0] truncate\",\n \"font-[Montserrat,sans-serif]\",\n compact ? \"text-lg\" : \"text-xl\"\n )}\n >\n {title}\n </h1>\n {subtitle && (\n <p\n className={cn(\n \"text-[#8890A0] truncate mt-0.5\",\n \"font-[Space_Grotesk,sans-serif]\",\n compact ? \"text-xs\" : \"text-sm\"\n )}\n >\n {subtitle}\n </p>\n )}\n </div>\n </div>\n\n {/* Action buttons */}\n {actions && (\n <div className=\"shrink-0 flex items-center gap-2\">\n {actions}\n </div>\n )}\n </div>\n\n {/* Optional children below header */}\n {children && (\n <div className={cn(\"mt-3\", compact && \"mt-2\")}>\n {children}\n </div>\n )}\n </div>\n );\n}\n","/**\n * SharedFooter — standard footer for all Oplytics subdomains.\n *\n * Displays: Oplytics branding, service links, legal links, copyright.\n * Consistent across all subdomains.\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #8890A0 text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport type { ReactNode } from \"react\";\n\ndeclare const __APP_VERSION__: string;\n\n/* ── Service definitions ── */\n\nexport interface FooterService {\n name: string;\n slug: string;\n /** Local route within the portal (e.g. /services/sqdcp) */\n localRoute: string;\n /** External URL for the subdomain (e.g. https://sqdcp.oplytics.digital) */\n externalUrl?: string;\n}\n\n/** Standard Oplytics service list */\nexport const FOOTER_SERVICES: FooterService[] = [\n { name: \"SQDCP\", slug: \"sqdcp\", localRoute: \"/services/sqdcp\", externalUrl: \"https://sqdcp.oplytics.digital\" },\n { name: \"OEE Manager\", slug: \"oee-manager\", localRoute: \"/services/oee-manager\", externalUrl: \"https://oee.oplytics.digital\" },\n { name: \"Policy Deployment\", slug: \"policy-deployment\", localRoute: \"/services/policy-deployment\", externalUrl: \"https://policy.oplytics.digital\" },\n { name: \"Action Manager\", slug: \"action-manager\", localRoute: \"/services/action-manager\", externalUrl: \"https://actions.oplytics.digital\" },\n { name: \"Safety Manager\", slug: \"safety-manager\", localRoute: \"/services/safety-manager\" },\n { name: \"Connect\", slug: \"connect\", localRoute: \"/services/connect\", externalUrl: \"https://connect.oplytics.digital\" },\n];\n\n/* ── Footer props ── */\n\nexport interface SharedFooterProps {\n /** Override the default service list */\n services?: FooterService[];\n /** Show pricing link (default: true) */\n showPricing?: boolean;\n /** Show sign-in link (default: true) */\n showSignIn?: boolean;\n /** Handler for service link clicks — receives the service */\n onServiceClick?: (service: FooterService, e: React.MouseEvent) => void;\n /** Handler for pricing link click */\n onPricingClick?: (e: React.MouseEvent) => void;\n /** Handler for sign-in link click */\n onSignInClick?: (e: React.MouseEvent) => void;\n /** Additional content to render in the footer */\n children?: ReactNode;\n /** CSS class for the footer container */\n className?: string;\n /** Whether to use external URLs (subdomain links) or local routes */\n useExternalUrls?: boolean;\n /** Current year override (default: auto) */\n year?: number;\n}\n\n/* ── Component ── */\n\nexport function SharedFooter({\n services = FOOTER_SERVICES,\n showPricing = true,\n showSignIn = true,\n onServiceClick,\n onPricingClick,\n onSignInClick,\n children,\n className,\n useExternalUrls = false,\n year,\n}: SharedFooterProps) {\n const currentYear = year ?? new Date().getFullYear();\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738]\",\n \"bg-[#0D1220] text-[#8890A0]\",\n \"font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n <div className=\"max-w-7xl mx-auto px-6 py-10\">\n {/* Top section: Logo + columns */}\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-8 mb-8\">\n {/* Brand column */}\n <div className=\"md:col-span-1\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div\n className=\"w-7 h-7 rounded-full flex items-center justify-center\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold text-xs\">O</span>\n </div>\n <span className=\"text-[#E2E8F0] font-semibold text-sm font-[Montserrat,sans-serif]\">\n Oplytics.digital\n </span>\n </div>\n <p className=\"text-xs text-[#596475] leading-relaxed\">\n Operational intelligence platform for manufacturing excellence.\n </p>\n </div>\n\n {/* Services column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Services\n </h4>\n <ul className=\"space-y-2\">\n {services.map((service) => (\n <li key={service.slug}>\n <a\n href={useExternalUrls && service.externalUrl ? service.externalUrl : service.localRoute}\n onClick={onServiceClick ? (e) => onServiceClick(service, e) : undefined}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n {service.name}\n </a>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Company column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Company\n </h4>\n <ul className=\"space-y-2\">\n {showPricing && (\n <li>\n <a\n href=\"/pricing\"\n onClick={onPricingClick}\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Pricing\n </a>\n </li>\n )}\n <li>\n <a\n href=\"/about\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n About\n </a>\n </li>\n <li>\n <a\n href=\"/contact\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Contact\n </a>\n </li>\n </ul>\n </div>\n\n {/* Legal column */}\n <div>\n <h4 className=\"text-[#E2E8F0] font-medium text-xs uppercase tracking-wider mb-3 font-[Montserrat,sans-serif]\">\n Legal\n </h4>\n <ul className=\"space-y-2\">\n <li>\n <a\n href=\"/privacy\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Privacy Policy\n </a>\n </li>\n <li>\n <a\n href=\"/terms\"\n className=\"text-xs text-[#8890A0] hover:text-[#E2E8F0] transition-colors\"\n >\n Terms of Service\n </a>\n </li>\n </ul>\n {showSignIn && (\n <div className=\"mt-4\">\n <a\n href=\"/login\"\n onClick={onSignInClick}\n className=\"text-xs text-[#8C34E9] hover:text-[#A855F7] transition-colors font-medium\"\n >\n Sign In\n </a>\n </div>\n )}\n </div>\n </div>\n\n {/* Custom content */}\n {children}\n\n {/* Bottom bar */}\n <div className=\"pt-6 border-t border-[#1E2738] flex flex-col sm:flex-row items-center justify-between gap-3\">\n <p className=\"text-xs text-[#596475]\">\n &copy; {currentYear} Oplytics.digital. All rights reserved.\n </p>\n <p className=\"text-xs text-[#3E4A5C]\">\n Built for manufacturing excellence\n </p>\n {typeof __APP_VERSION__ !== \"undefined\" && (\n <p className=\"text-xs text-[#3E4A5C]\">\n Platform v{__APP_VERSION__}\n </p>\n )}\n </div>\n </div>\n </footer>\n );\n}\n","/**\n * ServiceFooter — slim status bar for all Oplytics subdomain services.\n *\n * ~36px horizontal strip: [Logo + Tagline] [Health Monitor] [Copyright + Version]\n * Health monitor is visible only when showHealthMonitor is true (typically platform_admin).\n *\n * Oplytics dark theme: #0D1220 bg, #1E2738 border, #596475/#3E4A5C text, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\n\ndeclare const __APP_VERSION__: string;\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 /** Show health monitor strip (default: false) */\n showHealthMonitor?: boolean;\n /** CSS class for the footer container */\n className?: string;\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 showHealthMonitor = false,\n className,\n}: ServiceFooterProps) {\n const currentYear = new Date().getFullYear();\n const env = environment ? ENV_LABELS[environment] : null;\n const version = buildVersion ?? (typeof __APP_VERSION__ !== \"undefined\" ? __APP_VERSION__ : undefined);\n\n return (\n <footer\n className={cn(\n \"w-full border-t border-[#1E2738] bg-[#0D1220]\",\n \"flex items-center justify-between px-4\",\n \"h-9 min-h-[36px] shrink-0\",\n \"font-[Montserrat,sans-serif]\",\n className,\n )}\n >\n {/* Left: Logo + Tagline */}\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"w-5 h-5 rounded-full flex items-center justify-center shrink-0\"\n style={{ background: \"#8C34E9\" }}\n >\n <span className=\"text-white font-bold\" style={{ fontSize: 9 }}>O</span>\n </div>\n <span className=\"text-[10px] text-[#596475] whitespace-nowrap hidden sm:inline\">\n Operational Excellence. One Digital Platform.\n </span>\n </div>\n\n {/* Center: Health Monitor (admin only) */}\n {showHealthMonitor && (\n <div className=\"flex items-center gap-1.5 text-[10px] text-[#3E4A5C] min-w-0 overflow-hidden\">\n {enterpriseName && (\n <>\n <span className=\"whitespace-nowrap\">\n <span className=\"text-[#596475]\">{enterpriseName}</span>\n {enterpriseId != null && (\n <span className=\"text-[#3E4A5C]\"> ({enterpriseId})</span>\n )}\n </span>\n <Separator />\n </>\n )}\n {userEmail && (\n <>\n <span className=\"whitespace-nowrap text-[#596475]\">{userEmail}</span>\n {userRole && (\n <span className=\"text-[#3E4A5C]\"> ({userRole})</span>\n )}\n <Separator />\n </>\n )}\n {env && (\n <>\n <span className=\"whitespace-nowrap\">\n ENV:{\" \"}\n <span style={{ color: env.color }} className=\"font-semibold\">\n {env.label}\n </span>\n </span>\n <Separator />\n </>\n )}\n {version && (\n <span className=\"whitespace-nowrap text-[#3E4A5C] font-mono\">\n v.{version}\n </span>\n )}\n </div>\n )}\n\n {/* Right: Copyright + Version (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 && version && (\n <>\n <Separator />\n <span className=\"font-mono\">v.{version}</span>\n </>\n )}\n </div>\n </footer>\n );\n}\n\nfunction Separator() {\n return (\n <span className=\"text-[#1E2738] select-none\" aria-hidden>\n |\n </span>\n );\n}\n","/**\n * createServiceLayout — Factory function that generates a complete DashboardLayout\n * for any Oplytics subdomain from a SharedSidebarConfig.\n *\n * This is the primary API for subdomains. Instead of each subdomain maintaining\n * its own 200-350 line DashboardLayout, they call:\n *\n * const SQDCPLayout = createServiceLayout({ serviceName: \"SQDCP\", ... });\n *\n * And get a fully-featured layout with:\n * - Resizable sidebar with all standard features\n * - SharedPageHeader integration\n * - ReportingToolbar integration (optional)\n * - HierarchyProvider wrapping (optional)\n * - Auth gate with loading skeleton + sign-in fallback (optional)\n * - User footer with logout dropdown\n *\n * @module shared/components/createServiceLayout\n */\n\nimport React from \"react\";\nimport type { SharedSidebarConfig, MenuSection } from \"./SharedSidebar\";\nimport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize } from \"./SharedSidebar\";\n\n/* ── Service Layout Configuration ── */\n\nexport interface ServiceLayoutConfig extends SharedSidebarConfig {\n /** Show SharedPageHeader above content (default: true) */\n showPageHeader?: boolean;\n /** SharedPageHeader props — passed through when showPageHeader is true */\n pageHeaderProps?: {\n showHierarchy?: boolean;\n hierarchyMaxDepth?: number;\n showSidebarTrigger?: boolean;\n };\n /** Show ReportingToolbar in the header (default: false) */\n showReportingToolbar?: boolean;\n /** Module name passed to ReportingToolbar */\n reportingModuleName?: string;\n /** Wrap layout in HierarchyProvider (default: false) */\n wrapHierarchyProvider?: boolean;\n /** Show auth gate — loading skeleton + sign-in fallback (default: true) */\n showAuthGate?: boolean;\n /** Report submit handler */\n onReportSubmit?: (report: any) => Promise<void> | void;\n /** Feedback submit handler */\n onFeedbackSubmit?: (feedback: any) => Promise<void> | void;\n}\n\n/**\n * Creates a service-specific layout component from configuration.\n *\n * NOTE: This is a blueprint/factory specification. The actual React component\n * is assembled by each subdomain using their local UI primitives (Sidebar,\n * SidebarProvider, etc.) since these are not shared across subdomains yet.\n *\n * When subdomain consolidation (#311) is complete, this factory will directly\n * render the shared UI primitives. Until then, subdomains use this config\n * to standardise their DashboardLayout implementations.\n *\n * @example\n * ```tsx\n * // In SQDCP's DashboardLayout.tsx:\n * import { sqdcpLayoutConfig } from \"./sqdcp-config\";\n * // Use sqdcpLayoutConfig.menuSections, .backLink, etc. to build the layout\n * ```\n */\nexport function defineServiceLayout(config: ServiceLayoutConfig): ServiceLayoutConfig {\n return {\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n showPageHeader: true,\n showReportingToolbar: false,\n wrapHierarchyProvider: false,\n showAuthGate: true,\n ...config,\n storageKeyPrefix: config.storageKeyPrefix || config.serviceName.toLowerCase().replace(/\\s+/g, \"-\"),\n };\n}\n\n/* ── Pre-built Service Configurations ── */\n\n// These are the canonical configurations for each subdomain.\n// Subdomains should import and use these instead of defining their own.\n\nexport { isAdminRole, getInitials, isMenuItemActive, useSidebarResize };\n","/**\n * Canonical Service Layout Configurations for all Oplytics subdomains.\n *\n * Each subdomain imports its config from here and uses it to build its\n * DashboardLayout. This ensures consistency across all services while\n * allowing per-service customisation (menu items, features, etc.).\n *\n * When subdomain consolidation (#311) is complete, these configs will\n * drive the shared createServiceLayout factory directly.\n *\n * @module shared/components/serviceConfigs\n */\n\nimport type { ServiceLayoutConfig } from \"./createServiceLayout\";\n\n/* ── Icon placeholder types ── */\n// Icons are imported locally by each subdomain from lucide-react.\n// These configs use string identifiers that map to icon components.\n\nexport interface MenuItemConfig {\n iconName: string;\n label: string;\n path: string;\n}\n\nexport interface MenuSectionConfig {\n label?: string;\n items: MenuItemConfig[];\n adminOnly?: boolean;\n}\n\nexport interface ServiceConfig {\n serviceName: string;\n serviceAbbreviation: string;\n serviceIconName: string;\n menuSections: MenuSectionConfig[];\n backLink?: {\n label: string;\n path: string;\n };\n showPageHeader: boolean;\n showReportingToolbar: boolean;\n reportingModuleName?: string;\n wrapHierarchyProvider: boolean;\n showAuthGate: boolean;\n storageKeyPrefix: string;\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n}\n\n/* ── SQDCP ── */\n\nexport const sqdcpConfig: ServiceConfig = {\n serviceName: \"SQDCP\",\n serviceAbbreviation: \"S\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"PenLine\", label: \"Data Entry\", path: \"/entry\" },\n { iconName: \"ClipboardList\", label: \"Action Tracker\", path: \"/actions\" },\n { iconName: \"FileText\", label: \"Reports\", path: \"/reports\" },\n { iconName: \"Settings\", label: \"Admin\", path: \"/admin\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"SQDCP\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"sqdcp-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── OEE Manager ── */\n\nexport const oeeManagerConfig: ServiceConfig = {\n serviceName: \"OEE Manager\",\n serviceAbbreviation: \"o\",\n serviceIconName: \"BarChart3\",\n menuSections: [\n {\n items: [\n { iconName: \"BarChart3\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Database\", label: \"Data Input\", path: \"/data-input\" },\n { iconName: \"Clock\", label: \"Downtime\", path: \"/downtime\" },\n { iconName: \"Settings\", label: \"Settings\", path: \"/settings\" },\n ],\n },\n ],\n backLink: {\n label: \"Back to Portal\",\n path: \"https://portal.oplytics.digital\",\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"OEE Enterprise Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: false,\n storageKeyPrefix: \"oee-sidebar\",\n defaultWidth: 240,\n minWidth: 200,\n maxWidth: 400,\n};\n\n/* ── Action Manager ── */\n\nexport const actionManagerConfig: ServiceConfig = {\n serviceName: \"Action Manager\",\n serviceAbbreviation: \"A\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Teams\", path: \"/teams\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Action Manager\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"actionmanager-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Business Hub ── */\n\nexport const businessHubConfig: ServiceConfig = {\n serviceName: \"Business Hub\",\n serviceAbbreviation: \"B\",\n serviceIconName: \"LayoutDashboard\",\n menuSections: [\n {\n items: [\n { iconName: \"LayoutDashboard\", label: \"Dashboard\", path: \"/\" },\n { iconName: \"Users\", label: \"Analytics\", path: \"/analytics\" },\n ],\n },\n ],\n showPageHeader: true,\n showReportingToolbar: false,\n reportingModuleName: \"Business Hub\",\n wrapHierarchyProvider: false,\n showAuthGate: true,\n storageKeyPrefix: \"businesshub-sidebar\",\n defaultWidth: 280,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── Policy Deployment ── */\n\nexport const policyDeploymentConfig: ServiceConfig = {\n serviceName: \"Policy Deployment\",\n serviceAbbreviation: \"PD\",\n serviceIconName: \"FileStack\",\n menuSections: [\n {\n label: \"Analysis\",\n items: [\n { iconName: \"LayoutGrid\", label: \"Dashboard\", path: \"/app/policy-deployment\" },\n { iconName: \"Grid3X3\", label: \"X-Matrix\", path: \"/app/policy-deployment/xmatrix\" },\n { iconName: \"BarChart3\", label: \"Bowling Chart\", path: \"/app/policy-deployment/bowling\" },\n { iconName: \"FolderKanban\", label: \"Action Plans\", path: \"/app/policy-deployment/actions\" },\n { iconName: \"GitBranchPlus\", label: \"Catchball\", path: \"/app/policy-deployment/catchball\" },\n { iconName: \"Target\", label: \"Deployments\", path: \"/app/policy-deployment/deployments\" },\n ],\n },\n {\n label: \"Administration\",\n adminOnly: true,\n items: [\n { iconName: \"Settings\", label: \"Manage Policy\", path: \"/app/policy-deployment/manage\" },\n { iconName: \"Plug\", label: \"Integrations\", path: \"/app/policy-deployment/integrations\" },\n ],\n },\n ],\n backLink: {\n label: \"Service Hub\",\n path: \"/app\",\n },\n showPageHeader: true,\n showReportingToolbar: true,\n reportingModuleName: \"Policy Deployment\",\n wrapHierarchyProvider: true,\n showAuthGate: true,\n storageKeyPrefix: \"policy-sidebar\",\n defaultWidth: 260,\n minWidth: 200,\n maxWidth: 480,\n};\n\n/* ── All configs indexed by service key ── */\n\nexport const SERVICE_CONFIGS = {\n sqdcp: sqdcpConfig,\n oeeManager: oeeManagerConfig,\n actionManager: actionManagerConfig,\n businessHub: businessHubConfig,\n policyDeployment: policyDeploymentConfig,\n} as const;\n\nexport type ServiceKey = keyof typeof SERVICE_CONFIGS;\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs"]}
@@ -0,0 +1,75 @@
1
+ // src/hierarchy/orgHierarchy.ts
2
+ var ORG_HIERARCHY_EMPTY = {
3
+ enterprise: null,
4
+ businessUnit: null,
5
+ site: null,
6
+ area: null,
7
+ asset: null
8
+ };
9
+ var ORG_HIERARCHY_DATA_EMPTY = {
10
+ enterprises: [],
11
+ businessUnits: [],
12
+ sites: [],
13
+ areas: [],
14
+ assets: []
15
+ };
16
+ var ORG_HIERARCHY_LEVELS = [
17
+ "enterprise",
18
+ "businessUnit",
19
+ "site",
20
+ "area",
21
+ "asset"
22
+ ];
23
+ var ORG_HIERARCHY_LABELS = {
24
+ enterprise: "Enterprise",
25
+ businessUnit: "Business Unit",
26
+ site: "Site",
27
+ area: "Area",
28
+ asset: "Asset"
29
+ };
30
+
31
+ // src/hierarchy/enterpriseOverride.ts
32
+ var ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
33
+ function parseCookieValue(cookieHeader, name) {
34
+ const match = cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${name}=`));
35
+ if (!match) return null;
36
+ return decodeURIComponent(match.slice(name.length + 1));
37
+ }
38
+ function applyEnterpriseOverride(user, cookieHeader) {
39
+ if (!user || user.role !== "platform_admin") return user;
40
+ const activeEnterprise = parseCookieValue(
41
+ cookieHeader,
42
+ ACTIVE_ENTERPRISE_COOKIE
43
+ );
44
+ if (!activeEnterprise) return user;
45
+ const parsedId = parseInt(activeEnterprise, 10);
46
+ if (isNaN(parsedId) || parsedId <= 0) return user;
47
+ return { ...user, enterpriseId: parsedId };
48
+ }
49
+
50
+ // src/hierarchy/scopeHierarchy.ts
51
+ function scopeHierarchyToEnterprise(data, enterpriseId) {
52
+ if (!data || enterpriseId === null) {
53
+ return { enterprises: [], businessUnits: [], sites: [], areas: [], assets: [] };
54
+ }
55
+ const sites = data.sites.filter((s) => s.enterpriseId === enterpriseId);
56
+ const siteIds = new Set(sites.map((s) => s.id));
57
+ return {
58
+ enterprises: data.enterprises.filter((e) => e.id === enterpriseId),
59
+ businessUnits: data.businessUnits.filter((bu) => bu.enterpriseId === enterpriseId),
60
+ sites,
61
+ areas: data.areas.filter((a) => siteIds.has(a.siteId)),
62
+ assets: data.assets.filter((a) => siteIds.has(a.siteId))
63
+ };
64
+ }
65
+
66
+ export {
67
+ ORG_HIERARCHY_EMPTY,
68
+ ORG_HIERARCHY_DATA_EMPTY,
69
+ ORG_HIERARCHY_LEVELS,
70
+ ORG_HIERARCHY_LABELS,
71
+ ACTIVE_ENTERPRISE_COOKIE,
72
+ applyEnterpriseOverride,
73
+ scopeHierarchyToEnterprise
74
+ };
75
+ //# sourceMappingURL=chunk-NXSFRNI7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hierarchy/orgHierarchy.ts","../src/hierarchy/enterpriseOverride.ts","../src/hierarchy/scopeHierarchy.ts"],"sourcesContent":["/**\n * @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities\n *\n * Hierarchy: Enterprise > Business Unit > Site > Area > Asset\n *\n * Single source of truth for org-hierarchy types used by:\n * - Portal (direct DB via tRPC)\n * - All subdomains (via Portal Service API)\n *\n * The canonical data shape matches the Portal Service API response:\n * GET /api/service/hierarchy\n */\n\n/* ── Base Node ── */\n\nexport interface OrgHierarchyNode {\n id: number;\n name: string;\n code?: string;\n}\n\n/* ── Flat hierarchy data — matches Portal Service API /api/service/hierarchy ── */\n\nexport interface OrgHierarchyEnterprise extends OrgHierarchyNode {}\n\nexport interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {\n enterpriseId: number;\n}\n\nexport interface OrgHierarchySite extends OrgHierarchyNode {\n enterpriseId: number;\n businessUnitId: number;\n location?: string | null;\n}\n\nexport interface OrgHierarchyArea extends OrgHierarchyNode {\n siteId: number;\n}\n\nexport interface OrgHierarchyAsset extends OrgHierarchyNode {\n siteId: number;\n areaId: number;\n assetType?: string;\n}\n\n/**\n * Canonical flat hierarchy payload.\n * Every consumer (Portal tRPC, Service API REST) must return this shape.\n */\nexport interface OrgHierarchyData {\n enterprises: OrgHierarchyEnterprise[];\n businessUnits: OrgHierarchyBusinessUnit[];\n sites: OrgHierarchySite[];\n areas: OrgHierarchyArea[];\n assets: OrgHierarchyAsset[];\n}\n\n/* ── Selection state ── */\n\nexport interface OrgHierarchySelection {\n enterprise: OrgHierarchyNode | null;\n businessUnit: OrgHierarchyNode | null;\n site: OrgHierarchyNode | null;\n area: OrgHierarchyNode | null;\n asset: OrgHierarchyNode | null;\n}\n\nexport const ORG_HIERARCHY_EMPTY: OrgHierarchySelection = {\n enterprise: null,\n businessUnit: null,\n site: null,\n area: null,\n asset: null,\n};\n\n/**\n * An empty hierarchy payload. Use as the canonical \"no data\" value so every\n * consumer returns the same shape (never null/undefined for a missing tree).\n */\nexport const ORG_HIERARCHY_DATA_EMPTY: OrgHierarchyData = {\n enterprises: [],\n businessUnits: [],\n sites: [],\n areas: [],\n assets: [],\n};\n\n/* ── Context value ── */\n\nexport interface OrgHierarchyContextValue {\n /** Current selection at each level */\n selection: OrgHierarchySelection;\n\n /** Flat lists filtered by current selection */\n enterprises: OrgHierarchyNode[];\n businessUnits: OrgHierarchyNode[];\n sites: OrgHierarchyNode[];\n areas: OrgHierarchyNode[];\n assets: OrgHierarchyNode[];\n\n /** Setters — selecting a level clears all levels below it */\n selectEnterprise: (node: OrgHierarchyNode | null) => void;\n selectBusinessUnit: (node: OrgHierarchyNode | null) => void;\n selectSite: (node: OrgHierarchyNode | null) => void;\n selectArea: (node: OrgHierarchyNode | null) => void;\n selectAsset: (node: OrgHierarchyNode | null) => void;\n\n /** Whether the user can switch enterprises (platform_admin only) */\n canSwitchEnterprise: boolean;\n\n /** Loading state */\n loading: boolean;\n\n /** Breadcrumb trail as an array of { level, node } */\n breadcrumbs: { level: string; node: OrgHierarchyNode }[];\n}\n\n/* ── Hierarchy levels (for iteration) ── */\n\nexport const ORG_HIERARCHY_LEVELS = [\n \"enterprise\",\n \"businessUnit\",\n \"site\",\n \"area\",\n \"asset\",\n] as const;\n\nexport type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];\n\n/** Human-readable labels for each level */\nexport const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string> = {\n enterprise: \"Enterprise\",\n businessUnit: \"Business Unit\",\n site: \"Site\",\n area: \"Area\",\n asset: \"Asset\",\n};\n","/**\n * Enterprise Override — shared helper for platform_admin enterprise switching.\n *\n * When a platform_admin switches enterprise in the Portal UI, a cookie\n * `oplytics-active-enterprise` is set with the selected enterprise ID.\n * This cookie is shared across all *.oplytics.digital subdomains.\n *\n * Each subdomain's createContext should call applyEnterpriseOverride()\n * to override the user's DB-stored enterpriseId with the cookie value.\n *\n * Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect\n */\n\n/**\n * Cookie name for the platform_admin active enterprise override.\n * Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.\n */\nexport const ACTIVE_ENTERPRISE_COOKIE = \"oplytics-active-enterprise\";\n\n/**\n * Parse a single cookie value from a cookie header string.\n */\nfunction parseCookieValue(cookieHeader: string, name: string): string | null {\n const match = cookieHeader\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(`${name}=`));\n if (!match) return null;\n return decodeURIComponent(match.slice(name.length + 1));\n}\n\n/**\n * Apply the enterprise override for platform_admin users.\n *\n * If the user is a platform_admin and has the active enterprise cookie set,\n * returns a shallow copy of the user with the overridden enterpriseId.\n * Otherwise returns the user unchanged.\n *\n * @param user - The authenticated user (or null)\n * @param cookieHeader - The raw Cookie header string from the request\n * @returns The user with potentially overridden enterpriseId\n */\nexport function applyEnterpriseOverride<\n T extends { role: string; enterpriseId?: number | null },\n>(user: T | null, cookieHeader: string): T | null {\n if (!user || user.role !== \"platform_admin\") return user;\n\n const activeEnterprise = parseCookieValue(\n cookieHeader,\n ACTIVE_ENTERPRISE_COOKIE,\n );\n if (!activeEnterprise) return user;\n\n const parsedId = parseInt(activeEnterprise, 10);\n if (isNaN(parsedId) || parsedId <= 0) return user;\n\n // Return a shallow copy with the overridden enterpriseId\n return { ...user, enterpriseId: parsedId };\n}\n","/**\n * scopeHierarchyToEnterprise — the single source of truth for enterprise isolation.\n *\n * The Portal Service API (`GET /api/service/hierarchy`) is called with a\n * platform-level service key, so it returns every enterprise's hierarchy. This\n * function filters that payload down to ONE enterprise before it reaches a\n * subdomain client. It is pure and isomorphic (no I/O), so the same rule runs in\n * the fetch service, in tests, and anywhere else scoping is needed.\n *\n * Policy (deliberate — do not \"helpfully\" relax it):\n * - `null` data → empty hierarchy.\n * - `null` enterpriseId → empty hierarchy. Enterprise selection is owned solely\n * by the Portal switcher; a caller with no resolved enterprise (e.g. a\n * platform admin who hasn't picked one in Portal yet) sees nothing until they\n * select. Every non-admin always has exactly one enterprise, so they are\n * unaffected. Admin/Portal tooling that genuinely needs the full tree must use\n * the unscoped fetch, never this function.\n */\nimport type { OrgHierarchyData } from \"./orgHierarchy\";\n\nexport function scopeHierarchyToEnterprise(\n data: OrgHierarchyData | null,\n enterpriseId: number | null,\n): OrgHierarchyData {\n if (!data || enterpriseId === null) {\n return { enterprises: [], businessUnits: [], sites: [], areas: [], assets: [] };\n }\n\n const sites = data.sites.filter((s) => s.enterpriseId === enterpriseId);\n const siteIds = new Set(sites.map((s) => s.id));\n\n return {\n enterprises: data.enterprises.filter((e) => e.id === enterpriseId),\n businessUnits: data.businessUnits.filter((bu) => bu.enterpriseId === enterpriseId),\n sites,\n areas: data.areas.filter((a) => siteIds.has(a.siteId)),\n assets: data.assets.filter((a) => siteIds.has(a.siteId)),\n };\n}\n"],"mappings":";AAmEO,IAAM,sBAA6C;AAAA,EACxD,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAMO,IAAM,2BAA6C;AAAA,EACxD,aAAa,CAAC;AAAA,EACd,eAAe,CAAC;AAAA,EAChB,OAAO,CAAC;AAAA,EACR,OAAO,CAAC;AAAA,EACR,QAAQ,CAAC;AACX;AAkCO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,uBAA0D;AAAA,EACrE,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;;;ACvHO,IAAM,2BAA2B;AAKxC,SAAS,iBAAiB,cAAsB,MAA6B;AAC3E,QAAM,QAAQ,aACX,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,CAAC;AACvC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,mBAAmB,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC;AACxD;AAaO,SAAS,wBAEd,MAAgB,cAAgC;AAChD,MAAI,CAAC,QAAQ,KAAK,SAAS,iBAAkB,QAAO;AAEpD,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,EACF;AACA,MAAI,CAAC,iBAAkB,QAAO;AAE9B,QAAM,WAAW,SAAS,kBAAkB,EAAE;AAC9C,MAAI,MAAM,QAAQ,KAAK,YAAY,EAAG,QAAO;AAG7C,SAAO,EAAE,GAAG,MAAM,cAAc,SAAS;AAC3C;;;ACtCO,SAAS,2BACd,MACA,cACkB;AAClB,MAAI,CAAC,QAAQ,iBAAiB,MAAM;AAClC,WAAO,EAAE,aAAa,CAAC,GAAG,eAAe,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,EAChF;AAEA,QAAM,QAAQ,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,iBAAiB,YAAY;AACtE,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAE9C,SAAO;AAAA,IACL,aAAa,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,OAAO,YAAY;AAAA,IACjE,eAAe,KAAK,cAAc,OAAO,CAAC,OAAO,GAAG,iBAAiB,YAAY;AAAA,IACjF;AAAA,IACA,OAAO,KAAK,MAAM,OAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,MAAM,CAAC;AAAA,IACrD,QAAQ,KAAK,OAAO,OAAO,CAAC,MAAM,QAAQ,IAAI,EAAE,MAAM,CAAC;AAAA,EACzD;AACF;","names":[]}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities
3
+ *
4
+ * Hierarchy: Enterprise > Business Unit > Site > Area > Asset
5
+ *
6
+ * Single source of truth for org-hierarchy types used by:
7
+ * - Portal (direct DB via tRPC)
8
+ * - All subdomains (via Portal Service API)
9
+ *
10
+ * The canonical data shape matches the Portal Service API response:
11
+ * GET /api/service/hierarchy
12
+ */
13
+ interface OrgHierarchyNode {
14
+ id: number;
15
+ name: string;
16
+ code?: string;
17
+ }
18
+ interface OrgHierarchyEnterprise extends OrgHierarchyNode {
19
+ }
20
+ interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {
21
+ enterpriseId: number;
22
+ }
23
+ interface OrgHierarchySite extends OrgHierarchyNode {
24
+ enterpriseId: number;
25
+ businessUnitId: number;
26
+ location?: string | null;
27
+ }
28
+ interface OrgHierarchyArea extends OrgHierarchyNode {
29
+ siteId: number;
30
+ }
31
+ interface OrgHierarchyAsset extends OrgHierarchyNode {
32
+ siteId: number;
33
+ areaId: number;
34
+ assetType?: string;
35
+ }
36
+ /**
37
+ * Canonical flat hierarchy payload.
38
+ * Every consumer (Portal tRPC, Service API REST) must return this shape.
39
+ */
40
+ interface OrgHierarchyData {
41
+ enterprises: OrgHierarchyEnterprise[];
42
+ businessUnits: OrgHierarchyBusinessUnit[];
43
+ sites: OrgHierarchySite[];
44
+ areas: OrgHierarchyArea[];
45
+ assets: OrgHierarchyAsset[];
46
+ }
47
+ interface OrgHierarchySelection {
48
+ enterprise: OrgHierarchyNode | null;
49
+ businessUnit: OrgHierarchyNode | null;
50
+ site: OrgHierarchyNode | null;
51
+ area: OrgHierarchyNode | null;
52
+ asset: OrgHierarchyNode | null;
53
+ }
54
+ declare const ORG_HIERARCHY_EMPTY: OrgHierarchySelection;
55
+ /**
56
+ * An empty hierarchy payload. Use as the canonical "no data" value so every
57
+ * consumer returns the same shape (never null/undefined for a missing tree).
58
+ */
59
+ declare const ORG_HIERARCHY_DATA_EMPTY: OrgHierarchyData;
60
+ interface OrgHierarchyContextValue {
61
+ /** Current selection at each level */
62
+ selection: OrgHierarchySelection;
63
+ /** Flat lists filtered by current selection */
64
+ enterprises: OrgHierarchyNode[];
65
+ businessUnits: OrgHierarchyNode[];
66
+ sites: OrgHierarchyNode[];
67
+ areas: OrgHierarchyNode[];
68
+ assets: OrgHierarchyNode[];
69
+ /** Setters — selecting a level clears all levels below it */
70
+ selectEnterprise: (node: OrgHierarchyNode | null) => void;
71
+ selectBusinessUnit: (node: OrgHierarchyNode | null) => void;
72
+ selectSite: (node: OrgHierarchyNode | null) => void;
73
+ selectArea: (node: OrgHierarchyNode | null) => void;
74
+ selectAsset: (node: OrgHierarchyNode | null) => void;
75
+ /** Whether the user can switch enterprises (platform_admin only) */
76
+ canSwitchEnterprise: boolean;
77
+ /** Loading state */
78
+ loading: boolean;
79
+ /** Breadcrumb trail as an array of { level, node } */
80
+ breadcrumbs: {
81
+ level: string;
82
+ node: OrgHierarchyNode;
83
+ }[];
84
+ }
85
+ declare const ORG_HIERARCHY_LEVELS: readonly ["enterprise", "businessUnit", "site", "area", "asset"];
86
+ type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];
87
+ /** Human-readable labels for each level */
88
+ declare const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string>;
89
+
90
+ /**
91
+ * Enterprise Override — shared helper for platform_admin enterprise switching.
92
+ *
93
+ * When a platform_admin switches enterprise in the Portal UI, a cookie
94
+ * `oplytics-active-enterprise` is set with the selected enterprise ID.
95
+ * This cookie is shared across all *.oplytics.digital subdomains.
96
+ *
97
+ * Each subdomain's createContext should call applyEnterpriseOverride()
98
+ * to override the user's DB-stored enterpriseId with the cookie value.
99
+ *
100
+ * Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect
101
+ */
102
+ /**
103
+ * Cookie name for the platform_admin active enterprise override.
104
+ * Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.
105
+ */
106
+ declare const ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
107
+ /**
108
+ * Apply the enterprise override for platform_admin users.
109
+ *
110
+ * If the user is a platform_admin and has the active enterprise cookie set,
111
+ * returns a shallow copy of the user with the overridden enterpriseId.
112
+ * Otherwise returns the user unchanged.
113
+ *
114
+ * @param user - The authenticated user (or null)
115
+ * @param cookieHeader - The raw Cookie header string from the request
116
+ * @returns The user with potentially overridden enterpriseId
117
+ */
118
+ declare function applyEnterpriseOverride<T extends {
119
+ role: string;
120
+ enterpriseId?: number | null;
121
+ }>(user: T | null, cookieHeader: string): T | null;
122
+
123
+ /**
124
+ * scopeHierarchyToEnterprise — the single source of truth for enterprise isolation.
125
+ *
126
+ * The Portal Service API (`GET /api/service/hierarchy`) is called with a
127
+ * platform-level service key, so it returns every enterprise's hierarchy. This
128
+ * function filters that payload down to ONE enterprise before it reaches a
129
+ * subdomain client. It is pure and isomorphic (no I/O), so the same rule runs in
130
+ * the fetch service, in tests, and anywhere else scoping is needed.
131
+ *
132
+ * Policy (deliberate — do not "helpfully" relax it):
133
+ * - `null` data → empty hierarchy.
134
+ * - `null` enterpriseId → empty hierarchy. Enterprise selection is owned solely
135
+ * by the Portal switcher; a caller with no resolved enterprise (e.g. a
136
+ * platform admin who hasn't picked one in Portal yet) sees nothing until they
137
+ * select. Every non-admin always has exactly one enterprise, so they are
138
+ * unaffected. Admin/Portal tooling that genuinely needs the full tree must use
139
+ * the unscoped fetch, never this function.
140
+ */
141
+
142
+ declare function scopeHierarchyToEnterprise(data: OrgHierarchyData | null, enterpriseId: number | null): OrgHierarchyData;
143
+
144
+ export { ACTIVE_ENTERPRISE_COOKIE, ORG_HIERARCHY_DATA_EMPTY, ORG_HIERARCHY_EMPTY, ORG_HIERARCHY_LABELS, ORG_HIERARCHY_LEVELS, type OrgHierarchyArea, type OrgHierarchyAsset, type OrgHierarchyBusinessUnit, type OrgHierarchyContextValue, type OrgHierarchyData, type OrgHierarchyEnterprise, type OrgHierarchyLevel, type OrgHierarchyNode, type OrgHierarchySelection, type OrgHierarchySite, applyEnterpriseOverride, scopeHierarchyToEnterprise };
@@ -0,0 +1,19 @@
1
+ import {
2
+ ACTIVE_ENTERPRISE_COOKIE,
3
+ ORG_HIERARCHY_DATA_EMPTY,
4
+ ORG_HIERARCHY_EMPTY,
5
+ ORG_HIERARCHY_LABELS,
6
+ ORG_HIERARCHY_LEVELS,
7
+ applyEnterpriseOverride,
8
+ scopeHierarchyToEnterprise
9
+ } from "../chunk-NXSFRNI7.js";
10
+ export {
11
+ ACTIVE_ENTERPRISE_COOKIE,
12
+ ORG_HIERARCHY_DATA_EMPTY,
13
+ ORG_HIERARCHY_EMPTY,
14
+ ORG_HIERARCHY_LABELS,
15
+ ORG_HIERARCHY_LEVELS,
16
+ applyEnterpriseOverride,
17
+ scopeHierarchyToEnterprise
18
+ };
19
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,123 +1,8 @@
1
+ import { OrgHierarchyData, OrgHierarchyContextValue } from './core.js';
2
+ export { ACTIVE_ENTERPRISE_COOKIE, ORG_HIERARCHY_DATA_EMPTY, ORG_HIERARCHY_EMPTY, ORG_HIERARCHY_LABELS, ORG_HIERARCHY_LEVELS, OrgHierarchyArea, OrgHierarchyAsset, OrgHierarchyBusinessUnit, OrgHierarchyEnterprise, OrgHierarchyLevel, OrgHierarchyNode, OrgHierarchySelection, OrgHierarchySite, applyEnterpriseOverride, scopeHierarchyToEnterprise } from './core.js';
1
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
4
  import { ReactNode } from 'react';
3
5
 
4
- /**
5
- * @oplytics/shared-ui/hierarchy — Org Hierarchy Types & Utilities
6
- *
7
- * Hierarchy: Enterprise > Business Unit > Site > Area > Asset
8
- *
9
- * Single source of truth for org-hierarchy types used by:
10
- * - Portal (direct DB via tRPC)
11
- * - All subdomains (via Portal Service API)
12
- *
13
- * The canonical data shape matches the Portal Service API response:
14
- * GET /api/service/hierarchy
15
- */
16
- interface OrgHierarchyNode {
17
- id: number;
18
- name: string;
19
- code?: string;
20
- }
21
- interface OrgHierarchyEnterprise extends OrgHierarchyNode {
22
- }
23
- interface OrgHierarchyBusinessUnit extends OrgHierarchyNode {
24
- enterpriseId: number;
25
- }
26
- interface OrgHierarchySite extends OrgHierarchyNode {
27
- enterpriseId: number;
28
- businessUnitId: number;
29
- location?: string | null;
30
- }
31
- interface OrgHierarchyArea extends OrgHierarchyNode {
32
- siteId: number;
33
- }
34
- interface OrgHierarchyAsset extends OrgHierarchyNode {
35
- siteId: number;
36
- areaId: number;
37
- assetType?: string;
38
- }
39
- /**
40
- * Canonical flat hierarchy payload.
41
- * Every consumer (Portal tRPC, Service API REST) must return this shape.
42
- */
43
- interface OrgHierarchyData {
44
- enterprises: OrgHierarchyEnterprise[];
45
- businessUnits: OrgHierarchyBusinessUnit[];
46
- sites: OrgHierarchySite[];
47
- areas: OrgHierarchyArea[];
48
- assets: OrgHierarchyAsset[];
49
- }
50
- interface OrgHierarchySelection {
51
- enterprise: OrgHierarchyNode | null;
52
- businessUnit: OrgHierarchyNode | null;
53
- site: OrgHierarchyNode | null;
54
- area: OrgHierarchyNode | null;
55
- asset: OrgHierarchyNode | null;
56
- }
57
- declare const ORG_HIERARCHY_EMPTY: OrgHierarchySelection;
58
- interface OrgHierarchyContextValue {
59
- /** Current selection at each level */
60
- selection: OrgHierarchySelection;
61
- /** Flat lists filtered by current selection */
62
- enterprises: OrgHierarchyNode[];
63
- businessUnits: OrgHierarchyNode[];
64
- sites: OrgHierarchyNode[];
65
- areas: OrgHierarchyNode[];
66
- assets: OrgHierarchyNode[];
67
- /** Setters — selecting a level clears all levels below it */
68
- selectEnterprise: (node: OrgHierarchyNode | null) => void;
69
- selectBusinessUnit: (node: OrgHierarchyNode | null) => void;
70
- selectSite: (node: OrgHierarchyNode | null) => void;
71
- selectArea: (node: OrgHierarchyNode | null) => void;
72
- selectAsset: (node: OrgHierarchyNode | null) => void;
73
- /** Whether the user can switch enterprises (platform_admin only) */
74
- canSwitchEnterprise: boolean;
75
- /** Loading state */
76
- loading: boolean;
77
- /** Breadcrumb trail as an array of { level, node } */
78
- breadcrumbs: {
79
- level: string;
80
- node: OrgHierarchyNode;
81
- }[];
82
- }
83
- declare const ORG_HIERARCHY_LEVELS: readonly ["enterprise", "businessUnit", "site", "area", "asset"];
84
- type OrgHierarchyLevel = (typeof ORG_HIERARCHY_LEVELS)[number];
85
- /** Human-readable labels for each level */
86
- declare const ORG_HIERARCHY_LABELS: Record<OrgHierarchyLevel, string>;
87
-
88
- /**
89
- * Enterprise Override — shared helper for platform_admin enterprise switching.
90
- *
91
- * When a platform_admin switches enterprise in the Portal UI, a cookie
92
- * `oplytics-active-enterprise` is set with the selected enterprise ID.
93
- * This cookie is shared across all *.oplytics.digital subdomains.
94
- *
95
- * Each subdomain's createContext should call applyEnterpriseOverride()
96
- * to override the user's DB-stored enterpriseId with the cookie value.
97
- *
98
- * Used by: Portal, SQDCP, OEE Manager, Action Manager, Business Hub, Connect
99
- */
100
- /**
101
- * Cookie name for the platform_admin active enterprise override.
102
- * Shared across all *.oplytics.digital subdomains via the .oplytics.digital domain.
103
- */
104
- declare const ACTIVE_ENTERPRISE_COOKIE = "oplytics-active-enterprise";
105
- /**
106
- * Apply the enterprise override for platform_admin users.
107
- *
108
- * If the user is a platform_admin and has the active enterprise cookie set,
109
- * returns a shallow copy of the user with the overridden enterpriseId.
110
- * Otherwise returns the user unchanged.
111
- *
112
- * @param user - The authenticated user (or null)
113
- * @param cookieHeader - The raw Cookie header string from the request
114
- * @returns The user with potentially overridden enterpriseId
115
- */
116
- declare function applyEnterpriseOverride<T extends {
117
- role: string;
118
- enterpriseId?: number | null;
119
- }>(user: T | null, cookieHeader: string): T | null;
120
-
121
6
  interface CreateOrgHierarchyContextOptions {
122
7
  /**
123
8
  * Unique sessionStorage key for this subdomain's selection persistence.
@@ -176,4 +61,4 @@ interface HierarchyNavigatorProps {
176
61
  }
177
62
  declare function HierarchyNavigator({ hierarchy: ctx, compact, maxDepth, className, renderDropdown, }: HierarchyNavigatorProps): react_jsx_runtime.JSX.Element;
178
63
 
179
- export { ACTIVE_ENTERPRISE_COOKIE, type CreateOrgHierarchyContextOptions, type DropdownRenderProps, HierarchyNavigator, type HierarchyNavigatorProps, ORG_HIERARCHY_EMPTY, ORG_HIERARCHY_LABELS, ORG_HIERARCHY_LEVELS, type OrgHierarchyArea, type OrgHierarchyAsset, type OrgHierarchyBusinessUnit, type OrgHierarchyContextValue, type OrgHierarchyData, type OrgHierarchyEnterprise, type OrgHierarchyLevel, type OrgHierarchyNode, type OrgHierarchySelection, type OrgHierarchySite, applyEnterpriseOverride, createOrgHierarchyContext };
64
+ export { type CreateOrgHierarchyContextOptions, type DropdownRenderProps, HierarchyNavigator, type HierarchyNavigatorProps, OrgHierarchyContextValue, OrgHierarchyData, createOrgHierarchyContext };