@pablo2410/shared-ui 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/ErrorBoundary.tsx","../../src/components/NotFound.tsx","../../src/components/ManusDialog.tsx","../../src/components/AIChatBox.tsx","../../src/components/MapView.tsx","../../src/components/ViewOnlyBadge.tsx"],"sourcesContent":["/**\n * ErrorBoundary — standard error boundary for all Oplytics subdomains.\n *\n * Catches render errors and displays a fallback UI with Oplytics branding.\n * Optionally calls an onError callback for error reporting.\n */\nimport { Component, type ReactNode, type ErrorInfo } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { AlertTriangle, RefreshCw } from \"lucide-react\";\n\nexport interface ErrorBoundaryProps {\n /** Fallback UI to show when an error is caught. If not provided, uses the default Oplytics error UI. */\n fallback?: ReactNode;\n /** Error handler callback — use for logging/reporting */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Children to wrap */\n children: ReactNode;\n /** CSS class for the fallback container */\n className?: string;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n console.error(\"[ErrorBoundary] Caught error:\", error, errorInfo);\n this.props.onError?.(error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState({ hasError: false, error: null });\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-[400px] p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n this.props.className\n )}\n >\n <div className=\"flex flex-col items-center gap-4 max-w-md text-center\">\n <div className=\"w-14 h-14 rounded-full bg-[#ef4444]/10 flex items-center justify-center\">\n <AlertTriangle className=\"h-7 w-7 text-[#ef4444]\" />\n </div>\n <h2 className=\"text-lg font-semibold font-[Montserrat,sans-serif]\">\n Something went wrong\n </h2>\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif]\">\n An unexpected error occurred. Please try again or contact support if the problem persists.\n </p>\n {this.state.error && (\n <pre className=\"mt-2 p-3 rounded-lg bg-[#0D1220] border border-[#1E2738] text-xs text-[#596475] max-w-full overflow-x-auto font-[JetBrains_Mono,monospace]\">\n {this.state.error.message}\n </pre>\n )}\n <button\n onClick={this.handleRetry}\n className={cn(\n \"mt-2 flex items-center gap-2 px-4 py-2 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <RefreshCw className=\"h-4 w-4\" />\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","/**\n * NotFound — standard 404 page for all Oplytics subdomains.\n *\n * Displays a branded \"Page Not Found\" message with navigation options.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Home, ArrowLeft } from \"lucide-react\";\n\nexport interface NotFoundPageProps {\n /** Title text (default: \"Page Not Found\") */\n title?: string;\n /** Description text */\n description?: string;\n /** Home path to navigate to (default: \"/\") */\n homePath?: string;\n /** Back button handler (default: window.history.back) */\n onBack?: () => void;\n /** Home button handler — if provided, overrides the <a> link */\n onHome?: () => void;\n /** CSS class for the container */\n className?: string;\n}\n\nexport function NotFoundPage({\n title = \"Page Not Found\",\n description = \"The page you're looking for doesn't exist or has been moved.\",\n homePath = \"/\",\n onBack,\n onHome,\n className,\n}: NotFoundPageProps) {\n const handleBack = onBack || (() => {\n if (typeof window !== \"undefined\") {\n window.history.back();\n }\n });\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-screen p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-6 max-w-md text-center\">\n {/* 404 number */}\n <div className=\"relative\">\n <span\n className=\"text-[120px] font-bold leading-none font-[Montserrat,sans-serif]\"\n style={{\n background: \"linear-gradient(135deg, #8C34E9 0%, #14b8a6 100%)\",\n WebkitBackgroundClip: \"text\",\n WebkitTextFillColor: \"transparent\",\n opacity: 0.15,\n }}\n >\n 404\n </span>\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div\n className=\"w-16 h-16 rounded-full flex items-center justify-center\"\n style={{ background: \"rgba(140, 52, 233, 0.1)\", border: \"1px solid rgba(140, 52, 233, 0.2)\" }}\n >\n <span className=\"text-2xl font-bold text-[#8C34E9] font-[Montserrat,sans-serif]\">?</span>\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h1 className=\"text-2xl font-bold font-[Montserrat,sans-serif] text-[#E2E8F0]\">\n {title}\n </h1>\n\n {/* Description */}\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif] leading-relaxed\">\n {description}\n </p>\n\n {/* Action buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 mt-2\">\n <button\n onClick={handleBack}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"border border-[#1E2738] text-[#8890A0] text-sm font-medium\",\n \"hover:bg-[#1E2738] hover:text-[#E2E8F0] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Go Back\n </button>\n\n {onHome ? (\n <button\n onClick={onHome}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </button>\n ) : (\n <a\n href={homePath}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium no-underline\",\n \"hover:bg-[#7928d4] transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </a>\n )}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * ManusDialog — standard dialog/modal wrapper for Oplytics subdomains.\n *\n * Provides a consistent modal overlay with Oplytics branding.\n * Uses native HTML <dialog> element for accessibility.\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { X } from \"lucide-react\";\nimport { useEffect, useRef, useCallback, type ReactNode } from \"react\";\n\nexport interface ManusDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Close handler */\n onClose: () => void;\n /** Dialog title */\n title: string;\n /** Optional subtitle */\n subtitle?: string;\n /** Optional logo/icon to show in the header */\n logo?: string | ReactNode;\n /** Dialog content */\n children: ReactNode;\n /** Footer content (e.g. action buttons) */\n footer?: ReactNode;\n /** Max width class (default: \"max-w-md\") */\n maxWidth?: string;\n /** CSS class for the dialog panel */\n className?: string;\n /** Whether clicking the overlay closes the dialog (default: true) */\n closeOnOverlay?: boolean;\n}\n\nexport function ManusDialog({\n open,\n onClose,\n title,\n subtitle,\n logo,\n children,\n footer,\n maxWidth = \"max-w-md\",\n className,\n closeOnOverlay = true,\n}: ManusDialogProps) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open && !dialog.open) {\n dialog.showModal();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open]);\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (closeOnOverlay && e.target === dialogRef.current) {\n onClose();\n }\n },\n [closeOnOverlay, onClose]\n );\n\n const handleCancel = useCallback(\n (e: React.SyntheticEvent) => {\n e.preventDefault();\n onClose();\n },\n [onClose]\n );\n\n return (\n <dialog\n ref={dialogRef}\n onClick={handleBackdropClick}\n onCancel={handleCancel}\n className={cn(\n \"backdrop:bg-black/60 backdrop:backdrop-blur-sm\",\n \"bg-transparent p-0 m-auto\",\n \"open:animate-in open:fade-in-0 open:zoom-in-95\",\n maxWidth,\n \"w-full\"\n )}\n >\n <div\n className={cn(\n \"bg-[#0F1525] border border-[#1E2738] rounded-xl shadow-xl\",\n \"text-[#E2E8F0] font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between p-5 border-b border-[#1E2738]\">\n <div className=\"flex items-center gap-3\">\n {logo && (\n <div className=\"shrink-0\">\n {typeof logo === \"string\" ? (\n <img\n src={logo}\n alt=\"Dialog graphic\"\n className=\"w-10 h-10 rounded-md\"\n />\n ) : (\n logo\n )}\n </div>\n )}\n <div>\n <h2 className=\"text-base font-semibold font-[Montserrat,sans-serif]\">\n {title}\n </h2>\n {subtitle && (\n <p className=\"text-xs text-[#8890A0] mt-0.5\">{subtitle}</p>\n )}\n </div>\n </div>\n <button\n onClick={onClose}\n className={cn(\n \"shrink-0 p-1.5 rounded-md -mt-1 -mr-1\",\n \"text-[#596475] 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 >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"p-5\">{children}</div>\n\n {/* Footer */}\n {footer && (\n <div className=\"px-5 pb-5 pt-2 flex items-center justify-end gap-2\">\n {footer}\n </div>\n )}\n </div>\n </dialog>\n );\n}\n","/**\n * AIChatBox — full-featured chat interface for Oplytics subdomains.\n *\n * Provides a chat UI with message history, input area, and streaming support.\n * The actual LLM integration is handled by the consuming app via callbacks.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport { Send, Bot, User, Loader2, X, Minimize2, Maximize2 } from \"lucide-react\";\nimport {\n useState,\n useRef,\n useCallback,\n useEffect,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\n\n/* ── Message types ── */\n\nexport interface ChatMessage {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n timestamp: number;\n /** Whether the message is still streaming */\n isStreaming?: boolean;\n}\n\n/* ── Component props ── */\n\nexport interface AIChatBoxProps {\n /** Chat messages to display */\n messages: ChatMessage[];\n /** Handler when user sends a message */\n onSend: (message: string) => void;\n /** Whether the assistant is currently generating a response */\n isLoading?: boolean;\n /** Placeholder text for the input */\n placeholder?: string;\n /** Title for the chat header */\n title?: string;\n /** Whether to show the chat in a floating panel (default: false — inline) */\n floating?: boolean;\n /** Whether the floating panel is open */\n isOpen?: boolean;\n /** Toggle floating panel open/closed */\n onToggle?: () => void;\n /** Render prop for message content (e.g. to use Streamdown for markdown) */\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n /** Max height for the messages area (default: \"400px\") */\n maxHeight?: string;\n /** CSS class for the container */\n className?: string;\n /** Whether to show the header (default: true) */\n showHeader?: boolean;\n}\n\n/* ── Message bubble ── */\n\nfunction MessageBubble({\n message,\n renderContent,\n}: {\n message: ChatMessage;\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n}) {\n const isUser = message.role === \"user\";\n\n return (\n <div\n className={cn(\n \"flex gap-3 px-4 py-3\",\n isUser ? \"flex-row-reverse\" : \"flex-row\"\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"shrink-0 w-7 h-7 rounded-full flex items-center justify-center\",\n isUser ? \"bg-[#8C34E9]/20\" : \"bg-[#14b8a6]/20\"\n )}\n >\n {isUser ? (\n <User className=\"h-3.5 w-3.5 text-[#A855F7]\" />\n ) : (\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n )}\n </div>\n\n {/* Content */}\n <div\n className={cn(\n \"max-w-[80%] rounded-xl px-3.5 py-2.5 text-sm\",\n \"font-[Space_Grotesk,sans-serif]\",\n isUser\n ? \"bg-[#8C34E9]/15 text-[#E2E8F0] rounded-tr-sm\"\n : \"bg-[#1E2738] text-[#C4CDD9] rounded-tl-sm\"\n )}\n >\n {renderContent ? (\n renderContent(message.content, message.isStreaming)\n ) : (\n <p className=\"whitespace-pre-wrap break-words\">{message.content}</p>\n )}\n {message.isStreaming && (\n <span className=\"inline-block w-1.5 h-4 bg-[#8C34E9] animate-pulse ml-0.5 align-text-bottom rounded-sm\" />\n )}\n </div>\n </div>\n );\n}\n\n/* ── Main component ── */\n\nexport function AIChatBox({\n messages,\n onSend,\n isLoading = false,\n placeholder = \"Type a message...\",\n title = \"AI Assistant\",\n floating = false,\n isOpen = true,\n onToggle,\n renderContent,\n maxHeight = \"400px\",\n className,\n showHeader = true,\n}: AIChatBoxProps) {\n const [input, setInput] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n // Auto-scroll to bottom\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages]);\n\n const handleSend = useCallback(() => {\n const trimmed = input.trim();\n if (!trimmed || isLoading) return;\n onSend(trimmed);\n setInput(\"\");\n // Reset textarea height\n if (inputRef.current) {\n inputRef.current.style.height = \"auto\";\n }\n }, [input, isLoading, onSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend]\n );\n\n // Auto-resize textarea\n const handleInput = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInput(e.target.value);\n const el = e.target;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 120)}px`;\n }, []);\n\n // Floating mode: toggle button\n if (floating && !isOpen) {\n return (\n <button\n onClick={onToggle}\n className={cn(\n \"fixed bottom-6 right-6 z-50\",\n \"w-12 h-12 rounded-full shadow-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"flex items-center justify-center\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2\"\n )}\n title=\"Open chat\"\n >\n <Bot className=\"h-5 w-5\" />\n </button>\n );\n }\n\n const chatContent = (\n <div\n className={cn(\n \"flex flex-col bg-[#0F1525] border border-[#1E2738] rounded-xl overflow-hidden\",\n floating && \"fixed bottom-6 right-6 z-50 w-[380px] shadow-2xl\",\n className\n )}\n >\n {/* Header */}\n {showHeader && (\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-[#1E2738] bg-[#0D1220]\">\n <div className=\"flex items-center gap-2\">\n <Bot className=\"h-4 w-4 text-[#14b8a6]\" />\n <span className=\"text-sm font-semibold text-[#E2E8F0] font-[Montserrat,sans-serif]\">\n {title}\n </span>\n </div>\n {floating && onToggle && (\n <button\n onClick={onToggle}\n className=\"p-1 rounded-md text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n )}\n\n {/* Messages */}\n <div\n className=\"flex-1 overflow-y-auto py-2\"\n style={{ maxHeight }}\n >\n {messages.length === 0 && (\n <div className=\"flex flex-col items-center justify-center h-full py-8 text-center\">\n <Bot className=\"h-8 w-8 text-[#596475] mb-3\" />\n <p className=\"text-sm text-[#596475] font-[Space_Grotesk,sans-serif]\">\n Start a conversation\n </p>\n </div>\n )}\n {messages.map((msg) => (\n <MessageBubble\n key={msg.id}\n message={msg}\n renderContent={renderContent}\n />\n ))}\n {isLoading && messages[messages.length - 1]?.role !== \"assistant\" && (\n <div className=\"flex gap-3 px-4 py-3\">\n <div className=\"shrink-0 w-7 h-7 rounded-full bg-[#14b8a6]/20 flex items-center justify-center\">\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n </div>\n <div className=\"flex items-center gap-1.5 px-3.5 py-2.5 rounded-xl bg-[#1E2738] rounded-tl-sm\">\n <Loader2 className=\"h-3.5 w-3.5 text-[#596475] animate-spin\" />\n <span className=\"text-xs text-[#596475]\">Thinking...</span>\n </div>\n </div>\n )}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Input area */}\n <div className=\"border-t border-[#1E2738] p-3\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={handleInput}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n rows={1}\n className={cn(\n \"flex-1 resize-none rounded-lg px-3 py-2\",\n \"bg-[#0A0E1A] border border-[#1E2738] text-sm text-[#E2E8F0]\",\n \"placeholder:text-[#596475]\",\n \"focus:outline-none focus:border-[#8C34E9]\",\n \"font-[Space_Grotesk,sans-serif]\",\n \"transition-colors\"\n )}\n disabled={isLoading}\n />\n <button\n onClick={handleSend}\n disabled={!input.trim() || isLoading}\n className={cn(\n \"shrink-0 p-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"disabled:opacity-40 disabled:cursor-not-allowed\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </div>\n </div>\n );\n\n return chatContent;\n}\n","/**\n * MapView — Google Maps integration with Manus proxy authentication.\n *\n * Provides a MapView component with onMapReady callback for initialising\n * Google Maps services (Places, Geocoder, Directions, Drawing, etc.).\n * All map functionality works directly in the browser via the Manus proxy.\n *\n * Usage:\n * <MapView\n * apiKey={import.meta.env.VITE_FRONTEND_FORGE_API_KEY}\n * forgeBaseUrl={import.meta.env.VITE_FRONTEND_FORGE_API_URL}\n * initialCenter={{ lat: 40.7128, lng: -74.0060 }}\n * initialZoom={15}\n * onMapReady={(map) => { mapRef.current = map; }}\n * />\n */\n\n/// <reference types=\"@types/google.maps\" />\n\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { cn } from \"../utils/cn\";\n\ndeclare global {\n interface Window {\n google?: typeof google;\n }\n}\n\n/* ── Script loader ── */\n\nlet loadPromise: Promise<void> | null = null;\n\nfunction loadMapScript(apiKey: string, forgeBaseUrl: string): Promise<void> {\n if (loadPromise) return loadPromise;\n if (typeof window !== \"undefined\" && window.google?.maps) {\n return Promise.resolve();\n }\n\n const proxyUrl = `${forgeBaseUrl}/v1/maps/proxy`;\n\n loadPromise = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n script.src = `${proxyUrl}/maps/api/js?key=${apiKey}&v=weekly&libraries=marker,places,geocoding,geometry`;\n script.async = true;\n script.crossOrigin = \"anonymous\";\n script.onload = () => {\n resolve();\n script.remove();\n };\n script.onerror = () => {\n loadPromise = null;\n reject(new Error(\"Failed to load Google Maps script\"));\n };\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n\n/* ── Component ── */\n\nexport interface MapViewProps {\n /** Manus Forge API key (VITE_FRONTEND_FORGE_API_KEY) */\n apiKey: string;\n /** Manus Forge base URL (VITE_FRONTEND_FORGE_API_URL) */\n forgeBaseUrl?: string;\n /** Initial map center */\n initialCenter?: google.maps.LatLngLiteral;\n /** Initial zoom level */\n initialZoom?: number;\n /** Callback when the map is ready — use to store ref and initialise services */\n onMapReady?: (map: google.maps.Map) => void;\n /** Map ID for cloud-based map styling */\n mapId?: string;\n /** CSS class for the map container */\n className?: string;\n}\n\nexport function MapView({\n apiKey,\n forgeBaseUrl = \"https://forge.butterfly-effect.dev\",\n initialCenter = { lat: 37.7749, lng: -122.4194 },\n initialZoom = 12,\n onMapReady,\n mapId = \"DEMO_MAP_ID\",\n className,\n}: MapViewProps) {\n const mapContainer = useRef<HTMLDivElement>(null);\n const mapInstance = useRef<google.maps.Map | null>(null);\n const onMapReadyRef = useRef(onMapReady);\n onMapReadyRef.current = onMapReady;\n\n const init = useCallback(async () => {\n try {\n await loadMapScript(apiKey, forgeBaseUrl);\n } catch (err) {\n console.error(\"[MapView] Failed to load Google Maps:\", err);\n return;\n }\n\n if (!mapContainer.current || !window.google) return;\n\n mapInstance.current = new window.google.maps.Map(mapContainer.current, {\n zoom: initialZoom,\n center: initialCenter,\n mapTypeControl: true,\n fullscreenControl: true,\n zoomControl: true,\n streetViewControl: true,\n mapId,\n });\n\n onMapReadyRef.current?.(mapInstance.current);\n }, [apiKey, forgeBaseUrl, initialZoom, initialCenter, mapId]);\n\n useEffect(() => {\n init();\n }, [init]);\n\n return (\n <div ref={mapContainer} className={cn(\"w-full h-[500px]\", className)} />\n );\n}\n","/**\n * ViewOnlyBadge — displays a \"View Only\" indicator for read-only users.\n *\n * Used across subdomains to indicate when a user has view-only access\n * (e.g. Viewer role in the RBAC hierarchy).\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Eye } from \"lucide-react\";\n\nexport interface ViewOnlyBadgeProps {\n /** Custom label (default: \"View Only\") */\n label?: string;\n /** Size variant */\n size?: \"sm\" | \"md\";\n /** CSS class */\n className?: string;\n}\n\nexport function ViewOnlyBadge({\n label = \"View Only\",\n size = \"sm\",\n className,\n}: ViewOnlyBadgeProps) {\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1 rounded-full font-medium\",\n \"bg-[#f59e0b]/10 text-[#f59e0b] border border-[#f59e0b]/20\",\n size === \"sm\" ? \"px-2 py-0.5 text-[10px]\" : \"px-3 py-1 text-xs\",\n className\n )}\n >\n <Eye className={cn(size === \"sm\" ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n {label}\n </span>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","X","useRef","useCallback","useEffect","jsx","jsxs","useEffect","useRef","useCallback","jsx","useRef","useCallback","useEffect","jsx","jsxs"]}
1
+ {"version":3,"sources":["../../src/components/ErrorBoundary.tsx","../../src/components/NotFound.tsx","../../src/components/ManusDialog.tsx","../../src/components/AIChatBox.tsx","../../src/components/MapView.tsx","../../src/components/ViewOnlyBadge.tsx","../../src/components/ExportDialog.tsx"],"sourcesContent":["/**\n * ErrorBoundary — standard error boundary for all Oplytics subdomains.\n *\n * Catches render errors and displays a fallback UI with Oplytics branding.\n * Optionally calls an onError callback for error reporting.\n */\nimport { Component, type ReactNode, type ErrorInfo } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { AlertTriangle, RefreshCw } from \"lucide-react\";\n\nexport interface ErrorBoundaryProps {\n /** Fallback UI to show when an error is caught. If not provided, uses the default Oplytics error UI. */\n fallback?: ReactNode;\n /** Error handler callback — use for logging/reporting */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Children to wrap */\n children: ReactNode;\n /** CSS class for the fallback container */\n className?: string;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n console.error(\"[ErrorBoundary] Caught error:\", error, errorInfo);\n this.props.onError?.(error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState({ hasError: false, error: null });\n };\n\n render() {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-[400px] p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n this.props.className\n )}\n >\n <div className=\"flex flex-col items-center gap-4 max-w-md text-center\">\n <div className=\"w-14 h-14 rounded-full bg-[#ef4444]/10 flex items-center justify-center\">\n <AlertTriangle className=\"h-7 w-7 text-[#ef4444]\" />\n </div>\n <h2 className=\"text-lg font-semibold font-[Montserrat,sans-serif]\">\n Something went wrong\n </h2>\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif]\">\n An unexpected error occurred. Please try again or contact support if the problem persists.\n </p>\n {this.state.error && (\n <pre className=\"mt-2 p-3 rounded-lg bg-[#0D1220] border border-[#1E2738] text-xs text-[#596475] max-w-full overflow-x-auto font-[JetBrains_Mono,monospace]\">\n {this.state.error.message}\n </pre>\n )}\n <button\n onClick={this.handleRetry}\n className={cn(\n \"mt-2 flex items-center gap-2 px-4 py-2 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <RefreshCw className=\"h-4 w-4\" />\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","/**\n * NotFound — standard 404 page for all Oplytics subdomains.\n *\n * Displays a branded \"Page Not Found\" message with navigation options.\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Home, ArrowLeft } from \"lucide-react\";\n\nexport interface NotFoundPageProps {\n /** Title text (default: \"Page Not Found\") */\n title?: string;\n /** Description text */\n description?: string;\n /** Home path to navigate to (default: \"/\") */\n homePath?: string;\n /** Back button handler (default: window.history.back) */\n onBack?: () => void;\n /** Home button handler — if provided, overrides the <a> link */\n onHome?: () => void;\n /** CSS class for the container */\n className?: string;\n}\n\nexport function NotFoundPage({\n title = \"Page Not Found\",\n description = \"The page you're looking for doesn't exist or has been moved.\",\n homePath = \"/\",\n onBack,\n onHome,\n className,\n}: NotFoundPageProps) {\n const handleBack = onBack || (() => {\n if (typeof window !== \"undefined\") {\n window.history.back();\n }\n });\n\n return (\n <div\n className={cn(\n \"flex flex-col items-center justify-center min-h-screen p-8\",\n \"bg-[#0A0E1A] text-[#E2E8F0]\",\n className\n )}\n >\n <div className=\"flex flex-col items-center gap-6 max-w-md text-center\">\n {/* 404 number */}\n <div className=\"relative\">\n <span\n className=\"text-[120px] font-bold leading-none font-[Montserrat,sans-serif]\"\n style={{\n background: \"linear-gradient(135deg, #8C34E9 0%, #14b8a6 100%)\",\n WebkitBackgroundClip: \"text\",\n WebkitTextFillColor: \"transparent\",\n opacity: 0.15,\n }}\n >\n 404\n </span>\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div\n className=\"w-16 h-16 rounded-full flex items-center justify-center\"\n style={{ background: \"rgba(140, 52, 233, 0.1)\", border: \"1px solid rgba(140, 52, 233, 0.2)\" }}\n >\n <span className=\"text-2xl font-bold text-[#8C34E9] font-[Montserrat,sans-serif]\">?</span>\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h1 className=\"text-2xl font-bold font-[Montserrat,sans-serif] text-[#E2E8F0]\">\n {title}\n </h1>\n\n {/* Description */}\n <p className=\"text-sm text-[#8890A0] font-[Space_Grotesk,sans-serif] leading-relaxed\">\n {description}\n </p>\n\n {/* Action buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 mt-2\">\n <button\n onClick={handleBack}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"border border-[#1E2738] text-[#8890A0] text-sm font-medium\",\n \"hover:bg-[#1E2738] hover:text-[#E2E8F0] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Go Back\n </button>\n\n {onHome ? (\n <button\n onClick={onHome}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </button>\n ) : (\n <a\n href={homePath}\n className={cn(\n \"flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white text-sm font-medium no-underline\",\n \"hover:bg-[#7928d4] transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0A0E1A]\"\n )}\n >\n <Home className=\"h-4 w-4\" />\n Go Home\n </a>\n )}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * ManusDialog — standard dialog/modal wrapper for Oplytics subdomains.\n *\n * Provides a consistent modal overlay with Oplytics branding.\n * Uses native HTML <dialog> element for accessibility.\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { X } from \"lucide-react\";\nimport { useEffect, useRef, useCallback, type ReactNode } from \"react\";\n\nexport interface ManusDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Close handler */\n onClose: () => void;\n /** Dialog title */\n title: string;\n /** Optional subtitle */\n subtitle?: string;\n /** Optional logo/icon to show in the header */\n logo?: string | ReactNode;\n /** Dialog content */\n children: ReactNode;\n /** Footer content (e.g. action buttons) */\n footer?: ReactNode;\n /** Max width class (default: \"max-w-md\") */\n maxWidth?: string;\n /** CSS class for the dialog panel */\n className?: string;\n /** Whether clicking the overlay closes the dialog (default: true) */\n closeOnOverlay?: boolean;\n}\n\nexport function ManusDialog({\n open,\n onClose,\n title,\n subtitle,\n logo,\n children,\n footer,\n maxWidth = \"max-w-md\",\n className,\n closeOnOverlay = true,\n}: ManusDialogProps) {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open && !dialog.open) {\n dialog.showModal();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open]);\n\n const handleBackdropClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (closeOnOverlay && e.target === dialogRef.current) {\n onClose();\n }\n },\n [closeOnOverlay, onClose]\n );\n\n const handleCancel = useCallback(\n (e: React.SyntheticEvent) => {\n e.preventDefault();\n onClose();\n },\n [onClose]\n );\n\n return (\n <dialog\n ref={dialogRef}\n onClick={handleBackdropClick}\n onCancel={handleCancel}\n className={cn(\n \"backdrop:bg-black/60 backdrop:backdrop-blur-sm\",\n \"bg-transparent p-0 m-auto\",\n \"open:animate-in open:fade-in-0 open:zoom-in-95\",\n maxWidth,\n \"w-full\"\n )}\n >\n <div\n className={cn(\n \"bg-[#0F1525] border border-[#1E2738] rounded-xl shadow-xl\",\n \"text-[#E2E8F0] font-[Space_Grotesk,sans-serif]\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between p-5 border-b border-[#1E2738]\">\n <div className=\"flex items-center gap-3\">\n {logo && (\n <div className=\"shrink-0\">\n {typeof logo === \"string\" ? (\n <img\n src={logo}\n alt=\"Dialog graphic\"\n className=\"w-10 h-10 rounded-md\"\n />\n ) : (\n logo\n )}\n </div>\n )}\n <div>\n <h2 className=\"text-base font-semibold font-[Montserrat,sans-serif]\">\n {title}\n </h2>\n {subtitle && (\n <p className=\"text-xs text-[#8890A0] mt-0.5\">{subtitle}</p>\n )}\n </div>\n </div>\n <button\n onClick={onClose}\n className={cn(\n \"shrink-0 p-1.5 rounded-md -mt-1 -mr-1\",\n \"text-[#596475] 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 >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"p-5\">{children}</div>\n\n {/* Footer */}\n {footer && (\n <div className=\"px-5 pb-5 pt-2 flex items-center justify-end gap-2\">\n {footer}\n </div>\n )}\n </div>\n </dialog>\n );\n}\n","/**\n * AIChatBox — full-featured chat interface for Oplytics subdomains.\n *\n * Provides a chat UI with message history, input area, and streaming support.\n * The actual LLM integration is handled by the consuming app via callbacks.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent\n */\nimport { cn } from \"../utils/cn\";\nimport { Send, Bot, User, Loader2, X, Minimize2, Maximize2 } from \"lucide-react\";\nimport {\n useState,\n useRef,\n useCallback,\n useEffect,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\n\n/* ── Message types ── */\n\nexport interface ChatMessage {\n id: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n timestamp: number;\n /** Whether the message is still streaming */\n isStreaming?: boolean;\n}\n\n/* ── Component props ── */\n\nexport interface AIChatBoxProps {\n /** Chat messages to display */\n messages: ChatMessage[];\n /** Handler when user sends a message */\n onSend: (message: string) => void;\n /** Whether the assistant is currently generating a response */\n isLoading?: boolean;\n /** Placeholder text for the input */\n placeholder?: string;\n /** Title for the chat header */\n title?: string;\n /** Whether to show the chat in a floating panel (default: false — inline) */\n floating?: boolean;\n /** Whether the floating panel is open */\n isOpen?: boolean;\n /** Toggle floating panel open/closed */\n onToggle?: () => void;\n /** Render prop for message content (e.g. to use Streamdown for markdown) */\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n /** Max height for the messages area (default: \"400px\") */\n maxHeight?: string;\n /** CSS class for the container */\n className?: string;\n /** Whether to show the header (default: true) */\n showHeader?: boolean;\n}\n\n/* ── Message bubble ── */\n\nfunction MessageBubble({\n message,\n renderContent,\n}: {\n message: ChatMessage;\n renderContent?: (content: string, isStreaming?: boolean) => ReactNode;\n}) {\n const isUser = message.role === \"user\";\n\n return (\n <div\n className={cn(\n \"flex gap-3 px-4 py-3\",\n isUser ? \"flex-row-reverse\" : \"flex-row\"\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"shrink-0 w-7 h-7 rounded-full flex items-center justify-center\",\n isUser ? \"bg-[#8C34E9]/20\" : \"bg-[#14b8a6]/20\"\n )}\n >\n {isUser ? (\n <User className=\"h-3.5 w-3.5 text-[#A855F7]\" />\n ) : (\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n )}\n </div>\n\n {/* Content */}\n <div\n className={cn(\n \"max-w-[80%] rounded-xl px-3.5 py-2.5 text-sm\",\n \"font-[Space_Grotesk,sans-serif]\",\n isUser\n ? \"bg-[#8C34E9]/15 text-[#E2E8F0] rounded-tr-sm\"\n : \"bg-[#1E2738] text-[#C4CDD9] rounded-tl-sm\"\n )}\n >\n {renderContent ? (\n renderContent(message.content, message.isStreaming)\n ) : (\n <p className=\"whitespace-pre-wrap break-words\">{message.content}</p>\n )}\n {message.isStreaming && (\n <span className=\"inline-block w-1.5 h-4 bg-[#8C34E9] animate-pulse ml-0.5 align-text-bottom rounded-sm\" />\n )}\n </div>\n </div>\n );\n}\n\n/* ── Main component ── */\n\nexport function AIChatBox({\n messages,\n onSend,\n isLoading = false,\n placeholder = \"Type a message...\",\n title = \"AI Assistant\",\n floating = false,\n isOpen = true,\n onToggle,\n renderContent,\n maxHeight = \"400px\",\n className,\n showHeader = true,\n}: AIChatBoxProps) {\n const [input, setInput] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n // Auto-scroll to bottom\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages]);\n\n const handleSend = useCallback(() => {\n const trimmed = input.trim();\n if (!trimmed || isLoading) return;\n onSend(trimmed);\n setInput(\"\");\n // Reset textarea height\n if (inputRef.current) {\n inputRef.current.style.height = \"auto\";\n }\n }, [input, isLoading, onSend]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend]\n );\n\n // Auto-resize textarea\n const handleInput = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setInput(e.target.value);\n const el = e.target;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 120)}px`;\n }, []);\n\n // Floating mode: toggle button\n if (floating && !isOpen) {\n return (\n <button\n onClick={onToggle}\n className={cn(\n \"fixed bottom-6 right-6 z-50\",\n \"w-12 h-12 rounded-full shadow-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"flex items-center justify-center\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#8C34E9] focus-visible:ring-offset-2\"\n )}\n title=\"Open chat\"\n >\n <Bot className=\"h-5 w-5\" />\n </button>\n );\n }\n\n const chatContent = (\n <div\n className={cn(\n \"flex flex-col bg-[#0F1525] border border-[#1E2738] rounded-xl overflow-hidden\",\n floating && \"fixed bottom-6 right-6 z-50 w-[380px] shadow-2xl\",\n className\n )}\n >\n {/* Header */}\n {showHeader && (\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-[#1E2738] bg-[#0D1220]\">\n <div className=\"flex items-center gap-2\">\n <Bot className=\"h-4 w-4 text-[#14b8a6]\" />\n <span className=\"text-sm font-semibold text-[#E2E8F0] font-[Montserrat,sans-serif]\">\n {title}\n </span>\n </div>\n {floating && onToggle && (\n <button\n onClick={onToggle}\n className=\"p-1 rounded-md text-[#596475] hover:text-[#E2E8F0] hover:bg-[#1E2738] transition-colors\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n )}\n\n {/* Messages */}\n <div\n className=\"flex-1 overflow-y-auto py-2\"\n style={{ maxHeight }}\n >\n {messages.length === 0 && (\n <div className=\"flex flex-col items-center justify-center h-full py-8 text-center\">\n <Bot className=\"h-8 w-8 text-[#596475] mb-3\" />\n <p className=\"text-sm text-[#596475] font-[Space_Grotesk,sans-serif]\">\n Start a conversation\n </p>\n </div>\n )}\n {messages.map((msg) => (\n <MessageBubble\n key={msg.id}\n message={msg}\n renderContent={renderContent}\n />\n ))}\n {isLoading && messages[messages.length - 1]?.role !== \"assistant\" && (\n <div className=\"flex gap-3 px-4 py-3\">\n <div className=\"shrink-0 w-7 h-7 rounded-full bg-[#14b8a6]/20 flex items-center justify-center\">\n <Bot className=\"h-3.5 w-3.5 text-[#14b8a6]\" />\n </div>\n <div className=\"flex items-center gap-1.5 px-3.5 py-2.5 rounded-xl bg-[#1E2738] rounded-tl-sm\">\n <Loader2 className=\"h-3.5 w-3.5 text-[#596475] animate-spin\" />\n <span className=\"text-xs text-[#596475]\">Thinking...</span>\n </div>\n </div>\n )}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Input area */}\n <div className=\"border-t border-[#1E2738] p-3\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={handleInput}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n rows={1}\n className={cn(\n \"flex-1 resize-none rounded-lg px-3 py-2\",\n \"bg-[#0A0E1A] border border-[#1E2738] text-sm text-[#E2E8F0]\",\n \"placeholder:text-[#596475]\",\n \"focus:outline-none focus:border-[#8C34E9]\",\n \"font-[Space_Grotesk,sans-serif]\",\n \"transition-colors\"\n )}\n disabled={isLoading}\n />\n <button\n onClick={handleSend}\n disabled={!input.trim() || isLoading}\n className={cn(\n \"shrink-0 p-2.5 rounded-lg\",\n \"bg-[#8C34E9] text-white\",\n \"hover:bg-[#7928d4] transition-colors cursor-pointer\",\n \"disabled:opacity-40 disabled:cursor-not-allowed\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#8C34E9]\"\n )}\n >\n {isLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </div>\n </div>\n );\n\n return chatContent;\n}\n","/**\n * MapView — Google Maps integration with Manus proxy authentication.\n *\n * Provides a MapView component with onMapReady callback for initialising\n * Google Maps services (Places, Geocoder, Directions, Drawing, etc.).\n * All map functionality works directly in the browser via the Manus proxy.\n *\n * Usage:\n * <MapView\n * apiKey={import.meta.env.VITE_FRONTEND_FORGE_API_KEY}\n * forgeBaseUrl={import.meta.env.VITE_FRONTEND_FORGE_API_URL}\n * initialCenter={{ lat: 40.7128, lng: -74.0060 }}\n * initialZoom={15}\n * onMapReady={(map) => { mapRef.current = map; }}\n * />\n */\n\n/// <reference types=\"@types/google.maps\" />\n\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { cn } from \"../utils/cn\";\n\ndeclare global {\n interface Window {\n google?: typeof google;\n }\n}\n\n/* ── Script loader ── */\n\nlet loadPromise: Promise<void> | null = null;\n\nfunction loadMapScript(apiKey: string, forgeBaseUrl: string): Promise<void> {\n if (loadPromise) return loadPromise;\n if (typeof window !== \"undefined\" && window.google?.maps) {\n return Promise.resolve();\n }\n\n const proxyUrl = `${forgeBaseUrl}/v1/maps/proxy`;\n\n loadPromise = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n script.src = `${proxyUrl}/maps/api/js?key=${apiKey}&v=weekly&libraries=marker,places,geocoding,geometry`;\n script.async = true;\n script.crossOrigin = \"anonymous\";\n script.onload = () => {\n resolve();\n script.remove();\n };\n script.onerror = () => {\n loadPromise = null;\n reject(new Error(\"Failed to load Google Maps script\"));\n };\n document.head.appendChild(script);\n });\n\n return loadPromise;\n}\n\n/* ── Component ── */\n\nexport interface MapViewProps {\n /** Manus Forge API key (VITE_FRONTEND_FORGE_API_KEY) */\n apiKey: string;\n /** Manus Forge base URL (VITE_FRONTEND_FORGE_API_URL) */\n forgeBaseUrl?: string;\n /** Initial map center */\n initialCenter?: google.maps.LatLngLiteral;\n /** Initial zoom level */\n initialZoom?: number;\n /** Callback when the map is ready — use to store ref and initialise services */\n onMapReady?: (map: google.maps.Map) => void;\n /** Map ID for cloud-based map styling */\n mapId?: string;\n /** CSS class for the map container */\n className?: string;\n}\n\nexport function MapView({\n apiKey,\n forgeBaseUrl = \"https://forge.butterfly-effect.dev\",\n initialCenter = { lat: 37.7749, lng: -122.4194 },\n initialZoom = 12,\n onMapReady,\n mapId = \"DEMO_MAP_ID\",\n className,\n}: MapViewProps) {\n const mapContainer = useRef<HTMLDivElement>(null);\n const mapInstance = useRef<google.maps.Map | null>(null);\n const onMapReadyRef = useRef(onMapReady);\n onMapReadyRef.current = onMapReady;\n\n const init = useCallback(async () => {\n try {\n await loadMapScript(apiKey, forgeBaseUrl);\n } catch (err) {\n console.error(\"[MapView] Failed to load Google Maps:\", err);\n return;\n }\n\n if (!mapContainer.current || !window.google) return;\n\n mapInstance.current = new window.google.maps.Map(mapContainer.current, {\n zoom: initialZoom,\n center: initialCenter,\n mapTypeControl: true,\n fullscreenControl: true,\n zoomControl: true,\n streetViewControl: true,\n mapId,\n });\n\n onMapReadyRef.current?.(mapInstance.current);\n }, [apiKey, forgeBaseUrl, initialZoom, initialCenter, mapId]);\n\n useEffect(() => {\n init();\n }, [init]);\n\n return (\n <div ref={mapContainer} className={cn(\"w-full h-[500px]\", className)} />\n );\n}\n","/**\n * ViewOnlyBadge — displays a \"View Only\" indicator for read-only users.\n *\n * Used across subdomains to indicate when a user has view-only access\n * (e.g. Viewer role in the RBAC hierarchy).\n *\n * Oplytics dark theme.\n */\nimport { cn } from \"../utils/cn\";\nimport { Eye } from \"lucide-react\";\n\nexport interface ViewOnlyBadgeProps {\n /** Custom label (default: \"View Only\") */\n label?: string;\n /** Size variant */\n size?: \"sm\" | \"md\";\n /** CSS class */\n className?: string;\n}\n\nexport function ViewOnlyBadge({\n label = \"View Only\",\n size = \"sm\",\n className,\n}: ViewOnlyBadgeProps) {\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1 rounded-full font-medium\",\n \"bg-[#f59e0b]/10 text-[#f59e0b] border border-[#f59e0b]/20\",\n size === \"sm\" ? \"px-2 py-0.5 text-[10px]\" : \"px-3 py-1 text-xs\",\n className\n )}\n >\n <Eye className={cn(size === \"sm\" ? \"h-3 w-3\" : \"h-3.5 w-3.5\")} />\n {label}\n </span>\n );\n}\n","/**\n * ExportDialog — reusable \"export data\" flow for any Oplytics subdomain.\n *\n * Lets the user choose a format (CSV or spreadsheet/XLSX), then save the\n * result. On browsers that support the File System Access API (Chrome/Edge)\n * this opens a native save-location picker via `showSaveFilePicker`; on\n * browsers that don't (Safari/Firefox) it falls back to a normal anchor-tag\n * download, which is the best available experience there.\n *\n * The dialog knows nothing about the data being exported — the consumer\n * supplies `onExport(format)` and does its own CSV/XLSX serialization. This\n * keeps the dialog reusable across Action Manager, Policy Deployment, and\n * Portal reporting without a shared data shape.\n */\nimport { useState } from \"react\";\nimport { Download, FileSpreadsheet, FileText, Loader2 } from \"lucide-react\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"../primitives/dialog\";\nimport { Button } from \"../primitives/button\";\nimport { RadioGroup, RadioGroupItem } from \"../primitives/radio-group\";\nimport { Label } from \"../primitives/label\";\nimport { cn } from \"../utils/cn\";\n\nexport type ExportFormat = \"csv\" | \"xlsx\";\n\nexport interface ExportResult {\n /** File contents to save. */\n data: Blob | string;\n /** Suggested filename, including extension. */\n filename: string;\n /** MIME type for the Blob/save picker. */\n mimeType: string;\n}\n\nexport interface ExportDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Title shown in the dialog header, e.g. \"Export Actions\". */\n title?: string;\n /** Description shown under the title. */\n description?: string;\n /**\n * Produce the file contents for the chosen format. Called only when the\n * user confirms — do the actual CSV/XLSX serialization here. May be async\n * (e.g. fetching a fuller dataset than what's on screen).\n */\n onExport: (format: ExportFormat) => Promise<ExportResult> | ExportResult;\n /** Restrict which formats are offered (default: both). */\n formats?: ExportFormat[];\n}\n\nconst FORMAT_META: Record<ExportFormat, { label: string; hint: string; icon: typeof FileText }> = {\n csv: {\n label: \"CSV\",\n hint: \"Plain text, opens in Excel, Sheets, or Numbers\",\n icon: FileText,\n },\n xlsx: {\n label: \"Spreadsheet (.xlsx)\",\n hint: \"Native Excel format with column formatting preserved\",\n icon: FileSpreadsheet,\n },\n};\n\n/** True when the browser supports a native save-location picker. */\nfunction supportsSavePicker(): boolean {\n return typeof window !== \"undefined\" && \"showSaveFilePicker\" in window;\n}\n\nasync function saveWithPicker(result: ExportResult): Promise<\"saved\" | \"cancelled\"> {\n // Narrow, feature-detected access — showSaveFilePicker isn't in the\n // standard lib.dom types yet across all supported TS targets.\n const picker = (window as unknown as {\n showSaveFilePicker: (options: {\n suggestedName: string;\n types: { description: string; accept: Record<string, string[]> }[];\n }) => Promise<FileSystemFileHandle>;\n }).showSaveFilePicker;\n\n const ext = result.filename.split(\".\").pop() ?? \"txt\";\n let handle: FileSystemFileHandle;\n try {\n handle = await picker({\n suggestedName: result.filename,\n types: [\n {\n description: FORMAT_META[ext === \"xlsx\" ? \"xlsx\" : \"csv\"].label,\n accept: { [result.mimeType]: [`.${ext}`] },\n },\n ],\n });\n } catch (err) {\n // AbortError is thrown when the user cancels the picker — not a failure.\n if (err instanceof DOMException && err.name === \"AbortError\") return \"cancelled\";\n throw err;\n }\n\n const writable = await handle.createWritable();\n await writable.write(result.data);\n await writable.close();\n return \"saved\";\n}\n\nfunction downloadFallback(result: ExportResult) {\n const blob = typeof result.data === \"string\" ? new Blob([result.data], { type: result.mimeType }) : result.data;\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = result.filename;\n a.click();\n URL.revokeObjectURL(url);\n}\n\nexport function ExportDialog({\n open,\n onOpenChange,\n title = \"Export data\",\n description = \"Choose a format to export.\",\n onExport,\n formats = [\"csv\", \"xlsx\"],\n}: ExportDialogProps) {\n const [format, setFormat] = useState<ExportFormat>(formats[0] ?? \"csv\");\n const [busy, setBusy] = useState(false);\n\n const handleExport = async () => {\n setBusy(true);\n try {\n const result = await onExport(format);\n\n if (supportsSavePicker()) {\n const outcome = await saveWithPicker(result);\n if (outcome === \"cancelled\") return;\n } else {\n downloadFallback(result);\n }\n\n onOpenChange(false);\n } finally {\n setBusy(false);\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <RadioGroup value={format} onValueChange={(v) => setFormat(v as ExportFormat)} className=\"gap-3\">\n {formats.map((f) => {\n const meta = FORMAT_META[f];\n const Icon = meta.icon;\n return (\n <Label\n key={f}\n htmlFor={`export-format-${f}`}\n className={cn(\n \"flex items-center gap-3 rounded-lg border p-3 cursor-pointer transition-colors\",\n format === f ? \"border-primary bg-primary/5\" : \"border-border hover:bg-accent/50\",\n )}\n >\n <RadioGroupItem value={f} id={`export-format-${f}`} />\n <Icon className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <span className=\"flex flex-col min-w-0\">\n <span className=\"text-sm font-medium\">{meta.label}</span>\n <span className=\"text-xs text-muted-foreground\">{meta.hint}</span>\n </span>\n </Label>\n );\n })}\n </RadioGroup>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={busy}>\n Cancel\n </Button>\n <Button onClick={handleExport} disabled={busy}>\n {busy ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Download className=\"h-4 w-4\" />\n )}\n {supportsSavePicker() ? \"Choose location & export\" : \"Download\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","X","useRef","useCallback","useEffect","jsx","jsxs","useEffect","useRef","useCallback","jsx","useRef","useCallback","useEffect","jsx","jsxs","useState","Loader2","jsx","jsxs","useState","Loader2"]}
@@ -2,7 +2,7 @@ import {
2
2
  HierarchyNavigator,
3
3
  createOrgHierarchyContext,
4
4
  readActiveEnterpriseCookie
5
- } from "../chunk-ETIKYZIX.js";
5
+ } from "../chunk-J7LWAIQK.js";
6
6
  import {
7
7
  ORG_HIERARCHY_PARAM,
8
8
  appendSelectionParam,
@@ -23,8 +23,9 @@ import {
23
23
  ORG_HIERARCHY_LABELS,
24
24
  ORG_HIERARCHY_LEVELS
25
25
  } from "../chunk-PS4K7FKB.js";
26
- import "../chunk-LTUYIBMA.js";
26
+ import "../chunk-HR77H6PE.js";
27
27
  import "../chunk-JT3XLKKD.js";
28
+ import "../chunk-R4NOLCSI.js";
28
29
  export {
29
30
  ACTIVE_ENTERPRISE_COOKIE,
30
31
  HierarchyNavigator,
@@ -1,5 +1,5 @@
1
- import { S as SharedSidebarConfig } from '../HealthFooter-BN5LHTp6.js';
2
- export { C as CrossServiceNav, d as CrossServiceNavProps, D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection, i as SharedSidebarProps, j as getInitials, k as isAdminRole, l as isMenuItemActive, u as useSidebarResize } from '../HealthFooter-BN5LHTp6.js';
1
+ import { S as SharedSidebarConfig } from '../HealthFooter-9rZjMmqc.js';
2
+ export { C as CrossServiceNav, d as CrossServiceNavProps, D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection, i as SharedSidebarProps, j as getInitials, k as isAdminRole, l as isMenuItemActive, u as useSidebarResize } from '../HealthFooter-9rZjMmqc.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode } from 'react';
5
5
  import '../crossNav-Bd8-0qoi.js';
@@ -19,14 +19,15 @@ import {
19
19
  policyDeploymentConfig,
20
20
  sqdcpConfig,
21
21
  useSidebarResize
22
- } from "../chunk-NQZDFCIU.js";
22
+ } from "../chunk-L2N36IW6.js";
23
23
  import "../chunk-4QJMLQSH.js";
24
- import "../chunk-ETIKYZIX.js";
24
+ import "../chunk-J7LWAIQK.js";
25
25
  import "../chunk-3E3GLIQV.js";
26
26
  import "../chunk-3G43UG63.js";
27
27
  import "../chunk-PS4K7FKB.js";
28
- import "../chunk-LTUYIBMA.js";
28
+ import "../chunk-HR77H6PE.js";
29
29
  import "../chunk-JT3XLKKD.js";
30
+ import "../chunk-R4NOLCSI.js";
30
31
  export {
31
32
  CrossServiceNav,
32
33
  DashboardLayout,
@@ -29,7 +29,6 @@ import {
29
29
  BreadcrumbList,
30
30
  BreadcrumbPage,
31
31
  BreadcrumbSeparator,
32
- Button,
33
32
  ButtonGroup,
34
33
  ButtonGroupSeparator,
35
34
  ButtonGroupText,
@@ -81,16 +80,6 @@ import {
81
80
  ContextMenuSubContent,
82
81
  ContextMenuSubTrigger,
83
82
  ContextMenuTrigger,
84
- Dialog,
85
- DialogClose,
86
- DialogContent,
87
- DialogDescription,
88
- DialogFooter,
89
- DialogHeader,
90
- DialogOverlay,
91
- DialogPortal,
92
- DialogTitle,
93
- DialogTrigger,
94
83
  Drawer,
95
84
  DrawerClose,
96
85
  DrawerContent,
@@ -165,7 +154,6 @@ import {
165
154
  ItemTitle,
166
155
  Kbd,
167
156
  KbdGroup,
168
- Label,
169
157
  Menubar,
170
158
  MenubarCheckboxItem,
171
159
  MenubarContent,
@@ -202,8 +190,6 @@ import {
202
190
  PopoverContent,
203
191
  PopoverTrigger,
204
192
  Progress,
205
- RadioGroup,
206
- RadioGroupItem,
207
193
  ResizableHandle,
208
194
  ResizablePanel,
209
195
  ResizablePanelGroup,
@@ -278,15 +264,31 @@ import {
278
264
  TooltipTrigger,
279
265
  badgeVariants,
280
266
  buttonGroupVariants,
281
- buttonVariants,
282
- cn,
283
267
  navigationMenuTriggerStyle,
284
268
  toggleVariants,
285
- useDialogComposition,
286
269
  useFormField,
287
270
  useIsMobile,
288
271
  useSidebar
289
- } from "../chunk-LTUYIBMA.js";
272
+ } from "../chunk-HR77H6PE.js";
273
+ import {
274
+ Button,
275
+ Dialog,
276
+ DialogClose,
277
+ DialogContent,
278
+ DialogDescription,
279
+ DialogFooter,
280
+ DialogHeader,
281
+ DialogOverlay,
282
+ DialogPortal,
283
+ DialogTitle,
284
+ DialogTrigger,
285
+ Label,
286
+ RadioGroup,
287
+ RadioGroupItem,
288
+ buttonVariants,
289
+ cn,
290
+ useDialogComposition
291
+ } from "../chunk-R4NOLCSI.js";
290
292
  export {
291
293
  Accordion,
292
294
  AccordionContent,
@@ -1,29 +1,31 @@
1
1
  import {
2
2
  Badge,
3
- Button,
4
- Dialog,
5
- DialogContent,
6
- DialogDescription,
7
- DialogFooter,
8
- DialogHeader,
9
- DialogTitle,
10
3
  DropdownMenu,
11
4
  DropdownMenuContent,
12
5
  DropdownMenuItem,
13
6
  DropdownMenuSeparator,
14
7
  DropdownMenuTrigger,
15
8
  Input,
16
- Label,
17
9
  Select,
18
10
  SelectContent,
19
11
  SelectItem,
20
12
  SelectTrigger,
21
13
  SelectValue,
22
14
  Textarea
23
- } from "../chunk-LTUYIBMA.js";
15
+ } from "../chunk-HR77H6PE.js";
24
16
  import {
25
17
  cn
26
18
  } from "../chunk-JT3XLKKD.js";
19
+ import {
20
+ Button,
21
+ Dialog,
22
+ DialogContent,
23
+ DialogDescription,
24
+ DialogFooter,
25
+ DialogHeader,
26
+ DialogTitle,
27
+ Label
28
+ } from "../chunk-R4NOLCSI.js";
27
29
 
28
30
  // src/reporting/ReportingToolbar.tsx
29
31
  import { useState } from "react";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/reporting/ReportingToolbar.tsx","../../src/reporting/index.ts"],"sourcesContent":["/**\n * ReportingToolbar — standard Safety / Environmental / Stop Work incident\n * reporting and Problem / Suggestion feedback for all Oplytics subdomains.\n *\n * Visual model — two grouped dropdown buttons in the top-right of the header:\n * ⚠ Report ▾ → Safety Incident | Environmental | Stop Work\n * 💡 Feedback ▾ → Report Problem | Suggestion\n *\n * Selecting an option opens a self-contained dialog with the right form for\n * that report/feedback type (severity / location / description / immediate\n * action for incidents; subject / description for feedback). On submit the\n * configured `onReportSubmit` / `onFeedbackSubmit` handler is called with\n * the canonical `IncidentReport` / `FeedbackReport` shape.\n *\n * Ported from the Policy Deployment subdomain — the canonical visual\n * reference. Replaces the previous headless / individual-icon-button design.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent.\n */\nimport { useState } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport {\n Button,\n Badge,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Input,\n Label,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../primitives\";\nimport {\n ShieldAlert,\n Leaf,\n OctagonX,\n Bug,\n Lightbulb,\n ChevronDown,\n Send,\n AlertTriangle,\n} from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport type {\n ReportCategory,\n FeedbackCategory,\n IncidentReport,\n FeedbackReport,\n ReportingToolbarConfig,\n} from \"./index\";\n\n/* ── Report / feedback option catalogues ── */\n\ninterface ReportOption {\n category: ReportCategory;\n label: string;\n description: string;\n icon: React.ElementType;\n color: string;\n}\n\ninterface FeedbackOption {\n category: FeedbackCategory;\n label: string;\n description: string;\n icon: React.ElementType;\n color: string;\n}\n\nconst ALL_REPORT_OPTIONS: ReportOption[] = [\n {\n category: \"safety\",\n label: \"Safety Incident\",\n description: \"Report a safety hazard, near miss, or injury\",\n icon: ShieldAlert,\n color: \"#EF4444\",\n },\n {\n category: \"environmental\",\n label: \"Environmental\",\n description: \"Report an environmental concern or spill\",\n icon: Leaf,\n color: \"#22C55E\",\n },\n {\n category: \"stop-work\",\n label: \"Stop Work\",\n description: \"Raise a stop work authority — immediate halt\",\n icon: OctagonX,\n color: \"#F59E0B\",\n },\n];\n\nconst ALL_FEEDBACK_OPTIONS: FeedbackOption[] = [\n {\n category: \"problem\",\n label: \"Report Problem\",\n description: \"Something isn't working\",\n icon: Bug,\n color: \"#EF4444\",\n },\n {\n category: \"suggestion\",\n label: \"Suggestion\",\n description: \"Idea for improvement\",\n icon: Lightbulb,\n color: \"#8C34E9\",\n },\n];\n\nconst SEVERITY_OPTIONS: {\n value: NonNullable<IncidentReport[\"severity\"]>;\n label: string;\n color: string;\n}[] = [\n { value: \"critical\", label: \"Critical\", color: \"#EF4444\" },\n { value: \"high\", label: \"High\", color: \"#F59E0B\" },\n { value: \"medium\", label: \"Medium\", color: \"#3b82f6\" },\n { value: \"low\", label: \"Low\", color: \"#22C55E\" },\n];\n\n/* ── Props ── */\n\nexport interface ReportingToolbarProps extends Omit<ReportingToolbarConfig, \"moduleName\"> {\n /** Service/module name (e.g. \"Connect\", \"Policy Deployment\"). Defaults to \"Oplytics\". */\n moduleName?: string;\n /** Optional pre-fill for the report dialog's Location/Area field. */\n defaultLocation?: string;\n /**\n * Compact mode — currently a no-op (the new design always responsive-collapses\n * the labels at small breakpoints). Kept on the prop type so callers that\n * still pass it continue to type-check.\n * @deprecated The toolbar handles responsive collapse on its own.\n */\n compact?: boolean;\n /** CSS class for the container. */\n className?: string;\n}\n\n/* ── Component ── */\n\nexport function ReportingToolbar({\n moduleName = \"Oplytics\",\n showIncidentReporting = true,\n showFeedback = true,\n reportCategories = [\"safety\", \"environmental\", \"stop-work\"],\n feedbackCategories = [\"problem\", \"suggestion\"],\n onReportSubmit,\n onFeedbackSubmit,\n defaultLocation = \"\",\n className,\n}: ReportingToolbarProps) {\n const reportOptions = ALL_REPORT_OPTIONS.filter((opt) =>\n reportCategories.includes(opt.category),\n );\n const feedbackOptions = ALL_FEEDBACK_OPTIONS.filter((opt) =>\n feedbackCategories.includes(opt.category),\n );\n\n /* ── Report dialog state ── */\n const [reportDialogOpen, setReportDialogOpen] = useState(false);\n const [selectedReportCategory, setSelectedReportCategory] =\n useState<ReportCategory>(\"safety\");\n const [reportSeverity, setReportSeverity] =\n useState<NonNullable<IncidentReport[\"severity\"]>>(\"medium\");\n const [reportLocation, setReportLocation] = useState(defaultLocation);\n const [reportDescription, setReportDescription] = useState(\"\");\n const [reportImmediateAction, setReportImmediateAction] = useState(\"\");\n\n /* ── Feedback dialog state ── */\n const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);\n const [selectedFeedbackCategory, setSelectedFeedbackCategory] =\n useState<FeedbackCategory>(\"problem\");\n const [feedbackSubject, setFeedbackSubject] = useState(\"\");\n const [feedbackDescription, setFeedbackDescription] = useState(\"\");\n\n const [submitting, setSubmitting] = useState(false);\n\n const openReportDialog = (category: ReportCategory) => {\n setSelectedReportCategory(category);\n setReportSeverity(category === \"stop-work\" ? \"critical\" : \"medium\");\n setReportLocation(defaultLocation);\n setReportDescription(\"\");\n setReportImmediateAction(\"\");\n setReportDialogOpen(true);\n };\n\n const openFeedbackDialog = (category: FeedbackCategory) => {\n setSelectedFeedbackCategory(category);\n setFeedbackSubject(\"\");\n setFeedbackDescription(\"\");\n setFeedbackDialogOpen(true);\n };\n\n const handleReportSubmit = async () => {\n if (!reportDescription.trim()) {\n toast.error(\"Please provide a description\");\n return;\n }\n setSubmitting(true);\n try {\n const report: IncidentReport = {\n category: selectedReportCategory,\n description: reportDescription,\n location: reportLocation || undefined,\n severity: reportSeverity,\n immediateAction: reportImmediateAction || undefined,\n moduleName,\n timestamp: new Date().toISOString(),\n };\n await onReportSubmit(report);\n toast.success(\"Report submitted successfully\", {\n description:\n selectedReportCategory === \"stop-work\"\n ? \"Stop Work authority raised — management has been notified\"\n : \"Your report has been logged and assigned for review\",\n });\n setReportDialogOpen(false);\n } catch (err) {\n console.error(\"[ReportingToolbar] report submit failed:\", err);\n toast.error(\"Failed to submit report. Please try again.\");\n } finally {\n setSubmitting(false);\n }\n };\n\n const handleFeedbackSubmit = async () => {\n if (!feedbackDescription.trim()) {\n toast.error(\"Please provide a description\");\n return;\n }\n setSubmitting(true);\n try {\n const feedback: FeedbackReport = {\n category: selectedFeedbackCategory,\n subject: feedbackSubject || undefined,\n description: feedbackDescription,\n moduleName,\n timestamp: new Date().toISOString(),\n pageContext:\n typeof window !== \"undefined\" ? window.location.pathname : undefined,\n };\n await onFeedbackSubmit(feedback);\n toast.success(\n selectedFeedbackCategory === \"problem\"\n ? \"Problem reported — Oplytics team will review\"\n : \"Suggestion submitted — thank you for your feedback\",\n );\n setFeedbackDialogOpen(false);\n } catch (err) {\n console.error(\"[ReportingToolbar] feedback submit failed:\", err);\n toast.error(\"Failed to submit feedback. Please try again.\");\n } finally {\n setSubmitting(false);\n }\n };\n\n const reportConfig = ALL_REPORT_OPTIONS.find(\n (r) => r.category === selectedReportCategory,\n );\n\n return (\n <>\n {/* Toolbar buttons */}\n <div className={cn(\"flex items-center gap-2\", className)}>\n {/* Report dropdown */}\n {showIncidentReporting && reportOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 gap-1.5 text-xs font-medium text-[#EF4444] hover:bg-[#EF4444]/10 hover:text-[#EF4444] border border-[#EF4444]/20\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">Report</span>\n <ChevronDown className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n {reportOptions.map((opt) => (\n <DropdownMenuItem\n key={opt.category}\n onClick={() => openReportDialog(opt.category)}\n className=\"gap-3 py-2.5\"\n >\n <opt.icon className=\"h-4 w-4\" style={{ color: opt.color }} />\n <div>\n <p className=\"text-sm font-medium\">{opt.label}</p>\n <p className=\"text-[10px] text-muted-foreground\">{opt.description}</p>\n </div>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n {/* Feedback dropdown */}\n {showFeedback && feedbackOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 gap-1.5 text-xs font-medium text-[#8C34E9] hover:bg-[#8C34E9]/10 hover:text-[#8C34E9] border border-[#8C34E9]/20\"\n >\n <Lightbulb className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">Feedback</span>\n <ChevronDown className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n {feedbackOptions.map((opt, idx) => (\n <span key={opt.category}>\n {idx > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem\n onClick={() => openFeedbackDialog(opt.category)}\n className=\"gap-3 py-2.5\"\n >\n <opt.icon className=\"h-4 w-4\" style={{ color: opt.color }} />\n <div>\n <p className=\"text-sm font-medium\">{opt.label}</p>\n <p className=\"text-[10px] text-muted-foreground\">{opt.description}</p>\n </div>\n </DropdownMenuItem>\n </span>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {/* Report Dialog */}\n <Dialog open={reportDialogOpen} onOpenChange={setReportDialogOpen}>\n <DialogContent className=\"sm:max-w-[500px]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n {reportConfig && (\n <reportConfig.icon className=\"h-5 w-5\" style={{ color: reportConfig.color }} />\n )}\n {reportConfig?.label || \"Report\"}\n {selectedReportCategory === \"stop-work\" && (\n <Badge className=\"bg-[#F59E0B] text-white text-[9px] border-0\">URGENT</Badge>\n )}\n </DialogTitle>\n <DialogDescription>\n {reportConfig?.description}. All reports are logged and tracked.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Severity</Label>\n <Select\n value={reportSeverity}\n onValueChange={(v) =>\n setReportSeverity(v as NonNullable<IncidentReport[\"severity\"]>)\n }\n >\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SEVERITY_OPTIONS.map((s) => (\n <SelectItem key={s.value} value={s.value}>\n <div className=\"flex items-center gap-2\">\n <div\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: s.color }}\n />\n {s.label}\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Location / Area</Label>\n <Input\n value={reportLocation}\n onChange={(e) => setReportLocation(e.target.value)}\n placeholder=\"e.g., Production Line 3, Warehouse B\"\n className=\"h-9\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Description *</Label>\n <Textarea\n value={reportDescription}\n onChange={(e) => setReportDescription(e.target.value)}\n placeholder=\"Describe what happened or what you observed...\"\n rows={4}\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Immediate Action Taken</Label>\n <Textarea\n value={reportImmediateAction}\n onChange={(e) => setReportImmediateAction(e.target.value)}\n placeholder=\"Describe any immediate action taken to make the area safe...\"\n rows={2}\n />\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setReportDialogOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={handleReportSubmit}\n disabled={submitting || !reportDescription.trim()}\n className=\"gap-2\"\n style={{\n backgroundColor: reportConfig?.color,\n color: \"white\",\n }}\n >\n <Send className=\"h-3.5 w-3.5\" />\n {submitting ? \"Submitting...\" : \"Submit Report\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {/* Feedback Dialog */}\n <Dialog open={feedbackDialogOpen} onOpenChange={setFeedbackDialogOpen}>\n <DialogContent className=\"sm:max-w-[500px]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n {selectedFeedbackCategory === \"problem\" ? (\n <Bug className=\"h-5 w-5 text-[#EF4444]\" />\n ) : (\n <Lightbulb className=\"h-5 w-5 text-[#8C34E9]\" />\n )}\n {selectedFeedbackCategory === \"problem\"\n ? \"Report a Problem\"\n : \"Submit a Suggestion\"}\n </DialogTitle>\n <DialogDescription>\n {selectedFeedbackCategory === \"problem\"\n ? \"Help us fix issues — describe what went wrong.\"\n : \"We'd love to hear your ideas for improving Oplytics.\"}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Subject</Label>\n <Input\n value={feedbackSubject}\n onChange={(e) => setFeedbackSubject(e.target.value)}\n placeholder={\n selectedFeedbackCategory === \"problem\"\n ? \"Brief summary of the issue\"\n : \"Brief summary of your idea\"\n }\n className=\"h-9\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Description *</Label>\n <Textarea\n value={feedbackDescription}\n onChange={(e) => setFeedbackDescription(e.target.value)}\n placeholder={\n selectedFeedbackCategory === \"problem\"\n ? \"What were you trying to do? What happened instead?\"\n : \"Describe your suggestion and how it would help...\"\n }\n rows={5}\n />\n </div>\n\n <div className=\"text-[10px] text-muted-foreground\">\n Module: {moduleName} — Feedback is reviewed by the Oplytics team\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setFeedbackDialogOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={handleFeedbackSubmit}\n disabled={submitting || !feedbackDescription.trim()}\n className=\"gap-2\"\n style={{\n backgroundColor:\n selectedFeedbackCategory === \"problem\" ? \"#EF4444\" : \"#8C34E9\",\n color: \"white\",\n }}\n >\n <Send className=\"h-3.5 w-3.5\" />\n {submitting ? \"Submitting...\" : \"Submit\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n","/**\n * @oplytics/shared-ui/reporting\n *\n * Reporting and feedback toolbar types and configuration for all Oplytics subdomains.\n * Provides the standard Safety/Environmental/Stop Work incident reporting buttons\n * and Problem/Suggestion feedback buttons.\n *\n * The actual ReportingToolbar React component depends on shadcn/ui primitives\n * (Dialog, Button, Textarea, Select) which vary across subdomains until\n * consolidation. This module provides the types, configuration, and submission\n * logic that are shared.\n */\n\n// ─── Report Types ────────────────────────────────────────────────────────────\n\nexport type ReportCategory = \"safety\" | \"environmental\" | \"stop-work\";\nexport type FeedbackCategory = \"problem\" | \"suggestion\";\n\nexport interface IncidentReport {\n category: ReportCategory;\n description: string;\n location?: string;\n severity?: \"low\" | \"medium\" | \"high\" | \"critical\";\n /** Optional — what was done at the scene to make the area safe. */\n immediateAction?: string;\n moduleName: string;\n /** ISO timestamp */\n timestamp: string;\n /** User who submitted */\n submittedBy?: string;\n /** Hierarchy context at time of submission */\n hierarchyContext?: {\n enterpriseId?: number;\n businessUnitId?: number;\n siteId?: number;\n areaId?: number;\n assetId?: number;\n };\n}\n\nexport interface FeedbackReport {\n category: FeedbackCategory;\n /** Optional — short summary entered in the dialog before the description. */\n subject?: string;\n description: string;\n moduleName: string;\n /** ISO timestamp */\n timestamp: string;\n /** User who submitted */\n submittedBy?: string;\n /** Page/route where feedback was submitted */\n pageContext?: string;\n}\n\n// ─── Toolbar Configuration ───────────────────────────────────────────────────\n\nexport interface ReportingToolbarConfig {\n /** Service/module name (e.g., \"SQDCP\", \"OEE Manager\") */\n moduleName: string;\n /** Whether to show incident reporting buttons (default: true) */\n showIncidentReporting?: boolean;\n /** Whether to show feedback buttons (default: true) */\n showFeedback?: boolean;\n /** Custom report categories to show (default: all three) */\n reportCategories?: ReportCategory[];\n /** Custom feedback categories to show (default: both) */\n feedbackCategories?: FeedbackCategory[];\n /** Handler called when an incident report is submitted */\n onReportSubmit: (report: IncidentReport) => Promise<void> | void;\n /** Handler called when feedback is submitted */\n onFeedbackSubmit: (feedback: FeedbackReport) => Promise<void> | void;\n}\n\n// ─── Report Button Definitions ───────────────────────────────────────────────\n\nexport interface ReportButtonDef {\n category: ReportCategory;\n label: string;\n iconName: string;\n color: string;\n description: string;\n}\n\nexport interface FeedbackButtonDef {\n category: FeedbackCategory;\n label: string;\n iconName: string;\n color: string;\n description: string;\n}\n\n/** Standard incident report buttons — consistent across all subdomains */\nexport const REPORT_BUTTONS: ReportButtonDef[] = [\n {\n category: \"safety\",\n label: \"Safety\",\n iconName: \"ShieldAlert\",\n color: \"#ef4444\",\n description: \"Report a safety incident, near-miss, or hazard\",\n },\n {\n category: \"environmental\",\n label: \"Environmental\",\n iconName: \"Leaf\",\n color: \"#22c55e\",\n description: \"Report an environmental incident or concern\",\n },\n {\n category: \"stop-work\",\n label: \"Stop Work\",\n iconName: \"OctagonX\",\n color: \"#f59e0b\",\n description: \"Raise a stop work authority — immediate safety concern\",\n },\n];\n\n/** Standard feedback buttons — consistent across all subdomains */\nexport const FEEDBACK_BUTTONS: FeedbackButtonDef[] = [\n {\n category: \"problem\",\n label: \"Problem\",\n iconName: \"AlertTriangle\",\n color: \"#ef4444\",\n description: \"Report a problem with the platform\",\n },\n {\n category: \"suggestion\",\n label: \"Suggestion\",\n iconName: \"Lightbulb\",\n color: \"#8C34E9\",\n description: \"Suggest an improvement or new feature\",\n },\n];\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Create a default ReportingToolbarConfig with sensible defaults */\nexport function createReportingConfig(\n moduleName: string,\n handlers: {\n onReportSubmit: (report: IncidentReport) => Promise<void> | void;\n onFeedbackSubmit: (feedback: FeedbackReport) => Promise<void> | void;\n },\n): ReportingToolbarConfig {\n return {\n moduleName,\n showIncidentReporting: true,\n showFeedback: true,\n reportCategories: [\"safety\", \"environmental\", \"stop-work\"],\n feedbackCategories: [\"problem\", \"suggestion\"],\n ...handlers,\n };\n}\n\n// ─── ReportingToolbar Component ─────────────────────────────────────────────\n\nexport {\n ReportingToolbar,\n type ReportingToolbarProps,\n} from \"./ReportingToolbar\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/reporting/ReportingToolbar.tsx","../../src/reporting/index.ts"],"sourcesContent":["/**\n * ReportingToolbar — standard Safety / Environmental / Stop Work incident\n * reporting and Problem / Suggestion feedback for all Oplytics subdomains.\n *\n * Visual model — two grouped dropdown buttons in the top-right of the header:\n * ⚠ Report ▾ → Safety Incident | Environmental | Stop Work\n * 💡 Feedback ▾ → Report Problem | Suggestion\n *\n * Selecting an option opens a self-contained dialog with the right form for\n * that report/feedback type (severity / location / description / immediate\n * action for incidents; subject / description for feedback). On submit the\n * configured `onReportSubmit` / `onFeedbackSubmit` handler is called with\n * the canonical `IncidentReport` / `FeedbackReport` shape.\n *\n * Ported from the Policy Deployment subdomain — the canonical visual\n * reference. Replaces the previous headless / individual-icon-button design.\n *\n * Oplytics dark theme: #0A0E1A bg, #8C34E9 accent.\n */\nimport { useState } from \"react\";\nimport { cn } from \"../utils/cn\";\nimport {\n Button,\n Badge,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Input,\n Label,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../primitives\";\nimport {\n ShieldAlert,\n Leaf,\n OctagonX,\n Bug,\n Lightbulb,\n ChevronDown,\n Send,\n AlertTriangle,\n} from \"lucide-react\";\nimport { toast } from \"sonner\";\nimport type {\n ReportCategory,\n FeedbackCategory,\n IncidentReport,\n FeedbackReport,\n ReportingToolbarConfig,\n} from \"./index\";\n\n/* ── Report / feedback option catalogues ── */\n\ninterface ReportOption {\n category: ReportCategory;\n label: string;\n description: string;\n icon: React.ElementType;\n color: string;\n}\n\ninterface FeedbackOption {\n category: FeedbackCategory;\n label: string;\n description: string;\n icon: React.ElementType;\n color: string;\n}\n\nconst ALL_REPORT_OPTIONS: ReportOption[] = [\n {\n category: \"safety\",\n label: \"Safety Incident\",\n description: \"Report a safety hazard, near miss, or injury\",\n icon: ShieldAlert,\n color: \"#EF4444\",\n },\n {\n category: \"environmental\",\n label: \"Environmental\",\n description: \"Report an environmental concern or spill\",\n icon: Leaf,\n color: \"#22C55E\",\n },\n {\n category: \"stop-work\",\n label: \"Stop Work\",\n description: \"Raise a stop work authority — immediate halt\",\n icon: OctagonX,\n color: \"#F59E0B\",\n },\n];\n\nconst ALL_FEEDBACK_OPTIONS: FeedbackOption[] = [\n {\n category: \"problem\",\n label: \"Report Problem\",\n description: \"Something isn't working\",\n icon: Bug,\n color: \"#EF4444\",\n },\n {\n category: \"suggestion\",\n label: \"Suggestion\",\n description: \"Idea for improvement\",\n icon: Lightbulb,\n color: \"#8C34E9\",\n },\n];\n\nconst SEVERITY_OPTIONS: {\n value: NonNullable<IncidentReport[\"severity\"]>;\n label: string;\n color: string;\n}[] = [\n { value: \"critical\", label: \"Critical\", color: \"#EF4444\" },\n { value: \"high\", label: \"High\", color: \"#F59E0B\" },\n { value: \"medium\", label: \"Medium\", color: \"#3b82f6\" },\n { value: \"low\", label: \"Low\", color: \"#22C55E\" },\n];\n\n/* ── Props ── */\n\nexport interface ReportingToolbarProps extends Omit<ReportingToolbarConfig, \"moduleName\"> {\n /** Service/module name (e.g. \"Connect\", \"Policy Deployment\"). Defaults to \"Oplytics\". */\n moduleName?: string;\n /** Optional pre-fill for the report dialog's Location/Area field. */\n defaultLocation?: string;\n /**\n * Compact mode — currently a no-op (the new design always responsive-collapses\n * the labels at small breakpoints). Kept on the prop type so callers that\n * still pass it continue to type-check.\n * @deprecated The toolbar handles responsive collapse on its own.\n */\n compact?: boolean;\n /** CSS class for the container. */\n className?: string;\n}\n\n/* ── Component ── */\n\nexport function ReportingToolbar({\n moduleName = \"Oplytics\",\n showIncidentReporting = true,\n showFeedback = true,\n reportCategories = [\"safety\", \"environmental\", \"stop-work\"],\n feedbackCategories = [\"problem\", \"suggestion\"],\n onReportSubmit,\n onFeedbackSubmit,\n defaultLocation = \"\",\n className,\n}: ReportingToolbarProps) {\n const reportOptions = ALL_REPORT_OPTIONS.filter((opt) =>\n reportCategories.includes(opt.category),\n );\n const feedbackOptions = ALL_FEEDBACK_OPTIONS.filter((opt) =>\n feedbackCategories.includes(opt.category),\n );\n\n /* ── Report dialog state ── */\n const [reportDialogOpen, setReportDialogOpen] = useState(false);\n const [selectedReportCategory, setSelectedReportCategory] =\n useState<ReportCategory>(\"safety\");\n const [reportSeverity, setReportSeverity] =\n useState<NonNullable<IncidentReport[\"severity\"]>>(\"medium\");\n const [reportLocation, setReportLocation] = useState(defaultLocation);\n const [reportDescription, setReportDescription] = useState(\"\");\n const [reportImmediateAction, setReportImmediateAction] = useState(\"\");\n\n /* ── Feedback dialog state ── */\n const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);\n const [selectedFeedbackCategory, setSelectedFeedbackCategory] =\n useState<FeedbackCategory>(\"problem\");\n const [feedbackSubject, setFeedbackSubject] = useState(\"\");\n const [feedbackDescription, setFeedbackDescription] = useState(\"\");\n\n const [submitting, setSubmitting] = useState(false);\n\n const openReportDialog = (category: ReportCategory) => {\n setSelectedReportCategory(category);\n setReportSeverity(category === \"stop-work\" ? \"critical\" : \"medium\");\n setReportLocation(defaultLocation);\n setReportDescription(\"\");\n setReportImmediateAction(\"\");\n setReportDialogOpen(true);\n };\n\n const openFeedbackDialog = (category: FeedbackCategory) => {\n setSelectedFeedbackCategory(category);\n setFeedbackSubject(\"\");\n setFeedbackDescription(\"\");\n setFeedbackDialogOpen(true);\n };\n\n const handleReportSubmit = async () => {\n if (!reportDescription.trim()) {\n toast.error(\"Please provide a description\");\n return;\n }\n setSubmitting(true);\n try {\n const report: IncidentReport = {\n category: selectedReportCategory,\n description: reportDescription,\n location: reportLocation || undefined,\n severity: reportSeverity,\n immediateAction: reportImmediateAction || undefined,\n moduleName,\n timestamp: new Date().toISOString(),\n };\n await onReportSubmit(report);\n toast.success(\"Report submitted successfully\", {\n description:\n selectedReportCategory === \"stop-work\"\n ? \"Stop Work authority raised — management has been notified\"\n : \"Your report has been logged and assigned for review\",\n });\n setReportDialogOpen(false);\n } catch (err) {\n console.error(\"[ReportingToolbar] report submit failed:\", err);\n toast.error(\"Failed to submit report. Please try again.\");\n } finally {\n setSubmitting(false);\n }\n };\n\n const handleFeedbackSubmit = async () => {\n if (!feedbackDescription.trim()) {\n toast.error(\"Please provide a description\");\n return;\n }\n setSubmitting(true);\n try {\n const feedback: FeedbackReport = {\n category: selectedFeedbackCategory,\n subject: feedbackSubject || undefined,\n description: feedbackDescription,\n moduleName,\n timestamp: new Date().toISOString(),\n pageContext:\n typeof window !== \"undefined\" ? window.location.pathname : undefined,\n };\n await onFeedbackSubmit(feedback);\n toast.success(\n selectedFeedbackCategory === \"problem\"\n ? \"Problem reported — Oplytics team will review\"\n : \"Suggestion submitted — thank you for your feedback\",\n );\n setFeedbackDialogOpen(false);\n } catch (err) {\n console.error(\"[ReportingToolbar] feedback submit failed:\", err);\n toast.error(\"Failed to submit feedback. Please try again.\");\n } finally {\n setSubmitting(false);\n }\n };\n\n const reportConfig = ALL_REPORT_OPTIONS.find(\n (r) => r.category === selectedReportCategory,\n );\n\n return (\n <>\n {/* Toolbar buttons */}\n <div className={cn(\"flex items-center gap-2\", className)}>\n {/* Report dropdown */}\n {showIncidentReporting && reportOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 gap-1.5 text-xs font-medium text-[#EF4444] hover:bg-[#EF4444]/10 hover:text-[#EF4444] border border-[#EF4444]/20\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">Report</span>\n <ChevronDown className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n {reportOptions.map((opt) => (\n <DropdownMenuItem\n key={opt.category}\n onClick={() => openReportDialog(opt.category)}\n className=\"gap-3 py-2.5\"\n >\n <opt.icon className=\"h-4 w-4\" style={{ color: opt.color }} />\n <div>\n <p className=\"text-sm font-medium\">{opt.label}</p>\n <p className=\"text-[10px] text-muted-foreground\">{opt.description}</p>\n </div>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n {/* Feedback dropdown */}\n {showFeedback && feedbackOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-9 gap-1.5 text-xs font-medium text-[#8C34E9] hover:bg-[#8C34E9]/10 hover:text-[#8C34E9] border border-[#8C34E9]/20\"\n >\n <Lightbulb className=\"h-3.5 w-3.5\" />\n <span className=\"hidden sm:inline\">Feedback</span>\n <ChevronDown className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n {feedbackOptions.map((opt, idx) => (\n <span key={opt.category}>\n {idx > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem\n onClick={() => openFeedbackDialog(opt.category)}\n className=\"gap-3 py-2.5\"\n >\n <opt.icon className=\"h-4 w-4\" style={{ color: opt.color }} />\n <div>\n <p className=\"text-sm font-medium\">{opt.label}</p>\n <p className=\"text-[10px] text-muted-foreground\">{opt.description}</p>\n </div>\n </DropdownMenuItem>\n </span>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n\n {/* Report Dialog */}\n <Dialog open={reportDialogOpen} onOpenChange={setReportDialogOpen}>\n <DialogContent className=\"sm:max-w-[500px]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n {reportConfig && (\n <reportConfig.icon className=\"h-5 w-5\" style={{ color: reportConfig.color }} />\n )}\n {reportConfig?.label || \"Report\"}\n {selectedReportCategory === \"stop-work\" && (\n <Badge className=\"bg-[#F59E0B] text-white text-[9px] border-0\">URGENT</Badge>\n )}\n </DialogTitle>\n <DialogDescription>\n {reportConfig?.description}. All reports are logged and tracked.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Severity</Label>\n <Select\n value={reportSeverity}\n onValueChange={(v) =>\n setReportSeverity(v as NonNullable<IncidentReport[\"severity\"]>)\n }\n >\n <SelectTrigger className=\"h-9\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SEVERITY_OPTIONS.map((s) => (\n <SelectItem key={s.value} value={s.value}>\n <div className=\"flex items-center gap-2\">\n <div\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: s.color }}\n />\n {s.label}\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Location / Area</Label>\n <Input\n value={reportLocation}\n onChange={(e) => setReportLocation(e.target.value)}\n placeholder=\"e.g., Production Line 3, Warehouse B\"\n className=\"h-9\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Description *</Label>\n <Textarea\n value={reportDescription}\n onChange={(e) => setReportDescription(e.target.value)}\n placeholder=\"Describe what happened or what you observed...\"\n rows={4}\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Immediate Action Taken</Label>\n <Textarea\n value={reportImmediateAction}\n onChange={(e) => setReportImmediateAction(e.target.value)}\n placeholder=\"Describe any immediate action taken to make the area safe...\"\n rows={2}\n />\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setReportDialogOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={handleReportSubmit}\n disabled={submitting || !reportDescription.trim()}\n className=\"gap-2\"\n style={{\n backgroundColor: reportConfig?.color,\n color: \"white\",\n }}\n >\n <Send className=\"h-3.5 w-3.5\" />\n {submitting ? \"Submitting...\" : \"Submit Report\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {/* Feedback Dialog */}\n <Dialog open={feedbackDialogOpen} onOpenChange={setFeedbackDialogOpen}>\n <DialogContent className=\"sm:max-w-[500px]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n {selectedFeedbackCategory === \"problem\" ? (\n <Bug className=\"h-5 w-5 text-[#EF4444]\" />\n ) : (\n <Lightbulb className=\"h-5 w-5 text-[#8C34E9]\" />\n )}\n {selectedFeedbackCategory === \"problem\"\n ? \"Report a Problem\"\n : \"Submit a Suggestion\"}\n </DialogTitle>\n <DialogDescription>\n {selectedFeedbackCategory === \"problem\"\n ? \"Help us fix issues — describe what went wrong.\"\n : \"We'd love to hear your ideas for improving Oplytics.\"}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Subject</Label>\n <Input\n value={feedbackSubject}\n onChange={(e) => setFeedbackSubject(e.target.value)}\n placeholder={\n selectedFeedbackCategory === \"problem\"\n ? \"Brief summary of the issue\"\n : \"Brief summary of your idea\"\n }\n className=\"h-9\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label className=\"text-xs font-medium\">Description *</Label>\n <Textarea\n value={feedbackDescription}\n onChange={(e) => setFeedbackDescription(e.target.value)}\n placeholder={\n selectedFeedbackCategory === \"problem\"\n ? \"What were you trying to do? What happened instead?\"\n : \"Describe your suggestion and how it would help...\"\n }\n rows={5}\n />\n </div>\n\n <div className=\"text-[10px] text-muted-foreground\">\n Module: {moduleName} — Feedback is reviewed by the Oplytics team\n </div>\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setFeedbackDialogOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={handleFeedbackSubmit}\n disabled={submitting || !feedbackDescription.trim()}\n className=\"gap-2\"\n style={{\n backgroundColor:\n selectedFeedbackCategory === \"problem\" ? \"#EF4444\" : \"#8C34E9\",\n color: \"white\",\n }}\n >\n <Send className=\"h-3.5 w-3.5\" />\n {submitting ? \"Submitting...\" : \"Submit\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n","/**\n * @oplytics/shared-ui/reporting\n *\n * Reporting and feedback toolbar types and configuration for all Oplytics subdomains.\n * Provides the standard Safety/Environmental/Stop Work incident reporting buttons\n * and Problem/Suggestion feedback buttons.\n *\n * The actual ReportingToolbar React component depends on shadcn/ui primitives\n * (Dialog, Button, Textarea, Select) which vary across subdomains until\n * consolidation. This module provides the types, configuration, and submission\n * logic that are shared.\n */\n\n// ─── Report Types ────────────────────────────────────────────────────────────\n\nexport type ReportCategory = \"safety\" | \"environmental\" | \"stop-work\";\nexport type FeedbackCategory = \"problem\" | \"suggestion\";\n\nexport interface IncidentReport {\n category: ReportCategory;\n description: string;\n location?: string;\n severity?: \"low\" | \"medium\" | \"high\" | \"critical\";\n /** Optional — what was done at the scene to make the area safe. */\n immediateAction?: string;\n moduleName: string;\n /** ISO timestamp */\n timestamp: string;\n /** User who submitted */\n submittedBy?: string;\n /** Hierarchy context at time of submission */\n hierarchyContext?: {\n enterpriseId?: number;\n businessUnitId?: number;\n siteId?: number;\n areaId?: number;\n assetId?: number;\n };\n}\n\nexport interface FeedbackReport {\n category: FeedbackCategory;\n /** Optional — short summary entered in the dialog before the description. */\n subject?: string;\n description: string;\n moduleName: string;\n /** ISO timestamp */\n timestamp: string;\n /** User who submitted */\n submittedBy?: string;\n /** Page/route where feedback was submitted */\n pageContext?: string;\n}\n\n// ─── Toolbar Configuration ───────────────────────────────────────────────────\n\nexport interface ReportingToolbarConfig {\n /** Service/module name (e.g., \"SQDCP\", \"OEE Manager\") */\n moduleName: string;\n /** Whether to show incident reporting buttons (default: true) */\n showIncidentReporting?: boolean;\n /** Whether to show feedback buttons (default: true) */\n showFeedback?: boolean;\n /** Custom report categories to show (default: all three) */\n reportCategories?: ReportCategory[];\n /** Custom feedback categories to show (default: both) */\n feedbackCategories?: FeedbackCategory[];\n /** Handler called when an incident report is submitted */\n onReportSubmit: (report: IncidentReport) => Promise<void> | void;\n /** Handler called when feedback is submitted */\n onFeedbackSubmit: (feedback: FeedbackReport) => Promise<void> | void;\n}\n\n// ─── Report Button Definitions ───────────────────────────────────────────────\n\nexport interface ReportButtonDef {\n category: ReportCategory;\n label: string;\n iconName: string;\n color: string;\n description: string;\n}\n\nexport interface FeedbackButtonDef {\n category: FeedbackCategory;\n label: string;\n iconName: string;\n color: string;\n description: string;\n}\n\n/** Standard incident report buttons — consistent across all subdomains */\nexport const REPORT_BUTTONS: ReportButtonDef[] = [\n {\n category: \"safety\",\n label: \"Safety\",\n iconName: \"ShieldAlert\",\n color: \"#ef4444\",\n description: \"Report a safety incident, near-miss, or hazard\",\n },\n {\n category: \"environmental\",\n label: \"Environmental\",\n iconName: \"Leaf\",\n color: \"#22c55e\",\n description: \"Report an environmental incident or concern\",\n },\n {\n category: \"stop-work\",\n label: \"Stop Work\",\n iconName: \"OctagonX\",\n color: \"#f59e0b\",\n description: \"Raise a stop work authority — immediate safety concern\",\n },\n];\n\n/** Standard feedback buttons — consistent across all subdomains */\nexport const FEEDBACK_BUTTONS: FeedbackButtonDef[] = [\n {\n category: \"problem\",\n label: \"Problem\",\n iconName: \"AlertTriangle\",\n color: \"#ef4444\",\n description: \"Report a problem with the platform\",\n },\n {\n category: \"suggestion\",\n label: \"Suggestion\",\n iconName: \"Lightbulb\",\n color: \"#8C34E9\",\n description: \"Suggest an improvement or new feature\",\n },\n];\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Create a default ReportingToolbarConfig with sensible defaults */\nexport function createReportingConfig(\n moduleName: string,\n handlers: {\n onReportSubmit: (report: IncidentReport) => Promise<void> | void;\n onFeedbackSubmit: (feedback: FeedbackReport) => Promise<void> | void;\n },\n): ReportingToolbarConfig {\n return {\n moduleName,\n showIncidentReporting: true,\n showFeedback: true,\n reportCategories: [\"safety\", \"environmental\", \"stop-work\"],\n feedbackCategories: [\"problem\", \"suggestion\"],\n ...handlers,\n };\n}\n\n// ─── ReportingToolbar Component ─────────────────────────────────────────────\n\nexport {\n ReportingToolbar,\n type ReportingToolbarProps,\n} from \"./ReportingToolbar\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,SAAS,gBAAgB;AAyBzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AA4NlB,mBAYY,KALF,YAPV;AAjMJ,IAAM,qBAAqC;AAAA,EACzC;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AACF;AAEA,IAAM,uBAAyC;AAAA,EAC7C;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AACF;AAEA,IAAM,mBAIA;AAAA,EACJ,EAAE,OAAO,YAAY,OAAO,YAAY,OAAO,UAAU;AAAA,EACzD,EAAE,OAAO,QAAQ,OAAO,QAAQ,OAAO,UAAU;AAAA,EACjD,EAAE,OAAO,UAAU,OAAO,UAAU,OAAO,UAAU;AAAA,EACrD,EAAE,OAAO,OAAO,OAAO,OAAO,OAAO,UAAU;AACjD;AAsBO,SAAS,iBAAiB;AAAA,EAC/B,aAAa;AAAA,EACb,wBAAwB;AAAA,EACxB,eAAe;AAAA,EACf,mBAAmB,CAAC,UAAU,iBAAiB,WAAW;AAAA,EAC1D,qBAAqB,CAAC,WAAW,YAAY;AAAA,EAC7C;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AACF,GAA0B;AACxB,QAAM,gBAAgB,mBAAmB;AAAA,IAAO,CAAC,QAC/C,iBAAiB,SAAS,IAAI,QAAQ;AAAA,EACxC;AACA,QAAM,kBAAkB,qBAAqB;AAAA,IAAO,CAAC,QACnD,mBAAmB,SAAS,IAAI,QAAQ;AAAA,EAC1C;AAGA,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,SAAS,KAAK;AAC9D,QAAM,CAAC,wBAAwB,yBAAyB,IACtD,SAAyB,QAAQ;AACnC,QAAM,CAAC,gBAAgB,iBAAiB,IACtC,SAAkD,QAAQ;AAC5D,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,eAAe;AACpE,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,EAAE;AAC7D,QAAM,CAAC,uBAAuB,wBAAwB,IAAI,SAAS,EAAE;AAGrE,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,SAAS,KAAK;AAClE,QAAM,CAAC,0BAA0B,2BAA2B,IAC1D,SAA2B,SAAS;AACtC,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,EAAE;AACzD,QAAM,CAAC,qBAAqB,sBAAsB,IAAI,SAAS,EAAE;AAEjE,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,QAAM,mBAAmB,CAAC,aAA6B;AACrD,8BAA0B,QAAQ;AAClC,sBAAkB,aAAa,cAAc,aAAa,QAAQ;AAClE,sBAAkB,eAAe;AACjC,yBAAqB,EAAE;AACvB,6BAAyB,EAAE;AAC3B,wBAAoB,IAAI;AAAA,EAC1B;AAEA,QAAM,qBAAqB,CAAC,aAA+B;AACzD,gCAA4B,QAAQ;AACpC,uBAAmB,EAAE;AACrB,2BAAuB,EAAE;AACzB,0BAAsB,IAAI;AAAA,EAC5B;AAEA,QAAM,qBAAqB,YAAY;AACrC,QAAI,CAAC,kBAAkB,KAAK,GAAG;AAC7B,YAAM,MAAM,8BAA8B;AAC1C;AAAA,IACF;AACA,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,SAAyB;AAAA,QAC7B,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UAAU,kBAAkB;AAAA,QAC5B,UAAU;AAAA,QACV,iBAAiB,yBAAyB;AAAA,QAC1C;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AACA,YAAM,eAAe,MAAM;AAC3B,YAAM,QAAQ,iCAAiC;AAAA,QAC7C,aACE,2BAA2B,cACvB,mEACA;AAAA,MACR,CAAC;AACD,0BAAoB,KAAK;AAAA,IAC3B,SAAS,KAAK;AACZ,cAAQ,MAAM,4CAA4C,GAAG;AAC7D,YAAM,MAAM,4CAA4C;AAAA,IAC1D,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,uBAAuB,YAAY;AACvC,QAAI,CAAC,oBAAoB,KAAK,GAAG;AAC/B,YAAM,MAAM,8BAA8B;AAC1C;AAAA,IACF;AACA,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,WAA2B;AAAA,QAC/B,UAAU;AAAA,QACV,SAAS,mBAAmB;AAAA,QAC5B,aAAa;AAAA,QACb;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,aACE,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAAA,MAC/D;AACA,YAAM,iBAAiB,QAAQ;AAC/B,YAAM;AAAA,QACJ,6BAA6B,YACzB,sDACA;AAAA,MACN;AACA,4BAAsB,KAAK;AAAA,IAC7B,SAAS,KAAK;AACZ,cAAQ,MAAM,8CAA8C,GAAG;AAC/D,YAAM,MAAM,8CAA8C;AAAA,IAC5D,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,eAAe,mBAAmB;AAAA,IACtC,CAAC,MAAM,EAAE,aAAa;AAAA,EACxB;AAEA,SACE,iCAEE;AAAA,yBAAC,SAAI,WAAW,GAAG,2BAA2B,SAAS,GAEpD;AAAA,+BAAyB,cAAc,SAAS,KAC/C,qBAAC,gBACC;AAAA,4BAAC,uBAAoB,SAAO,MAC1B;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YAEV;AAAA,kCAAC,iBAAc,WAAU,eAAc;AAAA,cACvC,oBAAC,UAAK,WAAU,oBAAmB,oBAAM;AAAA,cACzC,oBAAC,eAAY,WAAU,WAAU;AAAA;AAAA;AAAA,QACnC,GACF;AAAA,QACA,oBAAC,uBAAoB,OAAM,OAAM,WAAU,QACxC,wBAAc,IAAI,CAAC,QAClB;AAAA,UAAC;AAAA;AAAA,YAEC,SAAS,MAAM,iBAAiB,IAAI,QAAQ;AAAA,YAC5C,WAAU;AAAA,YAEV;AAAA,kCAAC,IAAI,MAAJ,EAAS,WAAU,WAAU,OAAO,EAAE,OAAO,IAAI,MAAM,GAAG;AAAA,cAC3D,qBAAC,SACC;AAAA,oCAAC,OAAE,WAAU,uBAAuB,cAAI,OAAM;AAAA,gBAC9C,oBAAC,OAAE,WAAU,qCAAqC,cAAI,aAAY;AAAA,iBACpE;AAAA;AAAA;AAAA,UARK,IAAI;AAAA,QASX,CACD,GACH;AAAA,SACF;AAAA,MAID,gBAAgB,gBAAgB,SAAS,KACxC,qBAAC,gBACC;AAAA,4BAAC,uBAAoB,SAAO,MAC1B;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAU;AAAA,YAEV;AAAA,kCAAC,aAAU,WAAU,eAAc;AAAA,cACnC,oBAAC,UAAK,WAAU,oBAAmB,sBAAQ;AAAA,cAC3C,oBAAC,eAAY,WAAU,WAAU;AAAA;AAAA;AAAA,QACnC,GACF;AAAA,QACA,oBAAC,uBAAoB,OAAM,OAAM,WAAU,QACxC,0BAAgB,IAAI,CAAC,KAAK,QACzB,qBAAC,UACE;AAAA,gBAAM,KAAK,oBAAC,yBAAsB;AAAA,UACnC;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,MAAM,mBAAmB,IAAI,QAAQ;AAAA,cAC9C,WAAU;AAAA,cAEV;AAAA,oCAAC,IAAI,MAAJ,EAAS,WAAU,WAAU,OAAO,EAAE,OAAO,IAAI,MAAM,GAAG;AAAA,gBAC3D,qBAAC,SACC;AAAA,sCAAC,OAAE,WAAU,uBAAuB,cAAI,OAAM;AAAA,kBAC9C,oBAAC,OAAE,WAAU,qCAAqC,cAAI,aAAY;AAAA,mBACpE;AAAA;AAAA;AAAA,UACF;AAAA,aAXS,IAAI,QAYf,CACD,GACH;AAAA,SACF;AAAA,OAEJ;AAAA,IAGA,oBAAC,UAAO,MAAM,kBAAkB,cAAc,qBAC5C,+BAAC,iBAAc,WAAU,oBACvB;AAAA,2BAAC,gBACC;AAAA,6BAAC,eAAY,WAAU,2BACpB;AAAA,0BACC,oBAAC,aAAa,MAAb,EAAkB,WAAU,WAAU,OAAO,EAAE,OAAO,aAAa,MAAM,GAAG;AAAA,UAE9E,cAAc,SAAS;AAAA,UACvB,2BAA2B,eAC1B,oBAAC,SAAM,WAAU,+CAA8C,oBAAM;AAAA,WAEzE;AAAA,QACA,qBAAC,qBACE;AAAA,wBAAc;AAAA,UAAY;AAAA,WAC7B;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,sBAAQ;AAAA,UAC/C;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,eAAe,CAAC,MACd,kBAAkB,CAA4C;AAAA,cAGhE;AAAA,oCAAC,iBAAc,WAAU,OACvB,8BAAC,eAAY,GACf;AAAA,gBACA,oBAAC,iBACE,2BAAiB,IAAI,CAAC,MACrB,oBAAC,cAAyB,OAAO,EAAE,OACjC,+BAAC,SAAI,WAAU,2BACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,WAAU;AAAA,sBACV,OAAO,EAAE,iBAAiB,EAAE,MAAM;AAAA;AAAA,kBACpC;AAAA,kBACC,EAAE;AAAA,mBACL,KAPe,EAAE,KAQnB,CACD,GACH;AAAA;AAAA;AAAA,UACF;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,6BAAe;AAAA,UACtD;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,kBAAkB,EAAE,OAAO,KAAK;AAAA,cACjD,aAAY;AAAA,cACZ,WAAU;AAAA;AAAA,UACZ;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,2BAAa;AAAA,UACpD;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,qBAAqB,EAAE,OAAO,KAAK;AAAA,cACpD,aAAY;AAAA,cACZ,MAAM;AAAA;AAAA,UACR;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,oCAAsB;AAAA,UAC7D;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,yBAAyB,EAAE,OAAO,KAAK;AAAA,cACxD,aAAY;AAAA,cACZ,MAAM;AAAA;AAAA,UACR;AAAA,WACF;AAAA,SACF;AAAA,MAEA,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,WAAU,SAAS,MAAM,oBAAoB,KAAK,GAAG,oBAErE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,UAAU,cAAc,CAAC,kBAAkB,KAAK;AAAA,YAChD,WAAU;AAAA,YACV,OAAO;AAAA,cACL,iBAAiB,cAAc;AAAA,cAC/B,OAAO;AAAA,YACT;AAAA,YAEA;AAAA,kCAAC,QAAK,WAAU,eAAc;AAAA,cAC7B,aAAa,kBAAkB;AAAA;AAAA;AAAA,QAClC;AAAA,SACF;AAAA,OACF,GACF;AAAA,IAGA,oBAAC,UAAO,MAAM,oBAAoB,cAAc,uBAC9C,+BAAC,iBAAc,WAAU,oBACvB;AAAA,2BAAC,gBACC;AAAA,6BAAC,eAAY,WAAU,2BACpB;AAAA,uCAA6B,YAC5B,oBAAC,OAAI,WAAU,0BAAyB,IAExC,oBAAC,aAAU,WAAU,0BAAyB;AAAA,UAE/C,6BAA6B,YAC1B,qBACA;AAAA,WACN;AAAA,QACA,oBAAC,qBACE,uCAA6B,YAC1B,wDACA,wDACN;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,qBAAO;AAAA,UAC9C;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,cAClD,aACE,6BAA6B,YACzB,+BACA;AAAA,cAEN,WAAU;AAAA;AAAA,UACZ;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,WAAU,uBAAsB,2BAAa;AAAA,UACpD;AAAA,YAAC;AAAA;AAAA,cACC,OAAO;AAAA,cACP,UAAU,CAAC,MAAM,uBAAuB,EAAE,OAAO,KAAK;AAAA,cACtD,aACE,6BAA6B,YACzB,uDACA;AAAA,cAEN,MAAM;AAAA;AAAA,UACR;AAAA,WACF;AAAA,QAEA,qBAAC,SAAI,WAAU,qCAAoC;AAAA;AAAA,UACxC;AAAA,UAAW;AAAA,WACtB;AAAA,SACF;AAAA,MAEA,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,WAAU,SAAS,MAAM,sBAAsB,KAAK,GAAG,oBAEvE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,UAAU,cAAc,CAAC,oBAAoB,KAAK;AAAA,YAClD,WAAU;AAAA,YACV,OAAO;AAAA,cACL,iBACE,6BAA6B,YAAY,YAAY;AAAA,cACvD,OAAO;AAAA,YACT;AAAA,YAEA;AAAA,kCAAC,QAAK,WAAU,eAAc;AAAA,cAC7B,aAAa,kBAAkB;AAAA;AAAA;AAAA,QAClC;AAAA,SACF;AAAA,OACF,GACF;AAAA,KACF;AAEJ;;;AC1aO,IAAM,iBAAoC;AAAA,EAC/C;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AACF;AAGO,IAAM,mBAAwC;AAAA,EACnD;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,UAAU;AAAA,IACV,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AACF;AAKO,SAAS,sBACd,YACA,UAIwB;AACxB,SAAO;AAAA,IACL;AAAA,IACA,uBAAuB;AAAA,IACvB,cAAc;AAAA,IACd,kBAAkB,CAAC,UAAU,iBAAiB,WAAW;AAAA,IACzD,oBAAoB,CAAC,WAAW,YAAY;AAAA,IAC5C,GAAG;AAAA,EACL;AACF;","names":[]}
@@ -1,5 +1,5 @@
1
- import { D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser } from '../HealthFooter-BN5LHTp6.js';
2
- export { C as CrossServiceNav, d as CrossServiceNavProps, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection } from '../HealthFooter-BN5LHTp6.js';
1
+ import { D as DashboardLayout, a as DashboardLayoutEnterprise, b as DashboardLayoutProps, c as DashboardLayoutUser } from '../HealthFooter-9rZjMmqc.js';
2
+ export { C as CrossServiceNav, d as CrossServiceNavProps, H as HealthFooter, e as HealthFooterEnterprise, f as HealthFooterProps, g as HealthFooterUser, M as MenuItem, h as MenuSection } from '../HealthFooter-9rZjMmqc.js';
3
3
  export { C as CrossServiceLink, b as buildCrossServiceLinks } from '../crossNav-Bd8-0qoi.js';
4
4
  import 'react/jsx-runtime';
5
5
  import 'react';
@@ -5,14 +5,15 @@ import {
5
5
  CrossServiceNav,
6
6
  DashboardLayout,
7
7
  HealthFooter
8
- } from "../chunk-NQZDFCIU.js";
8
+ } from "../chunk-L2N36IW6.js";
9
9
  import "../chunk-4QJMLQSH.js";
10
- import "../chunk-ETIKYZIX.js";
10
+ import "../chunk-J7LWAIQK.js";
11
11
  import "../chunk-3E3GLIQV.js";
12
12
  import "../chunk-3G43UG63.js";
13
13
  import "../chunk-PS4K7FKB.js";
14
- import "../chunk-LTUYIBMA.js";
14
+ import "../chunk-HR77H6PE.js";
15
15
  import "../chunk-JT3XLKKD.js";
16
+ import "../chunk-R4NOLCSI.js";
16
17
 
17
18
  // src/shell/index.ts
18
19
  var OplyticsServiceShell = DashboardLayout;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/index.ts"],"sourcesContent":["/**\n * @pablo2410/shared-ui/shell — Oplytics Service Shell\n *\n * The single composed chrome (sidebar + header + breadcrumb + reporting +\n * footer + user menu) used by every Oplytics subdomain. Subdomains become\n * a ~50-line adapter that supplies *data and identity*, nothing visual.\n * One PR to shared-ui + a coordinated version bump propagates a chrome\n * change to every service.\n *\n * Composition: re-exports the existing `DashboardLayout` from\n * `@pablo2410/shared-ui/layout` under the clearer canonical name\n * `OplyticsServiceShell`. The two are the same component; consumers\n * should prefer `/shell` going forward — `/layout` will be marked\n * `@deprecated` once every subdomain has migrated and removed in\n * shared-ui v1.0 (see oplytics-subdomains tracking epic).\n *\n * CSS: ship `dist/shell/oplytics-shell.css` containing every Tailwind v4\n * utility this shell uses (built by `pnpm build:shell-css`). Consumers\n * `@import` it once in their own `index.css` and never need an\n * `@source` directive.\n *\n * ```css\n * @import \"tailwindcss\";\n * @import \"@pablo2410/shared-ui/theme/oplytics-theme.css\";\n * @import \"@pablo2410/shared-ui/shell/oplytics-shell.css\";\n * ```\n *\n * Usage:\n *\n * ```tsx\n * import { OplyticsServiceShell } from \"@pablo2410/shared-ui/shell\";\n *\n * <OplyticsServiceShell\n * serviceName=\"Connect\"\n * serviceIcon={<Zap />}\n * menuSections={menuSections}\n * adminSections={adminSections}\n * isAdmin={user?.role === \"platform_admin\"}\n * activePath={location}\n * onNavigate={setLocation}\n * user={user}\n * onLogout={logout}\n * enterprise={selection.enterprise}\n * hierarchyNavigator={<HierarchyNavigator hierarchy={hierarchy} />}\n * reportingToolbar={<ReportingToolbar … />}\n * >\n * <Routes />\n * </OplyticsServiceShell>\n * ```\n */\n\nimport {\n DashboardLayout,\n type DashboardLayoutProps,\n type DashboardLayoutUser,\n type DashboardLayoutEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n} from \"../layout\";\nimport {\n buildCrossServiceLinks,\n type CrossServiceLink,\n} from \"../services/crossNav\";\n\n/**\n * The canonical composed Oplytics chrome.\n *\n * This is the same React component as `DashboardLayout` in\n * `@pablo2410/shared-ui/layout`, re-exported under a clearer name.\n * Consumers should prefer `OplyticsServiceShell` going forward.\n */\nconst OplyticsServiceShell = DashboardLayout;\n\n/**\n * Props accepted by `OplyticsServiceShell`.\n * (Alias of `DashboardLayoutProps`.)\n */\ntype ServiceShellProps = DashboardLayoutProps;\n\n/**\n * Authenticated user passed to the shell. Small enough to fit both\n * stateless-JWT subdomains (Connect, Policy Deployment) and DB-backed\n * ones (OEE Manager).\n */\ntype ServiceShellUser = DashboardLayoutUser;\n\n/**\n * Currently-scoped enterprise — drives the header enterprise badge.\n */\ntype ServiceShellEnterprise = DashboardLayoutEnterprise;\n\nexport {\n OplyticsServiceShell,\n type ServiceShellProps,\n type ServiceShellUser,\n type ServiceShellEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n buildCrossServiceLinks,\n type CrossServiceLink,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6EA,IAAM,uBAAuB;","names":[]}
1
+ {"version":3,"sources":["../../src/shell/index.ts"],"sourcesContent":["/**\n * @pablo2410/shared-ui/shell — Oplytics Service Shell\n *\n * The single composed chrome (sidebar + header + breadcrumb + reporting +\n * footer + user menu) used by every Oplytics subdomain. Subdomains become\n * a ~50-line adapter that supplies *data and identity*, nothing visual.\n * One PR to shared-ui + a coordinated version bump propagates a chrome\n * change to every service.\n *\n * Composition: re-exports the existing `DashboardLayout` from\n * `@pablo2410/shared-ui/layout` under the clearer canonical name\n * `OplyticsServiceShell`. The two are the same component; consumers\n * should prefer `/shell` going forward — `/layout` will be marked\n * `@deprecated` once every subdomain has migrated and removed in\n * shared-ui v1.0 (see oplytics-subdomains tracking epic).\n *\n * CSS: ship `dist/shell/oplytics-shell.css` containing every Tailwind v4\n * utility this shell uses (built by `pnpm build:shell-css`). Consumers\n * `@import` it once in their own `index.css` and never need an\n * `@source` directive.\n *\n * ```css\n * @import \"tailwindcss\";\n * @import \"@pablo2410/shared-ui/theme/oplytics-theme.css\";\n * @import \"@pablo2410/shared-ui/shell/oplytics-shell.css\";\n * ```\n *\n * Usage:\n *\n * ```tsx\n * import { OplyticsServiceShell } from \"@pablo2410/shared-ui/shell\";\n *\n * <OplyticsServiceShell\n * serviceName=\"Connect\"\n * serviceIcon={<Zap />}\n * menuSections={menuSections}\n * adminSections={adminSections}\n * isAdmin={user?.role === \"platform_admin\"}\n * activePath={location}\n * onNavigate={setLocation}\n * user={user}\n * onLogout={logout}\n * enterprise={selection.enterprise}\n * hierarchyNavigator={<HierarchyNavigator hierarchy={hierarchy} />}\n * reportingToolbar={<ReportingToolbar … />}\n * >\n * <Routes />\n * </OplyticsServiceShell>\n * ```\n */\n\nimport {\n DashboardLayout,\n type DashboardLayoutProps,\n type DashboardLayoutUser,\n type DashboardLayoutEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n} from \"../layout\";\nimport {\n buildCrossServiceLinks,\n type CrossServiceLink,\n} from \"../services/crossNav\";\n\n/**\n * The canonical composed Oplytics chrome.\n *\n * This is the same React component as `DashboardLayout` in\n * `@pablo2410/shared-ui/layout`, re-exported under a clearer name.\n * Consumers should prefer `OplyticsServiceShell` going forward.\n */\nconst OplyticsServiceShell = DashboardLayout;\n\n/**\n * Props accepted by `OplyticsServiceShell`.\n * (Alias of `DashboardLayoutProps`.)\n */\ntype ServiceShellProps = DashboardLayoutProps;\n\n/**\n * Authenticated user passed to the shell. Small enough to fit both\n * stateless-JWT subdomains (Connect, Policy Deployment) and DB-backed\n * ones (OEE Manager).\n */\ntype ServiceShellUser = DashboardLayoutUser;\n\n/**\n * Currently-scoped enterprise — drives the header enterprise badge.\n */\ntype ServiceShellEnterprise = DashboardLayoutEnterprise;\n\nexport {\n OplyticsServiceShell,\n type ServiceShellProps,\n type ServiceShellUser,\n type ServiceShellEnterprise,\n type MenuItem,\n type MenuSection,\n HealthFooter,\n type HealthFooterProps,\n type HealthFooterUser,\n type HealthFooterEnterprise,\n CrossServiceNav,\n type CrossServiceNavProps,\n buildCrossServiceLinks,\n type CrossServiceLink,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6EA,IAAM,uBAAuB;","names":[]}