@burdenoff/microfe-bigconsole 2026.801.1 → 2026.801.2

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.
Files changed (33) hide show
  1. package/dist/bigconsole/AdminRoutes.js +1 -1
  2. package/dist/bigconsole/AdminRoutes.js.map +1 -1
  3. package/dist/bigconsole/BigConsoleRoot.js +1 -1
  4. package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
  5. package/dist/bigconsole/BigConsoleRoutes.js +1 -1
  6. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  7. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +1 -1
  8. package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
  9. package/dist/bigconsole/contexts/NavigationContext.js +1 -1
  10. package/dist/bigconsole/contexts/NavigationContext.js.map +1 -1
  11. package/dist/bigconsole/hooks/useFilterUrlSync.js +1 -1
  12. package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
  13. package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
  14. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  15. package/dist/bigconsole/pages/DashboardViewPage.js +1 -1
  16. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  17. package/dist/bigconsole/pages/DataParsersPage.js +1 -1
  18. package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
  19. package/dist/bigconsole/pages/DataSinkBuilderPage.js +1 -1
  20. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  21. package/dist/bigconsole/pages/DataSinkViewPage.js +1 -1
  22. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  23. package/dist/bigconsole/pages/ParserBuilderPage.js +1 -1
  24. package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
  25. package/dist/bigconsole/pages/ParserViewPage.js +1 -1
  26. package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
  27. package/dist/bigconsole/pages/PipelineBuilderPage.js +1 -1
  28. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  29. package/dist/bigconsole/pages/PipelineViewPage.js +1 -1
  30. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  31. package/dist/bigconsole/pages/WorkflowViewPage.js +1 -1
  32. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  33. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  import { AdminPage as e } from "./pages/AdminPage.js";
2
- import { Route as t, Routes as n } from "react-router-dom";
2
+ import { Route as t, Routes as n } from "react-router";
3
3
  import { jsx as r } from "react/jsx-runtime";
4
4
  //#region src/bigconsole/AdminRoutes.tsx
5
5
  function i() {
@@ -1 +1 @@
1
- {"version":3,"file":"AdminRoutes.js","names":[],"sources":["../../src/bigconsole/AdminRoutes.tsx"],"sourcesContent":["/**\n * BigConsole Admin Routes\n *\n * Internal routing for the admin surface mounted inside admin-app.\n */\n\nimport { Route, Routes } from 'react-router-dom';\nimport { AdminPage } from './pages/AdminPage';\n\nexport function AdminRoutes() {\n return (\n <Routes>\n <Route path=\"*\" element={<AdminPage />} />\n </Routes>\n );\n}\n"],"mappings":";;;;AASA,SAAgB,IAAc;AAC5B,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAO,MAAK;EAAI,SAAS,kBAAC,GAAD,EAAa,CAAA;EAAI,CAAA,EACnC,CAAA"}
1
+ {"version":3,"file":"AdminRoutes.js","names":[],"sources":["../../src/bigconsole/AdminRoutes.tsx"],"sourcesContent":["/**\n * BigConsole Admin Routes\n *\n * Internal routing for the admin surface mounted inside admin-app.\n */\n\nimport { Route, Routes } from 'react-router';\nimport { AdminPage } from './pages/AdminPage';\n\nexport function AdminRoutes() {\n return (\n <Routes>\n <Route path=\"*\" element={<AdminPage />} />\n </Routes>\n );\n}\n"],"mappings":";;;;AASA,SAAgB,IAAc;AAC5B,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAO,MAAK;EAAI,SAAS,kBAAC,GAAD,EAAa,CAAA;EAAI,CAAA,EACnC,CAAA"}
@@ -12,7 +12,7 @@ import u from "./components/DrilldownDashboardRenderer.js";
12
12
  import d from "./components/preview/AiPreviewPanel.js";
13
13
  import f from "./components/DrilldownModal.js";
14
14
  import { useCallback as p, useEffect as m, useLayoutEffect as h, useRef as g, useState as _ } from "react";
15
- import { useSearchParams as v } from "react-router-dom";
15
+ import { useSearchParams as v } from "react-router";
16
16
  import { ErrorBoundary as y } from "@burdenoff/fe-libs/shared/components";
17
17
  import { FeatureFlagProvider as b } from "@burdenoff/fe-libs/shared/feature-flags";
18
18
  import { BarChart3 as x, Boxes as S, Database as C, LayoutDashboard as w, Lightbulb as T, Settings as ee } from "lucide-react";
@@ -1 +1 @@
1
- {"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { BigConsoleQuotaProvider } from './providers/BigConsoleQuotaProvider';\nimport { AiPreviewPanel } from './components/preview/AiPreviewPanel';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type GetWorkflowByKeyQueryVariables,\n type CreateWorkflowMutation,\n type CreateWorkflowMutationVariables,\n type ListWorkflowsQuery,\n type ListWorkflowsQueryVariables,\n} from '../generated/wspace-operations';\n\nconst getWorkflowByKeyDocument = GetWorkflowByKeyDocument as unknown as TypedDocumentNode<\n GetWorkflowByKeyQuery,\n GetWorkflowByKeyQueryVariables\n>;\nconst createWorkflowDocument = CreateWorkflowDocument as unknown as TypedDocumentNode<\n CreateWorkflowMutation,\n CreateWorkflowMutationVariables\n>;\nconst listWorkflowsDocument = ListWorkflowsDocument as unknown as TypedDocumentNode<\n ListWorkflowsQuery,\n ListWorkflowsQueryVariables\n>;\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole', navigate: hostNavigate } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: getWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: createWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: listWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n const handleViewBilling = useCallback(() => {\n if (hostNavigate) {\n hostNavigate('/billing');\n return;\n }\n window.location.assign('/billing');\n }, [hostNavigate]);\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <BigConsoleProvider props={props}>\n <BigConsoleQuotaProvider onViewBilling={handleViewBilling}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n {/* Sits ABOVE the routed page, so when the assistant starts building\n you watch it happen on whatever screen you were already on —\n rather than having to guess which list to go and refresh. */}\n <AiPreviewPanel />\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleQuotaProvider>\n </BigConsoleProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAM,IAA2B,GAI3B,IAAyB,GAIzB,IAAwB,GAqBxB,oBAAe,IAAI,KAAa,EA0BzB,KAA2C,MAAU;CAChE,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,eAAe,UAAU,MAAiB,GAG9F,IAAkB,EAAiB,YAAY,EAC/C,IAAe,EAAiB,SAAS,EACzC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAG7D,KAAY,GAAc,OACZ,EAAK,SAAS,IAAI,GAAG,EAAK,MAAM,GAAG,GAAG,GAAG,MACzC,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;AA8GtD,CAzGA,QAAgB;AACT,QAwDL,QADgB,EArDU;GACxB,IAAI;GACJ,OAAO;GACP,MAAM;GACN,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;GAC7B,OAAO;GACP,OAAO;GACP,UAAU;IACR;KACE,IAAI;KACJ,OAAO;KACP,MAAM;KACN,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;KAC7B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO;KACP,MAAM,EAAS,GAAU,aAAa;KACtC,MAAM,kBAAC,GAAD,EAAiB,MAAM,IAAM,CAAA;KACnC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO;KACP,MAAM,EAAS,GAAU,cAAc;KACvC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO;KACP,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;KAC7B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO;KACP,MAAM,EAAS,GAAU,eAAe;KACxC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;KACzB,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO;KACP,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,IAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACF;GACF,CAEkD;IAElD,CAAC,GAAkB,EAAS,CAAC,EAGhC,QAAgB;AACT,QAaL,QADgB,EAVI,CAClB;GACE,IAAI;GACJ,OAAO;GACP,MAAM;GACN,OAAO;GACP,SAAS;GACV,CACF,CAE+C;IAE/C,CAAC,EAAoB,CAAC,EAazB,SACM,KAAmB,MACrB,EAAoB,EAAgB,EACpC,EAAqB,EAAa,EAElC,EAAe,GAAK,SAET;AAEX,EADA,GAAuB,EACvB,GAAwB;KAEzB,CAAC,GAAiB,EAAa,CAAC,EAKnC,QAAgB;AACd,MAAI,EAAa;EACjB,IAAM,IAAQ,iBAAiB;AAC7B,GAAK,KACH,EACE,oGACD;KAEF,IAAO;AACV,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAY,CAAC;CAGjB,IAAM,IAAsB,EAC1B,OAAO,MAAgC;AAGjC,QAAO,cAAc,WAEzB;OAAI,CAAC,GAAiB;AACpB,YAAQ,KAAK,2DAA2D;AACxE;;AAKE,UAAa,IAAI,EAAO,YAAY,EACxC;MAAa,IAAI,EAAO,YAAY;AAEpC,QAAI;AAQF,UANiB,MAAM,EAAgB,MAA6B;MAClE,OAAO;MACP,WAAW,EAAE,KAAK,EAAO,aAAa;MACtC,aAAa;MACd,CAAC,EAEW,MAAM,cACjB;AAIF,WAAM,EAAgB,OAA+B;MACnD,UAAU;MACV,WAAW,EACT,OAAO;OACL,MAAM,EAAO;OACb,KAAK,EAAO;OACZ,aAAa,EAAO;OACpB,oBAAoB,EAAO;OAC5B,EACF;MACD,gBAAgB,CAAC,EAAE,OAAO,GAAuB,CAAC;MACnD,CAAC;aACK,GAAK;AAGZ,aAAQ,KAAK,0DAA0D,EAAI;cACnE;AACR,OAAa,OAAO,EAAO,YAAY;;;;IAG3C,CAAC,EAAgB,CAClB,EAMK,IAAyB,EAAO,EAAoB;AAO1D,CALA,QAAsB;AACpB,IAAuB,UAAU;IAChC,CAAC,EAAoB,CAAC,EAGzB,QAAgB;AACd,MAAI,EAAM,eAAe;GACvB,IAAM,IAAiB,EAAM,cAAc,SAAS,IAAI,GAAG,EAAM,cAAc,MAAM,GAAG,GAAG,GAAG,EAAM;AAUpG,GARA,QAAQ,IAAI,0CAA0C;IACpD,eAAe;IACf,aAAa,EAAM;IACnB,UAAU,EAAM;IAChB,WAAW,EAAM;IACjB,aAAa,EAAM;IACpB,CAAC,EAEF,EAAoB;IAClB,eAAe;IACf,WAAW,EAAM;IACjB,aAAa,EAAM,eAAe;IAClC,UAAU,EAAM;IAGhB,WAAW,EAAM,aAAa;IAC9B,aAAa,EAAM,eAAe;IAElC,kBAAkB,MAAgC,EAAuB,QAAQ,EAAO;IACzF,CAAC;;AAEJ,eAAa;AACX,MAAuB;;IAIxB;EAAC,EAAM;EAAe,EAAM;EAAW,EAAM;EAAa,EAAM;EAAU,EAAM;EAAW,EAAM;EAAY,CAAC;CAGjH,IAAM,CAAC,GAAc,KAAmB,GAAiB,EACnD,IAAc,GAAmB,MAAU,EAAM,OAAO,EACxD,IAAa,GAAmB,MAAU,EAAM,WAAW;AACjE,IAAkB;CAElB,IAAM,KAAuB,QAAkB;AAC7C,KAAY;EAEZ,IAAM,IAAY,IAAI,gBAAgB,EAAa;AAQnD,EAPA,MAAM,KAAK,EAAU,MAAM,CAAC,CAAC,SAAS,MAAQ;AAC5C,IAAI,EAAI,WAAW,UAAU,IAAI,EAAI,WAAW,OAAO,KACrD,EAAU,OAAO,EAAI;IAEvB,EACF,EAAgB,GAAW,EAAE,SAAS,IAAM,CAAC,EAEzC,SAAS,yBAAyB,eACpC,SAAS,cAAc,MAAM;IAE9B;EAAC;EAAY;EAAc;EAAgB,CAAC,EAEzC,KAAoB,QAAkB;AAC1C,MAAI,GAAc;AAChB,KAAa,WAAW;AACxB;;AAEF,SAAO,SAAS,OAAO,WAAW;IACjC,CAAC,EAAa,CAAC,EAMZ,KAA2B,GAC9B,GAAqB,MACpB,kBAAC,GAAD;EAAyC;EAAqB;EAAU,CAAA,EAE1E,EAAE,CACH;AA2BD,QAxBK,IAyBH,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;aAAsC;GAE/C,CAAA;YAGR,kBAAC,GAAD;GAAqB,aAAa;GAAM,SAAQ;GAAS,gBAAgB;aACvE,kBAAC,GAAD;IAA2B;cACzB,kBAAC,GAAD;KAAyB,eAAe;eACtC,kBAAC,GAAD;MAA8B;gBAC5B,kBAAC,GAAD,EAAA,UAAA;OAIE,kBAAC,GAAD,EAAkB,CAAA;OAClB,kBAAC,GAAD,EAAkB,GAAI,GAAS,CAAA;OAC9B,KACC,kBAAC,GAAD;QAAgB,SAAS;QAAsB,iBAAiB;QAA4B,CAAA;OAE1E,EAAA,CAAA;MACH,CAAA;KACG,CAAA;IACP,CAAA;GACD,CAAA;EACR,CAAA,GAjDZ,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAgB,CAAA,EACjE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,QAAQ;IACvC,WAAU;cACX;IAEQ,CAAA,CACL;;EACF,CAAA,GAIR,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,gFAAiF,CAAA;EAC5F,CAAA"}
1
+ {"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { BigConsoleQuotaProvider } from './providers/BigConsoleQuotaProvider';\nimport { AiPreviewPanel } from './components/preview/AiPreviewPanel';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type GetWorkflowByKeyQueryVariables,\n type CreateWorkflowMutation,\n type CreateWorkflowMutationVariables,\n type ListWorkflowsQuery,\n type ListWorkflowsQueryVariables,\n} from '../generated/wspace-operations';\n\nconst getWorkflowByKeyDocument = GetWorkflowByKeyDocument as unknown as TypedDocumentNode<\n GetWorkflowByKeyQuery,\n GetWorkflowByKeyQueryVariables\n>;\nconst createWorkflowDocument = CreateWorkflowDocument as unknown as TypedDocumentNode<\n CreateWorkflowMutation,\n CreateWorkflowMutationVariables\n>;\nconst listWorkflowsDocument = ListWorkflowsDocument as unknown as TypedDocumentNode<\n ListWorkflowsQuery,\n ListWorkflowsQueryVariables\n>;\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole', navigate: hostNavigate } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: getWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: createWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: listWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n const handleViewBilling = useCallback(() => {\n if (hostNavigate) {\n hostNavigate('/billing');\n return;\n }\n window.location.assign('/billing');\n }, [hostNavigate]);\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <BigConsoleProvider props={props}>\n <BigConsoleQuotaProvider onViewBilling={handleViewBilling}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n {/* Sits ABOVE the routed page, so when the assistant starts building\n you watch it happen on whatever screen you were already on —\n rather than having to guess which list to go and refresh. */}\n <AiPreviewPanel />\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleQuotaProvider>\n </BigConsoleProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { GetWorkflowDocument as n } from "../generated/wspace-operations.js";
5
5
  import r from "./hooks/useWorkflowOperations.js";
6
6
  import { BOOTSTRAP_RETRY_DELAY_MS as i } from "./constants.js";
7
7
  import { Suspense as a, lazy as o, memo as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
8
- import { Navigate as f, Route as p, Routes as m, useParams as h } from "react-router-dom";
8
+ import { Navigate as f, Route as p, Routes as m, useParams as h } from "react-router";
9
9
  import { useApolloClient as g } from "@apollo/client/react";
10
10
  import { Spinner as _ } from "@burdenoff/fe-libs/ui";
11
11
  import { jsx as v, jsxs as y } from "react/jsx-runtime";
@@ -1 +1 @@
1
- {"version":3,"file":"BigConsoleRoutes.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, memo, useState, useMemo, useEffect, useRef } from 'react';\nimport { Route, Routes, Navigate, useParams } from 'react-router-dom';\nimport { type BigConsoleRootProps } from './types';\nimport { HomePage } from './pages/HomePage';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAppNavigate } from './contexts';\nimport { useWorkflowOperations } from './hooks/useWorkflowOperations';\nimport { GetWorkflowDocument } from '../generated/wspace-operations';\nimport { BOOTSTRAP_RETRY_DELAY_MS } from './constants';\n\n// Import FluidGrids SDK workflow components (EXACT same as microfe-workflows)\nimport {\n WorkflowBuilderPage as SDKWorkflowBuilderPage,\n ExecutionsListPage,\n ExecutionDetailPage,\n HistoryPage,\n TemplatesPage,\n NodesPage,\n SettingsPage as WorkflowSettingsPage,\n} from '@burdenoff/fluidgrids-fe-sdk';\n\n// Lazy-loaded pages\nconst DashboardsPage = lazy(() => import('./pages/DashboardsPage'));\nconst DashboardViewPage = lazy(() => import('./pages/DashboardViewPage'));\nconst DashboardBuilderPage = lazy(() => import('./pages/DashboardBuilderPage'));\nconst DataSourcesPage = lazy(() => import('./pages/DataSourcesPage'));\nconst InsightsPage = lazy(() => import('./pages/InsightsPage'));\nconst SettingsPage = lazy(() => import('./pages/SettingsPage'));\nconst WidgetDemoPage = lazy(() => import('./pages/WidgetDemoPage'));\n// BigConsole-native workflow pages (query bigconsole-svc, not workflow-svc)\n// These use named exports (no default export), so we remap via .then()\nconst WorkflowsPage = lazy(() => import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage })));\nconst WorkflowViewPage = lazy(() => import('./pages/WorkflowViewPage').then((m) => ({ default: m.WorkflowViewPage })));\n// DataSink Pages\nconst DataSinksPage = lazy(() => import('./pages/DataSinksPage'));\nconst DataSinkViewPage = lazy(() => import('./pages/DataSinkViewPage'));\nconst DataSinkBuilderPage = lazy(() => import('./pages/DataSinkBuilderPage'));\n// Parser Pages\nconst DataParsersPage = lazy(() => import('./pages/DataParsersPage'));\nconst ParserViewPage = lazy(() => import('./pages/ParserViewPage'));\nconst ParserBuilderPage = lazy(() => import('./pages/ParserBuilderPage'));\n// Pipeline Pages\nconst PipelinesPage = lazy(() => import('./pages/PipelinesPage'));\nconst PipelineViewPage = lazy(() => import('./pages/PipelineViewPage'));\nconst PipelineBuilderPage = lazy(() => import('./pages/PipelineBuilderPage'));\n\n// Loading fallback component\nfunction PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n}\n\n/** Generate a new workflow builder path with a fresh UUID key. */\nexport const newWorkflowPath = () => `/workflows/workflow/${crypto.randomUUID()}/v1`;\n\n/**\n * Redirect /workflows/new to a UUID-based URL.\n * Uses <Navigate> for synchronous redirect (no blank frame).\n */\nfunction WorkflowNewRedirect() {\n const path = useMemo(() => newWorkflowPath(), []);\n return <Navigate to={path} replace />;\n}\n\n/**\n * Bridge component: resolves a BigConsole workflow ID to its externalWorkflowId\n * and navigates to the SDK's visual builder route.\n *\n * Route: /workflows/:workflowId/edit\n * Redirects to: /workflows/workflow/{workflowKey}/v1\n */\nconst WorkflowEditBridge = memo(function WorkflowEditBridge() {\n const { workflowId } = useParams<{ workflowId: string }>();\n const navigate = useAppNavigate();\n const { fetchWorkflow } = useWorkflowOperations({ skipFetch: true });\n const [error, setError] = useState<string | null>(null);\n const retryCount = useRef(0);\n\n useEffect(() => {\n if (!workflowId) {\n setError('No workflow ID provided');\n return;\n }\n\n // Reset retry budget and error when workflowId changes so a\n // previous failed attempt doesn't consume the new workflow's retry.\n retryCount.current = 0;\n setError(null);\n let cancelled = false;\n\n const handleResult = (workflow: Awaited<ReturnType<typeof fetchWorkflow>>) => {\n if (cancelled) return;\n\n if (workflow?.key) {\n // Navigate to the SDK builder route using the workflow key (e.g. \"github_user_profile\")\n // and default version \"v1\". The FluidGrids SDK fetches the definition by workflowKey.\n // Replace history so the bridge doesn't trap the back button.\n navigate(`/workflows/workflow/${workflow.key}/v1`, { replace: true });\n } else if (workflow) {\n // Workflow exists but has no key — open SDK builder in create mode\n navigate(`/workflows/builder`, { replace: true });\n } else if (retryCount.current < 1) {\n // On full-page loads the auth/workspace context may not be bootstrapped yet,\n // causing a false null. Retry once after a short delay (same pattern as WorkflowViewPage).\n retryCount.current += 1;\n setTimeout(() => {\n if (!cancelled) {\n fetchWorkflow(workflowId)\n .then(handleResult)\n .catch((err) => {\n if (!cancelled) setError(err?.message ?? 'Failed to load workflow');\n });\n }\n }, BOOTSTRAP_RETRY_DELAY_MS);\n } else {\n setError('Workflow not found');\n }\n };\n\n fetchWorkflow(workflowId)\n .then(handleResult)\n .catch((err) => {\n if (!cancelled) setError(err?.message ?? 'Failed to load workflow');\n });\n\n return () => {\n cancelled = true;\n };\n }, [workflowId, fetchWorkflow, navigate]);\n\n if (error) {\n return (\n <div className=\"p-6 flex flex-col items-center justify-center min-h-[400px] text-center gap-4\">\n <p className=\"text-sm text-text-secondary\">{error}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/workflows')}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken hover:bg-border-default rounded-md transition-colors\"\n >\n Back to Workflows\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n});\n\n/**\n * Matches a canonical v4-style UUID. BigConsole workflow RECORD ids are UUIDs;\n * workflow KEYS are human slugs (e.g. \"users_analytics\"). New-workflow URLs also\n * use a fresh `crypto.randomUUID()` as the builder key. We only attempt record\n * resolution for UUID-shaped `:id`s — slugs are always real keys and skip the\n * lookup entirely (no extra round-trip, no spinner flash).\n */\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/** Minimal shape of the GetWorkflow query — the resolver only needs the key. */\ntype WorkflowKeyLookup = { workflow: { key: string | null } | null };\n\n/**\n * Guards the SDK visual builder route.\n *\n * The SDK builder loads its definition from workflow-svc using the URL `:id` as\n * the `workflowKey`. That only works when `:id` is the workflow KEY. When a\n * BigConsole workflow RECORD id (a UUID) reaches the builder — via the SDK\n * card's `navigate(\"../workflow/\" + workflow.workflow_key + ...)`, a bookmarked\n * record-id URL, etc. — no definition matches that key, so the canvas is empty\n * AND the SDK silently creates a phantom 0-node definition keyed by the UUID.\n *\n * This resolver closes that gap centrally (the same id→key resolution the\n * `/workflows/:workflowId/edit` `WorkflowEditBridge` does, but applied to the\n * builder route itself so EVERY entry point is covered):\n * - `:id` is a slug → it's already a key → render the builder as-is.\n * - `:id` is a UUID that maps to a BigConsole record with a different key →\n * redirect to `/workflows/workflow/<key>/<version>`.\n * - `:id` is a UUID that is NOT a record (a fresh new-workflow id) → render\n * the builder as-is (create mode).\n */\nconst WorkflowBuilderResolver = memo(function WorkflowBuilderResolver() {\n const { id, version } = useParams<{ id: string; version: string }>();\n const navigate = useAppNavigate();\n const client = useApolloClient();\n // Slugs (real keys) render the builder immediately (derived below); only\n // UUID-shaped ids need a record lookup first.\n const isUuid = !!id && UUID_RE.test(id);\n // A UUID is only safe to open in the SDK builder once the lookup POSITIVELY\n // confirms it is not a BigConsole record (a clean not-found — e.g. a fresh\n // new-workflow id). Both verdicts are keyed by the id they describe so that\n // navigating between workflows re-resolves instead of reusing a stale answer.\n const [confirmedAbsentId, setConfirmedAbsentId] = useState<string | null>(null);\n const [failedId, setFailedId] = useState<string | null>(null);\n // Bumped by the \"Try again\" button to re-run the lookup effect after a failure.\n const [retryNonce, setRetryNonce] = useState(0);\n const retryCount = useRef(0);\n\n useEffect(() => {\n if (!id || !isUuid) return;\n\n retryCount.current = 0;\n let cancelled = false;\n const targetVersion = version ?? 'v1';\n\n const attempt = () => {\n // Query directly (not via fetchWorkflow, which swallows Apollo errors and\n // returns null) so we can tell a genuine \"no such record\" apart from a\n // transient lookup failure. Treating a failure as not-found would re-open\n // the builder with the record id and recreate a phantom 0-node definition.\n client\n .query<WorkflowKeyLookup>({\n query: GetWorkflowDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n })\n .then((result) => {\n if (cancelled) return;\n const record = result.data?.workflow;\n if (record) {\n // The UUID IS a BigConsole record.\n if (record.key && record.key !== id) {\n // Open the builder under its real key (where the definition + nodes\n // live). encodeURIComponent guards against URL-unsafe key chars.\n // Replace history so the resolver doesn't trap the back button.\n navigate(`/workflows/workflow/${encodeURIComponent(record.key)}/${targetVersion}`, {\n replace: true,\n });\n } else {\n // Record exists but has no usable key — open a blank builder rather\n // than reusing the record UUID as the key (which would phantom).\n navigate('/workflows/builder', { replace: true });\n }\n } else {\n // Query succeeded with no record → a genuine new-workflow id.\n setConfirmedAbsentId(id);\n }\n })\n .catch(() => {\n if (cancelled) return;\n if (retryCount.current < 1) {\n // On a cold full-page load the auth/workspace context may not be\n // bootstrapped yet. Retry once before giving up.\n retryCount.current += 1;\n setTimeout(() => {\n if (!cancelled) attempt();\n }, BOOTSTRAP_RETRY_DELAY_MS);\n } else {\n // Lookup failed — do NOT assume create mode (the id may be a record);\n // surface a retry instead of risking a phantom definition.\n setFailedId(id);\n }\n });\n };\n\n attempt();\n\n return () => {\n cancelled = true;\n };\n }, [id, isUuid, version, client, navigate, retryNonce]);\n\n // Slug keys, and UUIDs confirmed not to be records, render the builder.\n if (!isUuid || confirmedAbsentId === id) {\n return <SDKWorkflowBuilderPage />;\n }\n\n // A lookup that ultimately failed: surface a retry rather than silently\n // opening the builder with what may be a record id.\n if (failedId === id) {\n return (\n <div className=\"p-6 flex flex-col items-center justify-center min-h-[400px] text-center gap-4\">\n <p className=\"text-sm text-text-secondary\">Couldn&apos;t load this workflow. Please try again.</p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setFailedId(null);\n setRetryNonce((n) => n + 1);\n }}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md transition-colors\"\n >\n Try again\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/workflows')}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken hover:bg-border-default rounded-md transition-colors\"\n >\n Back to Workflows\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n});\n\nexport function BigConsoleRoutes(_props: BigConsoleRootProps) {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route path=\"/\" element={<HomePage />} />\n <Route path=\"/dashboards\" element={<DashboardsPage />} />\n <Route path=\"/dashboards/:dashboardId\" element={<DashboardViewPage />} />\n <Route path=\"/dashboards/:dashboardId/builder\" element={<DashboardBuilderPage />} />\n <Route path=\"/datasources\" element={<DataSourcesPage />} />\n <Route path=\"/insights\" element={<InsightsPage />} />\n <Route path=\"/settings\" element={<SettingsPage />} />\n <Route path=\"/widgets/demo\" element={<WidgetDemoPage />} />\n {/* DataSink Routes */}\n <Route path=\"/datasinks\" element={<DataSinksPage />} />\n <Route path=\"/datasinks/new\" element={<DataSinkBuilderPage />} />\n <Route path=\"/datasinks/:dataSinkId\" element={<DataSinkViewPage />} />\n <Route path=\"/datasinks/:dataSinkId/edit\" element={<DataSinkBuilderPage />} />\n {/* Parser Routes */}\n <Route path=\"/parsers\" element={<DataParsersPage />} />\n <Route path=\"/parsers/new\" element={<ParserBuilderPage />} />\n <Route path=\"/parsers/:parserId\" element={<ParserViewPage />} />\n <Route path=\"/parsers/:parserId/edit\" element={<ParserBuilderPage />} />\n {/* Dashboard-scoped Parser Routes */}\n <Route path=\"/dashboards/:consoleId/parsers\" element={<DataParsersPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/new\" element={<ParserBuilderPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/:parserId\" element={<ParserViewPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/:parserId/edit\" element={<ParserBuilderPage />} />\n {/* Workflow Routes — list/view use BigConsole-native pages (query bigconsole-svc).\n All static routes are grouped before the dynamic :workflowId catch-all.\n React Router v6 prioritizes static segments regardless of declaration order,\n but keeping them above the param routes makes intent unambiguous for maintainers. */}\n <Route path=\"/workflows\" element={<WorkflowsPage />} />\n <Route path=\"/workflows/list\" element={<WorkflowsPage />} />\n {/* New workflow — redirect to UUID-based URL (matches FluidGrids pattern) */}\n <Route path=\"/workflows/new\" element={<WorkflowNewRedirect />} />\n {/* SDK visual workflow builder — design existing workflows */}\n <Route path=\"/workflows/builder\" element={<SDKWorkflowBuilderPage />} />\n {/* All parameterised builder routes go through WorkflowBuilderResolver so a\n record-id `:id` is resolved to its workflow key first (slugs/new-workflow\n UUIDs pass straight through); otherwise the SDK opens an empty canvas and\n creates a phantom 0-node definition keyed by the record id. */}\n <Route path=\"/workflows/builder/:id/:version\" element={<WorkflowBuilderResolver />} />\n <Route path=\"/workflows/workflow/:id/:version\" element={<WorkflowBuilderResolver />} />\n {/* SDK sub-pages (runs, history, templates, nodes, settings) */}\n <Route path=\"/workflows/runs\" element={<ExecutionsListPage />} />\n <Route path=\"/workflows/runs/:runId\" element={<ExecutionDetailPage />} />\n <Route path=\"/workflows/history\" element={<HistoryPage />} />\n <Route path=\"/workflows/templates\" element={<TemplatesPage />} />\n <Route path=\"/workflows/nodes\" element={<NodesPage />} />\n <Route path=\"/workflows/settings\" element={<WorkflowSettingsPage />} />\n {/* Dynamic routes — :workflowId catch-all AFTER all static segments */}\n <Route path=\"/workflows/:workflowId\" element={<WorkflowViewPage />} />\n <Route path=\"/workflows/:workflowId/edit\" element={<WorkflowEditBridge />} />\n {/* Support SDK's navigation pattern (without /workflows/ prefix) — also\n resolves record-id → key via WorkflowBuilderResolver. */}\n <Route path=\"/workflow/:id/:version\" element={<WorkflowBuilderResolver />} />\n {/* Redirect /list to /workflows/list (SDK uses relative navigation) */}\n <Route path=\"/list\" element={<Navigate to=\"/bigconsole/workflows/list\" replace />} />\n {/* Pipeline Routes */}\n <Route path=\"/pipelines\" element={<PipelinesPage />} />\n <Route path=\"/pipelines/new\" element={<PipelineBuilderPage />} />\n <Route path=\"/pipelines/:pipelineId\" element={<PipelineViewPage />} />\n <Route path=\"/pipelines/:pipelineId/edit\" element={<PipelineBuilderPage />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BigConsoleRoutes.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, memo, useState, useMemo, useEffect, useRef } from 'react';\nimport { Route, Routes, Navigate, useParams } from 'react-router';\nimport { type BigConsoleRootProps } from './types';\nimport { HomePage } from './pages/HomePage';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAppNavigate } from './contexts';\nimport { useWorkflowOperations } from './hooks/useWorkflowOperations';\nimport { GetWorkflowDocument } from '../generated/wspace-operations';\nimport { BOOTSTRAP_RETRY_DELAY_MS } from './constants';\n\n// Import FluidGrids SDK workflow components (EXACT same as microfe-workflows)\nimport {\n WorkflowBuilderPage as SDKWorkflowBuilderPage,\n ExecutionsListPage,\n ExecutionDetailPage,\n HistoryPage,\n TemplatesPage,\n NodesPage,\n SettingsPage as WorkflowSettingsPage,\n} from '@burdenoff/fluidgrids-fe-sdk';\n\n// Lazy-loaded pages\nconst DashboardsPage = lazy(() => import('./pages/DashboardsPage'));\nconst DashboardViewPage = lazy(() => import('./pages/DashboardViewPage'));\nconst DashboardBuilderPage = lazy(() => import('./pages/DashboardBuilderPage'));\nconst DataSourcesPage = lazy(() => import('./pages/DataSourcesPage'));\nconst InsightsPage = lazy(() => import('./pages/InsightsPage'));\nconst SettingsPage = lazy(() => import('./pages/SettingsPage'));\nconst WidgetDemoPage = lazy(() => import('./pages/WidgetDemoPage'));\n// BigConsole-native workflow pages (query bigconsole-svc, not workflow-svc)\n// These use named exports (no default export), so we remap via .then()\nconst WorkflowsPage = lazy(() => import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage })));\nconst WorkflowViewPage = lazy(() => import('./pages/WorkflowViewPage').then((m) => ({ default: m.WorkflowViewPage })));\n// DataSink Pages\nconst DataSinksPage = lazy(() => import('./pages/DataSinksPage'));\nconst DataSinkViewPage = lazy(() => import('./pages/DataSinkViewPage'));\nconst DataSinkBuilderPage = lazy(() => import('./pages/DataSinkBuilderPage'));\n// Parser Pages\nconst DataParsersPage = lazy(() => import('./pages/DataParsersPage'));\nconst ParserViewPage = lazy(() => import('./pages/ParserViewPage'));\nconst ParserBuilderPage = lazy(() => import('./pages/ParserBuilderPage'));\n// Pipeline Pages\nconst PipelinesPage = lazy(() => import('./pages/PipelinesPage'));\nconst PipelineViewPage = lazy(() => import('./pages/PipelineViewPage'));\nconst PipelineBuilderPage = lazy(() => import('./pages/PipelineBuilderPage'));\n\n// Loading fallback component\nfunction PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n}\n\n/** Generate a new workflow builder path with a fresh UUID key. */\nexport const newWorkflowPath = () => `/workflows/workflow/${crypto.randomUUID()}/v1`;\n\n/**\n * Redirect /workflows/new to a UUID-based URL.\n * Uses <Navigate> for synchronous redirect (no blank frame).\n */\nfunction WorkflowNewRedirect() {\n const path = useMemo(() => newWorkflowPath(), []);\n return <Navigate to={path} replace />;\n}\n\n/**\n * Bridge component: resolves a BigConsole workflow ID to its externalWorkflowId\n * and navigates to the SDK's visual builder route.\n *\n * Route: /workflows/:workflowId/edit\n * Redirects to: /workflows/workflow/{workflowKey}/v1\n */\nconst WorkflowEditBridge = memo(function WorkflowEditBridge() {\n const { workflowId } = useParams<{ workflowId: string }>();\n const navigate = useAppNavigate();\n const { fetchWorkflow } = useWorkflowOperations({ skipFetch: true });\n const [error, setError] = useState<string | null>(null);\n const retryCount = useRef(0);\n\n useEffect(() => {\n if (!workflowId) {\n setError('No workflow ID provided');\n return;\n }\n\n // Reset retry budget and error when workflowId changes so a\n // previous failed attempt doesn't consume the new workflow's retry.\n retryCount.current = 0;\n setError(null);\n let cancelled = false;\n\n const handleResult = (workflow: Awaited<ReturnType<typeof fetchWorkflow>>) => {\n if (cancelled) return;\n\n if (workflow?.key) {\n // Navigate to the SDK builder route using the workflow key (e.g. \"github_user_profile\")\n // and default version \"v1\". The FluidGrids SDK fetches the definition by workflowKey.\n // Replace history so the bridge doesn't trap the back button.\n navigate(`/workflows/workflow/${workflow.key}/v1`, { replace: true });\n } else if (workflow) {\n // Workflow exists but has no key — open SDK builder in create mode\n navigate(`/workflows/builder`, { replace: true });\n } else if (retryCount.current < 1) {\n // On full-page loads the auth/workspace context may not be bootstrapped yet,\n // causing a false null. Retry once after a short delay (same pattern as WorkflowViewPage).\n retryCount.current += 1;\n setTimeout(() => {\n if (!cancelled) {\n fetchWorkflow(workflowId)\n .then(handleResult)\n .catch((err) => {\n if (!cancelled) setError(err?.message ?? 'Failed to load workflow');\n });\n }\n }, BOOTSTRAP_RETRY_DELAY_MS);\n } else {\n setError('Workflow not found');\n }\n };\n\n fetchWorkflow(workflowId)\n .then(handleResult)\n .catch((err) => {\n if (!cancelled) setError(err?.message ?? 'Failed to load workflow');\n });\n\n return () => {\n cancelled = true;\n };\n }, [workflowId, fetchWorkflow, navigate]);\n\n if (error) {\n return (\n <div className=\"p-6 flex flex-col items-center justify-center min-h-[400px] text-center gap-4\">\n <p className=\"text-sm text-text-secondary\">{error}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/workflows')}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken hover:bg-border-default rounded-md transition-colors\"\n >\n Back to Workflows\n </button>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n});\n\n/**\n * Matches a canonical v4-style UUID. BigConsole workflow RECORD ids are UUIDs;\n * workflow KEYS are human slugs (e.g. \"users_analytics\"). New-workflow URLs also\n * use a fresh `crypto.randomUUID()` as the builder key. We only attempt record\n * resolution for UUID-shaped `:id`s — slugs are always real keys and skip the\n * lookup entirely (no extra round-trip, no spinner flash).\n */\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/** Minimal shape of the GetWorkflow query — the resolver only needs the key. */\ntype WorkflowKeyLookup = { workflow: { key: string | null } | null };\n\n/**\n * Guards the SDK visual builder route.\n *\n * The SDK builder loads its definition from workflow-svc using the URL `:id` as\n * the `workflowKey`. That only works when `:id` is the workflow KEY. When a\n * BigConsole workflow RECORD id (a UUID) reaches the builder — via the SDK\n * card's `navigate(\"../workflow/\" + workflow.workflow_key + ...)`, a bookmarked\n * record-id URL, etc. — no definition matches that key, so the canvas is empty\n * AND the SDK silently creates a phantom 0-node definition keyed by the UUID.\n *\n * This resolver closes that gap centrally (the same id→key resolution the\n * `/workflows/:workflowId/edit` `WorkflowEditBridge` does, but applied to the\n * builder route itself so EVERY entry point is covered):\n * - `:id` is a slug → it's already a key → render the builder as-is.\n * - `:id` is a UUID that maps to a BigConsole record with a different key →\n * redirect to `/workflows/workflow/<key>/<version>`.\n * - `:id` is a UUID that is NOT a record (a fresh new-workflow id) → render\n * the builder as-is (create mode).\n */\nconst WorkflowBuilderResolver = memo(function WorkflowBuilderResolver() {\n const { id, version } = useParams<{ id: string; version: string }>();\n const navigate = useAppNavigate();\n const client = useApolloClient();\n // Slugs (real keys) render the builder immediately (derived below); only\n // UUID-shaped ids need a record lookup first.\n const isUuid = !!id && UUID_RE.test(id);\n // A UUID is only safe to open in the SDK builder once the lookup POSITIVELY\n // confirms it is not a BigConsole record (a clean not-found — e.g. a fresh\n // new-workflow id). Both verdicts are keyed by the id they describe so that\n // navigating between workflows re-resolves instead of reusing a stale answer.\n const [confirmedAbsentId, setConfirmedAbsentId] = useState<string | null>(null);\n const [failedId, setFailedId] = useState<string | null>(null);\n // Bumped by the \"Try again\" button to re-run the lookup effect after a failure.\n const [retryNonce, setRetryNonce] = useState(0);\n const retryCount = useRef(0);\n\n useEffect(() => {\n if (!id || !isUuid) return;\n\n retryCount.current = 0;\n let cancelled = false;\n const targetVersion = version ?? 'v1';\n\n const attempt = () => {\n // Query directly (not via fetchWorkflow, which swallows Apollo errors and\n // returns null) so we can tell a genuine \"no such record\" apart from a\n // transient lookup failure. Treating a failure as not-found would re-open\n // the builder with the record id and recreate a phantom 0-node definition.\n client\n .query<WorkflowKeyLookup>({\n query: GetWorkflowDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n })\n .then((result) => {\n if (cancelled) return;\n const record = result.data?.workflow;\n if (record) {\n // The UUID IS a BigConsole record.\n if (record.key && record.key !== id) {\n // Open the builder under its real key (where the definition + nodes\n // live). encodeURIComponent guards against URL-unsafe key chars.\n // Replace history so the resolver doesn't trap the back button.\n navigate(`/workflows/workflow/${encodeURIComponent(record.key)}/${targetVersion}`, {\n replace: true,\n });\n } else {\n // Record exists but has no usable key — open a blank builder rather\n // than reusing the record UUID as the key (which would phantom).\n navigate('/workflows/builder', { replace: true });\n }\n } else {\n // Query succeeded with no record → a genuine new-workflow id.\n setConfirmedAbsentId(id);\n }\n })\n .catch(() => {\n if (cancelled) return;\n if (retryCount.current < 1) {\n // On a cold full-page load the auth/workspace context may not be\n // bootstrapped yet. Retry once before giving up.\n retryCount.current += 1;\n setTimeout(() => {\n if (!cancelled) attempt();\n }, BOOTSTRAP_RETRY_DELAY_MS);\n } else {\n // Lookup failed — do NOT assume create mode (the id may be a record);\n // surface a retry instead of risking a phantom definition.\n setFailedId(id);\n }\n });\n };\n\n attempt();\n\n return () => {\n cancelled = true;\n };\n }, [id, isUuid, version, client, navigate, retryNonce]);\n\n // Slug keys, and UUIDs confirmed not to be records, render the builder.\n if (!isUuid || confirmedAbsentId === id) {\n return <SDKWorkflowBuilderPage />;\n }\n\n // A lookup that ultimately failed: surface a retry rather than silently\n // opening the builder with what may be a record id.\n if (failedId === id) {\n return (\n <div className=\"p-6 flex flex-col items-center justify-center min-h-[400px] text-center gap-4\">\n <p className=\"text-sm text-text-secondary\">Couldn&apos;t load this workflow. Please try again.</p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setFailedId(null);\n setRetryNonce((n) => n + 1);\n }}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md transition-colors\"\n >\n Try again\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/workflows')}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-sunken hover:bg-border-default rounded-md transition-colors\"\n >\n Back to Workflows\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n});\n\nexport function BigConsoleRoutes(_props: BigConsoleRootProps) {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route path=\"/\" element={<HomePage />} />\n <Route path=\"/dashboards\" element={<DashboardsPage />} />\n <Route path=\"/dashboards/:dashboardId\" element={<DashboardViewPage />} />\n <Route path=\"/dashboards/:dashboardId/builder\" element={<DashboardBuilderPage />} />\n <Route path=\"/datasources\" element={<DataSourcesPage />} />\n <Route path=\"/insights\" element={<InsightsPage />} />\n <Route path=\"/settings\" element={<SettingsPage />} />\n <Route path=\"/widgets/demo\" element={<WidgetDemoPage />} />\n {/* DataSink Routes */}\n <Route path=\"/datasinks\" element={<DataSinksPage />} />\n <Route path=\"/datasinks/new\" element={<DataSinkBuilderPage />} />\n <Route path=\"/datasinks/:dataSinkId\" element={<DataSinkViewPage />} />\n <Route path=\"/datasinks/:dataSinkId/edit\" element={<DataSinkBuilderPage />} />\n {/* Parser Routes */}\n <Route path=\"/parsers\" element={<DataParsersPage />} />\n <Route path=\"/parsers/new\" element={<ParserBuilderPage />} />\n <Route path=\"/parsers/:parserId\" element={<ParserViewPage />} />\n <Route path=\"/parsers/:parserId/edit\" element={<ParserBuilderPage />} />\n {/* Dashboard-scoped Parser Routes */}\n <Route path=\"/dashboards/:consoleId/parsers\" element={<DataParsersPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/new\" element={<ParserBuilderPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/:parserId\" element={<ParserViewPage />} />\n <Route path=\"/dashboards/:consoleId/parsers/:parserId/edit\" element={<ParserBuilderPage />} />\n {/* Workflow Routes — list/view use BigConsole-native pages (query bigconsole-svc).\n All static routes are grouped before the dynamic :workflowId catch-all.\n React Router v6 prioritizes static segments regardless of declaration order,\n but keeping them above the param routes makes intent unambiguous for maintainers. */}\n <Route path=\"/workflows\" element={<WorkflowsPage />} />\n <Route path=\"/workflows/list\" element={<WorkflowsPage />} />\n {/* New workflow — redirect to UUID-based URL (matches FluidGrids pattern) */}\n <Route path=\"/workflows/new\" element={<WorkflowNewRedirect />} />\n {/* SDK visual workflow builder — design existing workflows */}\n <Route path=\"/workflows/builder\" element={<SDKWorkflowBuilderPage />} />\n {/* All parameterised builder routes go through WorkflowBuilderResolver so a\n record-id `:id` is resolved to its workflow key first (slugs/new-workflow\n UUIDs pass straight through); otherwise the SDK opens an empty canvas and\n creates a phantom 0-node definition keyed by the record id. */}\n <Route path=\"/workflows/builder/:id/:version\" element={<WorkflowBuilderResolver />} />\n <Route path=\"/workflows/workflow/:id/:version\" element={<WorkflowBuilderResolver />} />\n {/* SDK sub-pages (runs, history, templates, nodes, settings) */}\n <Route path=\"/workflows/runs\" element={<ExecutionsListPage />} />\n <Route path=\"/workflows/runs/:runId\" element={<ExecutionDetailPage />} />\n <Route path=\"/workflows/history\" element={<HistoryPage />} />\n <Route path=\"/workflows/templates\" element={<TemplatesPage />} />\n <Route path=\"/workflows/nodes\" element={<NodesPage />} />\n <Route path=\"/workflows/settings\" element={<WorkflowSettingsPage />} />\n {/* Dynamic routes — :workflowId catch-all AFTER all static segments */}\n <Route path=\"/workflows/:workflowId\" element={<WorkflowViewPage />} />\n <Route path=\"/workflows/:workflowId/edit\" element={<WorkflowEditBridge />} />\n {/* Support SDK's navigation pattern (without /workflows/ prefix) — also\n resolves record-id → key via WorkflowBuilderResolver. */}\n <Route path=\"/workflow/:id/:version\" element={<WorkflowBuilderResolver />} />\n {/* Redirect /list to /workflows/list (SDK uses relative navigation) */}\n <Route path=\"/list\" element={<Navigate to=\"/bigconsole/workflows/list\" replace />} />\n {/* Pipeline Routes */}\n <Route path=\"/pipelines\" element={<PipelinesPage />} />\n <Route path=\"/pipelines/new\" element={<PipelineBuilderPage />} />\n <Route path=\"/pipelines/:pipelineId\" element={<PipelineViewPage />} />\n <Route path=\"/pipelines/:pipelineId/edit\" element={<PipelineBuilderPage />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { useAppNavigate as e } from "../../contexts/NavigationContext.js";
2
2
  import "../../contexts/index.js";
3
3
  import t from "../../hooks/useParserOperations.js";
4
4
  import { memo as n, useCallback as r, useEffect as i, useMemo as a } from "react";
5
- import { useParams as o } from "react-router-dom";
5
+ import { useParams as o } from "react-router";
6
6
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
7
  //#region src/bigconsole/components/datasink/LinkedParsersPanel.tsx
8
8
  var l = ({ className: e }) => /* @__PURE__ */ s("svg", {
@@ -1 +1 @@
1
- {"version":3,"file":"LinkedParsersPanel.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/LinkedParsersPanel.tsx"],"sourcesContent":["/**\n * LinkedParsersPanel Component\n *\n * Shows parsers that use this DataSink as their source.\n * Provides navigation to parser view pages.\n */\n\nimport { memo, useMemo, useCallback, useEffect } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../../contexts';\nimport { useParserOperations } from '../../hooks/useParserOperations';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface LinkedParsersPanelProps {\n dataSinkKey?: string;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst CodeIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\" />\n </svg>\n);\n\nconst ArrowRightIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n);\n\n// ============================================================================\n// PARSER TYPE BADGE\n// ============================================================================\n\nconst ParserTypeBadge = ({ type }: { type: string }) => {\n const config: Record<string, { label: string; className: string }> = {\n jq: { label: 'JQ', className: 'bg-purple-100 text-purple-700' },\n jsonata: { label: 'JSONata', className: 'bg-blue-100 text-blue-700' },\n };\n\n const { label, className } = config[type] || {\n label: type.toUpperCase(),\n className: 'bg-gray-100 text-gray-700',\n };\n\n return <span className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${className}`}>{label}</span>;\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const LinkedParsersPanel = memo(function LinkedParsersPanel({ dataSinkKey }: LinkedParsersPanelProps) {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Operations\n const { parsers, loading: isLoading, fetchParsers } = useParserOperations({ consoleId, skipFetch: true });\n\n // Fetch parsers when dataSinkKey is available (consoleId is optional)\n useEffect(() => {\n if (dataSinkKey) {\n fetchParsers();\n }\n }, [dataSinkKey, fetchParsers]);\n\n // Filter parsers that use this dataSink as input - using useMemo to avoid setState in effect\n const linkedParsers = useMemo(() => {\n if (!dataSinkKey) return [];\n return parsers.filter((p) => p.inputSinkKey === dataSinkKey);\n }, [parsers, dataSinkKey]);\n\n // Handle view parser — navigate to the parser detail page\n const handleViewParser = useCallback(\n (parserId: string) => {\n if (consoleId) {\n navigate(`/dashboards/${consoleId}/parsers/${parserId}`);\n } else {\n navigate(`/parsers/${parserId}`);\n }\n },\n [navigate, consoleId]\n );\n\n // Don't render if no dataSinkKey provided\n if (!dataSinkKey) {\n return null;\n }\n\n return (\n <div className=\"border border-border-default rounded-lg bg-bg-surface p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <CodeIcon className=\"w-4 h-4 text-text-secondary\" />\n <h2 className=\"text-sm font-semibold text-text-primary\">Parsers Using This DataSink</h2>\n </div>\n <span className=\"text-xs text-text-tertiary\">\n {linkedParsers.length} parser{linkedParsers.length !== 1 ? 's' : ''}\n </span>\n </div>\n\n {isLoading ? (\n <div className=\"flex items-center justify-center py-4\">\n <svg className=\"w-5 h-5 text-text-secondary animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n </div>\n ) : linkedParsers.length === 0 ? (\n <p className=\"text-sm text-text-tertiary py-2\">No parsers are using this DataSink as their source.</p>\n ) : (\n <div className=\"space-y-2\">\n {linkedParsers.map((parser) => (\n <button\n key={parser.id}\n type=\"button\"\n onClick={() => handleViewParser(parser.id)}\n className=\"w-full flex items-center justify-between px-3 py-2 bg-bg-sunken rounded-md hover:bg-border-default transition-colors group\"\n >\n <div className=\"flex items-center gap-3\">\n <CodeIcon className=\"w-4 h-4 text-text-secondary\" />\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">{parser.name}</p>\n <p className=\"text-xs text-text-tertiary font-mono\">{parser.outputKey}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <ParserTypeBadge type={parser.type} />\n <ArrowRightIcon className=\"w-4 h-4 text-text-tertiary group-hover:text-text-primary transition-colors\" />\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n );\n});\n\nexport default LinkedParsersPanel;\n"],"mappings":"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"}
1
+ {"version":3,"file":"LinkedParsersPanel.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/LinkedParsersPanel.tsx"],"sourcesContent":["/**\n * LinkedParsersPanel Component\n *\n * Shows parsers that use this DataSink as their source.\n * Provides navigation to parser view pages.\n */\n\nimport { memo, useMemo, useCallback, useEffect } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../../contexts';\nimport { useParserOperations } from '../../hooks/useParserOperations';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface LinkedParsersPanelProps {\n dataSinkKey?: string;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst CodeIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\" />\n </svg>\n);\n\nconst ArrowRightIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n);\n\n// ============================================================================\n// PARSER TYPE BADGE\n// ============================================================================\n\nconst ParserTypeBadge = ({ type }: { type: string }) => {\n const config: Record<string, { label: string; className: string }> = {\n jq: { label: 'JQ', className: 'bg-purple-100 text-purple-700' },\n jsonata: { label: 'JSONata', className: 'bg-blue-100 text-blue-700' },\n };\n\n const { label, className } = config[type] || {\n label: type.toUpperCase(),\n className: 'bg-gray-100 text-gray-700',\n };\n\n return <span className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${className}`}>{label}</span>;\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const LinkedParsersPanel = memo(function LinkedParsersPanel({ dataSinkKey }: LinkedParsersPanelProps) {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Operations\n const { parsers, loading: isLoading, fetchParsers } = useParserOperations({ consoleId, skipFetch: true });\n\n // Fetch parsers when dataSinkKey is available (consoleId is optional)\n useEffect(() => {\n if (dataSinkKey) {\n fetchParsers();\n }\n }, [dataSinkKey, fetchParsers]);\n\n // Filter parsers that use this dataSink as input - using useMemo to avoid setState in effect\n const linkedParsers = useMemo(() => {\n if (!dataSinkKey) return [];\n return parsers.filter((p) => p.inputSinkKey === dataSinkKey);\n }, [parsers, dataSinkKey]);\n\n // Handle view parser — navigate to the parser detail page\n const handleViewParser = useCallback(\n (parserId: string) => {\n if (consoleId) {\n navigate(`/dashboards/${consoleId}/parsers/${parserId}`);\n } else {\n navigate(`/parsers/${parserId}`);\n }\n },\n [navigate, consoleId]\n );\n\n // Don't render if no dataSinkKey provided\n if (!dataSinkKey) {\n return null;\n }\n\n return (\n <div className=\"border border-border-default rounded-lg bg-bg-surface p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <CodeIcon className=\"w-4 h-4 text-text-secondary\" />\n <h2 className=\"text-sm font-semibold text-text-primary\">Parsers Using This DataSink</h2>\n </div>\n <span className=\"text-xs text-text-tertiary\">\n {linkedParsers.length} parser{linkedParsers.length !== 1 ? 's' : ''}\n </span>\n </div>\n\n {isLoading ? (\n <div className=\"flex items-center justify-center py-4\">\n <svg className=\"w-5 h-5 text-text-secondary animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n </div>\n ) : linkedParsers.length === 0 ? (\n <p className=\"text-sm text-text-tertiary py-2\">No parsers are using this DataSink as their source.</p>\n ) : (\n <div className=\"space-y-2\">\n {linkedParsers.map((parser) => (\n <button\n key={parser.id}\n type=\"button\"\n onClick={() => handleViewParser(parser.id)}\n className=\"w-full flex items-center justify-between px-3 py-2 bg-bg-sunken rounded-md hover:bg-border-default transition-colors group\"\n >\n <div className=\"flex items-center gap-3\">\n <CodeIcon className=\"w-4 h-4 text-text-secondary\" />\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">{parser.name}</p>\n <p className=\"text-xs text-text-tertiary font-mono\">{parser.outputKey}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <ParserTypeBadge type={parser.type} />\n <ArrowRightIcon className=\"w-4 h-4 text-text-tertiary group-hover:text-text-primary transition-colors\" />\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n );\n});\n\nexport default LinkedParsersPanel;\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { createContext as e, forwardRef as t, useCallback as n, useContext as r, useMemo as i } from "react";
2
- import { Link as a, useNavigate as o } from "react-router-dom";
2
+ import { Link as a, useNavigate as o } from "react-router";
3
3
  import { jsx as s } from "react/jsx-runtime";
4
4
  //#region src/bigconsole/contexts/NavigationContext.tsx
5
5
  var c = e(null);
@@ -1 +1 @@
1
- {"version":3,"file":"NavigationContext.js","names":[],"sources":["../../../src/bigconsole/contexts/NavigationContext.tsx"],"sourcesContent":["/**\n * NavigationContext\n *\n * Provides basePath-aware navigation for BigConsole microfrontend.\n * Ensures all internal navigation stays within the /bigconsole route prefix.\n */\n\nimport { createContext, useContext, useCallback, useMemo, forwardRef, type ReactNode } from 'react';\nimport { useNavigate as useRouterNavigate, Link, type NavigateOptions, type LinkProps } from 'react-router-dom';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface NavigationContextValue {\n /** Base path for all routes (e.g., '/bigconsole') */\n basePath: string;\n /** Navigate to a path within the microfrontend */\n navigate: (to: string, options?: NavigateOptions) => void;\n /** Build a full path with basePath prefix */\n buildPath: (path: string) => string;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst NavigationContext = createContext<NavigationContextValue | null>(null);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface NavigationProviderProps {\n /** Base path for the microfrontend (e.g., '/bigconsole') */\n basePath: string;\n children: ReactNode;\n}\n\nexport function NavigationProvider({ basePath, children }: NavigationProviderProps) {\n const routerNavigate = useRouterNavigate();\n\n // Normalize basePath (remove trailing slash)\n const normalizedBasePath = useMemo(() => {\n return basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n }, [basePath]);\n\n // Build full path with basePath prefix\n const buildPath = useCallback(\n (path: string): string => {\n // If path is empty or just '/', return basePath\n if (!path || path === '/') {\n return normalizedBasePath || '/';\n }\n\n // If path already starts with basePath, return as-is.\n // Guard against false prefix matches (e.g. basePath '/big' matching '/bigconsole/foo')\n // and empty normalizedBasePath (when basePath is '/') which is a prefix of everything.\n if (normalizedBasePath && (path === normalizedBasePath || path.startsWith(normalizedBasePath + '/'))) {\n return path;\n }\n\n // If path starts with '/', it's an absolute path within the microfrontend\n // Prepend basePath\n if (path.startsWith('/')) {\n return `${normalizedBasePath}${path}`;\n }\n\n // Relative path - just prepend basePath with slash\n return `${normalizedBasePath}/${path}`;\n },\n [normalizedBasePath]\n );\n\n // Navigate with basePath support\n const navigate = useCallback(\n (to: string, options?: NavigateOptions) => {\n const fullPath = buildPath(to);\n routerNavigate(fullPath, options);\n },\n [buildPath, routerNavigate]\n );\n\n const value = useMemo(\n () => ({\n basePath: normalizedBasePath,\n navigate,\n buildPath,\n }),\n [normalizedBasePath, navigate, buildPath]\n );\n\n return <NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>;\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * Hook to access navigation context\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigation(): NavigationContextValue {\n const context = useContext(NavigationContext);\n if (!context) {\n throw new Error('useNavigation must be used within a NavigationProvider');\n }\n return context;\n}\n\n/**\n * Hook for basePath-aware navigation\n * Returns a navigate function that automatically prepends basePath\n */\nexport function useAppNavigate() {\n const { navigate } = useNavigation();\n return navigate;\n}\n\n/**\n * Hook to build full paths with basePath prefix\n */\nexport function useBuildPath() {\n const { buildPath } = useNavigation();\n return buildPath;\n}\n\n/**\n * Hook to get the current basePath\n */\nexport function useBasePath() {\n const { basePath } = useNavigation();\n return basePath;\n}\n\n// ============================================================================\n// Components\n// ============================================================================\n\n/**\n * AppLink - basePath-aware Link component\n * Automatically prepends basePath to the 'to' prop\n */\nexport interface AppLinkProps extends Omit<LinkProps, 'to'> {\n to: string;\n}\n\nexport const AppLink = forwardRef<HTMLAnchorElement, AppLinkProps>(function AppLink({ to, ...props }, ref) {\n const { buildPath } = useNavigation();\n const fullPath = buildPath(to);\n return <Link ref={ref} to={fullPath} {...props} />;\n});\n"],"mappings":";;;;AA2BA,IAAM,IAAoB,EAA6C,KAAK;AAY5E,SAAgB,EAAmB,EAAE,aAAU,eAAqC;CAClF,IAAM,IAAiB,GAAmB,EAGpC,IAAqB,QAClB,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG,GACvD,CAAC,EAAS,CAAC,EAGR,IAAY,GACf,MAEK,CAAC,KAAQ,MAAS,MACb,KAAsB,MAM3B,MAAuB,MAAS,KAAsB,EAAK,WAAW,IAAqB,IAAI,IAC1F,IAKL,EAAK,WAAW,IAAI,GACf,GAAG,IAAqB,MAI1B,GAAG,EAAmB,GAAG,KAElC,CAAC,EAAmB,CACrB,EAGK,IAAW,GACd,GAAY,MAA8B;AAEzC,IADiB,EAAU,EAAG,EACL,EAAQ;IAEnC,CAAC,GAAW,EAAe,CAC5B,EAEK,IAAQ,SACL;EACL,UAAU;EACV;EACA;EACD,GACD;EAAC;EAAoB;EAAU;EAAU,CAC1C;AAED,QAAO,kBAAC,EAAkB,UAAnB;EAAmC;EAAQ;EAAsC,CAAA;;AAW1F,SAAgB,IAAwC;CACtD,IAAM,IAAU,EAAW,EAAkB;AAC7C,KAAI,CAAC,EACH,OAAU,MAAM,yDAAyD;AAE3E,QAAO;;AAOT,SAAgB,IAAiB;CAC/B,IAAM,EAAE,gBAAa,GAAe;AACpC,QAAO;;AA+BT,IAAa,IAAU,EAA4C,SAAiB,EAAE,OAAI,GAAG,KAAS,GAAK;CACzG,IAAM,EAAE,iBAAc,GAAe;AAErC,QAAO,kBAAC,GAAD;EAAW;EAAK,IADN,EAAU,EAAG;EACO,GAAI;EAAS,CAAA;EAClD"}
1
+ {"version":3,"file":"NavigationContext.js","names":[],"sources":["../../../src/bigconsole/contexts/NavigationContext.tsx"],"sourcesContent":["/**\n * NavigationContext\n *\n * Provides basePath-aware navigation for BigConsole microfrontend.\n * Ensures all internal navigation stays within the /bigconsole route prefix.\n */\n\nimport { createContext, useContext, useCallback, useMemo, forwardRef, type ReactNode } from 'react';\nimport { useNavigate as useRouterNavigate, Link, type NavigateOptions, type LinkProps } from 'react-router';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface NavigationContextValue {\n /** Base path for all routes (e.g., '/bigconsole') */\n basePath: string;\n /** Navigate to a path within the microfrontend */\n navigate: (to: string, options?: NavigateOptions) => void;\n /** Build a full path with basePath prefix */\n buildPath: (path: string) => string;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst NavigationContext = createContext<NavigationContextValue | null>(null);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface NavigationProviderProps {\n /** Base path for the microfrontend (e.g., '/bigconsole') */\n basePath: string;\n children: ReactNode;\n}\n\nexport function NavigationProvider({ basePath, children }: NavigationProviderProps) {\n const routerNavigate = useRouterNavigate();\n\n // Normalize basePath (remove trailing slash)\n const normalizedBasePath = useMemo(() => {\n return basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n }, [basePath]);\n\n // Build full path with basePath prefix\n const buildPath = useCallback(\n (path: string): string => {\n // If path is empty or just '/', return basePath\n if (!path || path === '/') {\n return normalizedBasePath || '/';\n }\n\n // If path already starts with basePath, return as-is.\n // Guard against false prefix matches (e.g. basePath '/big' matching '/bigconsole/foo')\n // and empty normalizedBasePath (when basePath is '/') which is a prefix of everything.\n if (normalizedBasePath && (path === normalizedBasePath || path.startsWith(normalizedBasePath + '/'))) {\n return path;\n }\n\n // If path starts with '/', it's an absolute path within the microfrontend\n // Prepend basePath\n if (path.startsWith('/')) {\n return `${normalizedBasePath}${path}`;\n }\n\n // Relative path - just prepend basePath with slash\n return `${normalizedBasePath}/${path}`;\n },\n [normalizedBasePath]\n );\n\n // Navigate with basePath support\n const navigate = useCallback(\n (to: string, options?: NavigateOptions) => {\n const fullPath = buildPath(to);\n routerNavigate(fullPath, options);\n },\n [buildPath, routerNavigate]\n );\n\n const value = useMemo(\n () => ({\n basePath: normalizedBasePath,\n navigate,\n buildPath,\n }),\n [normalizedBasePath, navigate, buildPath]\n );\n\n return <NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>;\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * Hook to access navigation context\n * @throws Error if used outside NavigationProvider\n */\nexport function useNavigation(): NavigationContextValue {\n const context = useContext(NavigationContext);\n if (!context) {\n throw new Error('useNavigation must be used within a NavigationProvider');\n }\n return context;\n}\n\n/**\n * Hook for basePath-aware navigation\n * Returns a navigate function that automatically prepends basePath\n */\nexport function useAppNavigate() {\n const { navigate } = useNavigation();\n return navigate;\n}\n\n/**\n * Hook to build full paths with basePath prefix\n */\nexport function useBuildPath() {\n const { buildPath } = useNavigation();\n return buildPath;\n}\n\n/**\n * Hook to get the current basePath\n */\nexport function useBasePath() {\n const { basePath } = useNavigation();\n return basePath;\n}\n\n// ============================================================================\n// Components\n// ============================================================================\n\n/**\n * AppLink - basePath-aware Link component\n * Automatically prepends basePath to the 'to' prop\n */\nexport interface AppLinkProps extends Omit<LinkProps, 'to'> {\n to: string;\n}\n\nexport const AppLink = forwardRef<HTMLAnchorElement, AppLinkProps>(function AppLink({ to, ...props }, ref) {\n const { buildPath } = useNavigation();\n const fullPath = buildPath(to);\n return <Link ref={ref} to={fullPath} {...props} />;\n});\n"],"mappings":";;;;AA2BA,IAAM,IAAoB,EAA6C,KAAK;AAY5E,SAAgB,EAAmB,EAAE,aAAU,eAAqC;CAClF,IAAM,IAAiB,GAAmB,EAGpC,IAAqB,QAClB,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG,GACvD,CAAC,EAAS,CAAC,EAGR,IAAY,GACf,MAEK,CAAC,KAAQ,MAAS,MACb,KAAsB,MAM3B,MAAuB,MAAS,KAAsB,EAAK,WAAW,IAAqB,IAAI,IAC1F,IAKL,EAAK,WAAW,IAAI,GACf,GAAG,IAAqB,MAI1B,GAAG,EAAmB,GAAG,KAElC,CAAC,EAAmB,CACrB,EAGK,IAAW,GACd,GAAY,MAA8B;AAEzC,IADiB,EAAU,EAAG,EACL,EAAQ;IAEnC,CAAC,GAAW,EAAe,CAC5B,EAEK,IAAQ,SACL;EACL,UAAU;EACV;EACA;EACD,GACD;EAAC;EAAoB;EAAU;EAAU,CAC1C;AAED,QAAO,kBAAC,EAAkB,UAAnB;EAAmC;EAAQ;EAAsC,CAAA;;AAW1F,SAAgB,IAAwC;CACtD,IAAM,IAAU,EAAW,EAAkB;AAC7C,KAAI,CAAC,EACH,OAAU,MAAM,yDAAyD;AAE3E,QAAO;;AAOT,SAAgB,IAAiB;CAC/B,IAAM,EAAE,gBAAa,GAAe;AACpC,QAAO;;AA+BT,IAAa,IAAU,EAA4C,SAAiB,EAAE,OAAI,GAAG,KAAS,GAAK;CACzG,IAAM,EAAE,iBAAc,GAAe;AAErC,QAAO,kBAAC,GAAD;EAAW;EAAK,IADN,EAAU,EAAG;EACO,GAAI;EAAS,CAAA;EAClD"}
@@ -2,7 +2,7 @@ import { useDashboardStore as e, useGlobalFilterValues as t } from "../store/das
2
2
  import "../store/index.js";
3
3
  import { CONTEXT_PARAM_PREFIX as n, DRILLDOWN_PARAM_PREFIX as r, FILTER_PARAM_PREFIX as i } from "./useDrilldown.js";
4
4
  import { useCallback as a, useEffect as o, useMemo as s, useRef as c } from "react";
5
- import { useLocation as l } from "react-router-dom";
5
+ import { useLocation as l } from "react-router";
6
6
  //#region src/bigconsole/hooks/useFilterUrlSync.ts
7
7
  function u(e) {
8
8
  if (e == null) return "";
@@ -1 +1 @@
1
- {"version":3,"file":"useFilterUrlSync.js","names":[],"sources":["../../../src/bigconsole/hooks/useFilterUrlSync.ts"],"sourcesContent":["/**\n * useFilterUrlSync Hook\n *\n * Syncs global filter values to URL query parameters for sharing filtered views.\n * Supports all filter types: DATE_RANGE, CATEGORY, TEXT_SEARCH, NUMERIC_RANGE, CUSTOM\n *\n * Also handles drilldown context parameters:\n * - filter_* : Global filter values\n * - ctx_* : Drilldown context values (clicked item data)\n * - drilldown_* : Drilldown metadata (depth, parent, path)\n */\n\nimport { useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useGlobalFilterValues, useDashboardStore } from '../store';\nimport { CONTEXT_PARAM_PREFIX, FILTER_PARAM_PREFIX, DRILLDOWN_PARAM_PREFIX } from './useDrilldown';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface UseFilterUrlSyncOptions {\n /** Whether URL sync is enabled */\n enabled?: boolean;\n /** Prefix for URL params (default: 'filter_') */\n paramPrefix?: string;\n /** Debounce delay in ms for URL updates (default: 300) */\n debounceMs?: number;\n}\n\ninterface DrilldownMetadata {\n /** Current drilldown depth */\n depth: number;\n /** Parent dashboard ID */\n parent: string | null;\n /** Array of dashboard IDs in the drilldown path */\n path: string[];\n}\n\ninterface FilterUrlSyncResult {\n /** Manually sync current filters to URL */\n syncToUrl: () => void;\n /** Get shareable URL with current filters */\n getShareableUrl: () => string;\n /** Clear all filter params from URL */\n clearUrlParams: () => void;\n /** Get context params from URL (ctx_*) */\n getContextFromUrl: () => Record<string, unknown>;\n /** Get drilldown metadata from URL */\n getDrilldownMetadata: () => DrilldownMetadata;\n /** Check if currently in drilldown mode */\n isInDrilldown: boolean;\n /** Current drilldown depth */\n drilldownDepth: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Encode filter value for URL\n */\nfunction encodeFilterValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n\n // Array (multi-select): [\"a\", \"b\"] -> \"a,b\"\n if (Array.isArray(value)) {\n return value.map((v) => encodeURIComponent(String(v))).join(',');\n }\n\n // Object types\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Date range: {start, end} -> \"start,end\"\n if (obj.start || obj.end) {\n return `${obj.start || ''},${obj.end || ''}`;\n }\n\n // Numeric range: {min, max} -> \"min,max\"\n if (obj.min !== undefined || obj.max !== undefined) {\n return `${obj.min ?? ''},${obj.max ?? ''}`;\n }\n\n // Object with value property (category)\n if (obj.value !== undefined) {\n return encodeURIComponent(String(obj.value));\n }\n\n // Generic object - JSON stringify\n return encodeURIComponent(JSON.stringify(obj));\n }\n\n // Simple value\n return encodeURIComponent(String(value));\n}\n\n/**\n * Decode filter value from URL\n */\nfunction decodeFilterValue(encoded: string, filterType?: string): unknown {\n if (!encoded) {\n return undefined;\n }\n\n // Date range\n if (filterType === 'DATE_RANGE' || (encoded.includes('T') && encoded.includes(','))) {\n const parts = encoded.split(',');\n if (parts.length === 2) {\n return {\n start: parts[0] || undefined,\n end: parts[1] || undefined,\n };\n }\n }\n\n // Numeric range (check if both parts are numbers)\n if (filterType === 'NUMERIC_RANGE' || /^-?\\d*\\.?\\d*,-?\\d*\\.?\\d*$/.test(encoded)) {\n const parts = encoded.split(',');\n if (parts.length === 2) {\n const min = parts[0] ? parseFloat(parts[0]) : undefined;\n const max = parts[1] ? parseFloat(parts[1]) : undefined;\n if (!isNaN(min as number) || !isNaN(max as number)) {\n return { min, max };\n }\n }\n }\n\n // Multi-select array\n if (encoded.includes(',') && filterType === 'CATEGORY') {\n return encoded.split(',').map((v) => decodeURIComponent(v));\n }\n\n // Try to parse as JSON\n try {\n const decoded = decodeURIComponent(encoded);\n return JSON.parse(decoded);\n } catch {\n // Simple value\n return decodeURIComponent(encoded);\n }\n}\n\n/**\n * Decode a context param value from URL\n */\nfunction decodeContextValue(encoded: string): unknown {\n if (!encoded) {\n return undefined;\n }\n const decoded = decodeURIComponent(encoded);\n // Try to parse as JSON\n try {\n return JSON.parse(decoded);\n } catch {\n return decoded;\n }\n}\n\n/**\n * Parse URL search params to filter values\n */\nfunction parseUrlToFilters(search: string, paramPrefix: string): Record<string, unknown> {\n const params = new URLSearchParams(search);\n const filters: Record<string, unknown> = {};\n\n params.forEach((value, key) => {\n if (key.startsWith(paramPrefix)) {\n const filterId = key.slice(paramPrefix.length);\n // Try to detect type from value format\n filters[filterId] = decodeFilterValue(value);\n }\n });\n\n return filters;\n}\n\n/**\n * Parse URL search params to context values\n */\nfunction parseUrlToContext(search: string): Record<string, unknown> {\n const params = new URLSearchParams(search);\n const context: Record<string, unknown> = {};\n\n params.forEach((value, key) => {\n if (key.startsWith(CONTEXT_PARAM_PREFIX)) {\n const contextKey = key.slice(CONTEXT_PARAM_PREFIX.length);\n context[contextKey] = decodeContextValue(value);\n }\n });\n\n return context;\n}\n\n/**\n * Parse drilldown metadata from URL\n */\nfunction parseUrlToDrilldownMetadata(search: string): DrilldownMetadata {\n const params = new URLSearchParams(search);\n const depthStr = params.get(`${DRILLDOWN_PARAM_PREFIX}depth`);\n const parent = params.get(`${DRILLDOWN_PARAM_PREFIX}parent`);\n const pathStr = params.get(`${DRILLDOWN_PARAM_PREFIX}path`);\n\n let path: string[] = [];\n if (pathStr) {\n try {\n path = JSON.parse(decodeURIComponent(pathStr));\n } catch {\n path = [];\n }\n }\n\n return {\n depth: depthStr ? parseInt(depthStr, 10) : 0,\n parent: parent || null,\n path,\n };\n}\n\n/**\n * Build URL search params from filter values\n */\nfunction filtersToUrlParams(filters: Record<string, unknown>, paramPrefix: string): URLSearchParams {\n const params = new URLSearchParams();\n\n Object.entries(filters).forEach(([filterId, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n const encoded = encodeFilterValue(value);\n if (encoded) {\n params.set(`${paramPrefix}${filterId}`, encoded);\n }\n }\n });\n\n return params;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useFilterUrlSync(options: UseFilterUrlSyncOptions = {}): FilterUrlSyncResult {\n const { enabled = true, paramPrefix = FILTER_PARAM_PREFIX, debounceMs = 300 } = options;\n\n // Use react-router's useLocation to react to URL changes\n const location = useLocation();\n\n const filterValues = useGlobalFilterValues();\n const setGlobalFilterValue = useDashboardStore((state) => state.setGlobalFilterValue);\n\n // Track if initial load from URL is done\n const initialLoadDone = useRef(false);\n // Track last synced values to avoid loops\n const lastSyncedValues = useRef<string>('');\n // Debounce timer\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Track the last pathname to detect same-route navigations (e.g. drilldown)\n const lastPathname = useRef(location.pathname);\n\n // Reset initialLoadDone when pathname changes (e.g. drilldown to different dashboard).\n // Without this, same-route navigation reuses the component and the filter_* URL params\n // from the drilldown URL are never loaded into the Zustand store.\n useEffect(() => {\n if (location.pathname !== lastPathname.current) {\n initialLoadDone.current = false;\n lastSyncedValues.current = '';\n lastPathname.current = location.pathname;\n }\n }, [location.pathname]);\n\n // Compute drilldown state from URL - now reactive to location.search changes\n const drilldownMetadata = useMemo(() => {\n if (typeof window === 'undefined') {\n return { depth: 0, parent: null, path: [] };\n }\n return parseUrlToDrilldownMetadata(location.search);\n }, [location.search]);\n\n const isInDrilldown = drilldownMetadata.depth > 0;\n const drilldownDepth = drilldownMetadata.depth;\n\n // Load filters from URL on initial mount\n useEffect(() => {\n if (!enabled || initialLoadDone.current) return;\n\n // Parse filter params\n const urlFilters = parseUrlToFilters(window.location.search, paramPrefix);\n\n if (Object.keys(urlFilters).length > 0) {\n // Set each filter value\n Object.entries(urlFilters).forEach(([filterId, value]) => {\n setGlobalFilterValue(filterId, value);\n });\n }\n\n // IMPORTANT: Set lastSyncedValues to current filterValues AFTER the sync effect runs\n // This prevents the race condition where sync effect runs with stale (empty) filterValues\n // and strips the URL params that we just loaded.\n // We use the URL filters as the initial synced values since that's what we loaded from.\n lastSyncedValues.current = JSON.stringify(urlFilters);\n\n initialLoadDone.current = true;\n }, [enabled, paramPrefix, setGlobalFilterValue]);\n\n // Sync filter values to URL when they change\n useEffect(() => {\n if (!enabled || !initialLoadDone.current) return;\n\n const currentValues = JSON.stringify(filterValues);\n\n // Skip if values haven't changed\n if (currentValues === lastSyncedValues.current) {\n return;\n }\n\n // Debounce URL updates\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n\n debounceTimer.current = setTimeout(() => {\n const params = filtersToUrlParams(filterValues, paramPrefix);\n\n // Get current URL without filter params (but keep other params like ctx_*, drilldown_*)\n const currentParams = new URLSearchParams(window.location.search);\n // Remove existing filter params only\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n // Merge with new filter params\n params.forEach((value, key) => {\n currentParams.set(key, value);\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n // Update URL without triggering navigation\n window.history.replaceState({ ...window.history.state, filters: filterValues }, '', newUrl);\n\n lastSyncedValues.current = currentValues;\n }, debounceMs);\n\n return () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n };\n }, [enabled, filterValues, paramPrefix, debounceMs]);\n\n // Manual sync to URL\n const syncToUrl = useCallback(() => {\n if (!enabled) return;\n\n const params = filtersToUrlParams(filterValues, paramPrefix);\n const currentParams = new URLSearchParams(window.location.search);\n\n // Remove existing filter params only\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n // Merge with new filter params\n params.forEach((value, key) => {\n currentParams.set(key, value);\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n window.history.replaceState({ ...window.history.state, filters: filterValues }, '', newUrl);\n\n lastSyncedValues.current = JSON.stringify(filterValues);\n }, [enabled, filterValues, paramPrefix]);\n\n // Get shareable URL\n const getShareableUrl = useCallback((): string => {\n const params = filtersToUrlParams(filterValues, paramPrefix);\n const baseUrl = `${window.location.origin}${window.location.pathname}`;\n\n // Preserve non-filter params (ctx_*, drilldown_*)\n const currentParams = new URLSearchParams(window.location.search);\n Array.from(currentParams.keys()).forEach((key) => {\n if (!key.startsWith(paramPrefix)) {\n params.set(key, currentParams.get(key) || '');\n }\n });\n\n const search = params.toString();\n return search ? `${baseUrl}?${search}` : baseUrl;\n }, [filterValues, paramPrefix]);\n\n // Clear all filter params from URL\n const clearUrlParams = useCallback(() => {\n const currentParams = new URLSearchParams(window.location.search);\n\n // Remove filter params only (keep ctx_*, drilldown_*)\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n window.history.replaceState({}, '', newUrl);\n lastSyncedValues.current = '{}';\n }, [paramPrefix]);\n\n // Get context params from URL\n const getContextFromUrl = useCallback((): Record<string, unknown> => {\n return parseUrlToContext(window.location.search);\n }, []);\n\n // Get drilldown metadata from URL\n const getDrilldownMetadata = useCallback((): DrilldownMetadata => {\n return parseUrlToDrilldownMetadata(window.location.search);\n }, []);\n\n return {\n syncToUrl,\n getShareableUrl,\n clearUrlParams,\n getContextFromUrl,\n getDrilldownMetadata,\n isInDrilldown,\n drilldownDepth,\n };\n}\n\nexport default useFilterUrlSync;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useFilterUrlSync.js","names":[],"sources":["../../../src/bigconsole/hooks/useFilterUrlSync.ts"],"sourcesContent":["/**\n * useFilterUrlSync Hook\n *\n * Syncs global filter values to URL query parameters for sharing filtered views.\n * Supports all filter types: DATE_RANGE, CATEGORY, TEXT_SEARCH, NUMERIC_RANGE, CUSTOM\n *\n * Also handles drilldown context parameters:\n * - filter_* : Global filter values\n * - ctx_* : Drilldown context values (clicked item data)\n * - drilldown_* : Drilldown metadata (depth, parent, path)\n */\n\nimport { useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useLocation } from 'react-router';\nimport { useGlobalFilterValues, useDashboardStore } from '../store';\nimport { CONTEXT_PARAM_PREFIX, FILTER_PARAM_PREFIX, DRILLDOWN_PARAM_PREFIX } from './useDrilldown';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface UseFilterUrlSyncOptions {\n /** Whether URL sync is enabled */\n enabled?: boolean;\n /** Prefix for URL params (default: 'filter_') */\n paramPrefix?: string;\n /** Debounce delay in ms for URL updates (default: 300) */\n debounceMs?: number;\n}\n\ninterface DrilldownMetadata {\n /** Current drilldown depth */\n depth: number;\n /** Parent dashboard ID */\n parent: string | null;\n /** Array of dashboard IDs in the drilldown path */\n path: string[];\n}\n\ninterface FilterUrlSyncResult {\n /** Manually sync current filters to URL */\n syncToUrl: () => void;\n /** Get shareable URL with current filters */\n getShareableUrl: () => string;\n /** Clear all filter params from URL */\n clearUrlParams: () => void;\n /** Get context params from URL (ctx_*) */\n getContextFromUrl: () => Record<string, unknown>;\n /** Get drilldown metadata from URL */\n getDrilldownMetadata: () => DrilldownMetadata;\n /** Check if currently in drilldown mode */\n isInDrilldown: boolean;\n /** Current drilldown depth */\n drilldownDepth: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Encode filter value for URL\n */\nfunction encodeFilterValue(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n\n // Array (multi-select): [\"a\", \"b\"] -> \"a,b\"\n if (Array.isArray(value)) {\n return value.map((v) => encodeURIComponent(String(v))).join(',');\n }\n\n // Object types\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Date range: {start, end} -> \"start,end\"\n if (obj.start || obj.end) {\n return `${obj.start || ''},${obj.end || ''}`;\n }\n\n // Numeric range: {min, max} -> \"min,max\"\n if (obj.min !== undefined || obj.max !== undefined) {\n return `${obj.min ?? ''},${obj.max ?? ''}`;\n }\n\n // Object with value property (category)\n if (obj.value !== undefined) {\n return encodeURIComponent(String(obj.value));\n }\n\n // Generic object - JSON stringify\n return encodeURIComponent(JSON.stringify(obj));\n }\n\n // Simple value\n return encodeURIComponent(String(value));\n}\n\n/**\n * Decode filter value from URL\n */\nfunction decodeFilterValue(encoded: string, filterType?: string): unknown {\n if (!encoded) {\n return undefined;\n }\n\n // Date range\n if (filterType === 'DATE_RANGE' || (encoded.includes('T') && encoded.includes(','))) {\n const parts = encoded.split(',');\n if (parts.length === 2) {\n return {\n start: parts[0] || undefined,\n end: parts[1] || undefined,\n };\n }\n }\n\n // Numeric range (check if both parts are numbers)\n if (filterType === 'NUMERIC_RANGE' || /^-?\\d*\\.?\\d*,-?\\d*\\.?\\d*$/.test(encoded)) {\n const parts = encoded.split(',');\n if (parts.length === 2) {\n const min = parts[0] ? parseFloat(parts[0]) : undefined;\n const max = parts[1] ? parseFloat(parts[1]) : undefined;\n if (!isNaN(min as number) || !isNaN(max as number)) {\n return { min, max };\n }\n }\n }\n\n // Multi-select array\n if (encoded.includes(',') && filterType === 'CATEGORY') {\n return encoded.split(',').map((v) => decodeURIComponent(v));\n }\n\n // Try to parse as JSON\n try {\n const decoded = decodeURIComponent(encoded);\n return JSON.parse(decoded);\n } catch {\n // Simple value\n return decodeURIComponent(encoded);\n }\n}\n\n/**\n * Decode a context param value from URL\n */\nfunction decodeContextValue(encoded: string): unknown {\n if (!encoded) {\n return undefined;\n }\n const decoded = decodeURIComponent(encoded);\n // Try to parse as JSON\n try {\n return JSON.parse(decoded);\n } catch {\n return decoded;\n }\n}\n\n/**\n * Parse URL search params to filter values\n */\nfunction parseUrlToFilters(search: string, paramPrefix: string): Record<string, unknown> {\n const params = new URLSearchParams(search);\n const filters: Record<string, unknown> = {};\n\n params.forEach((value, key) => {\n if (key.startsWith(paramPrefix)) {\n const filterId = key.slice(paramPrefix.length);\n // Try to detect type from value format\n filters[filterId] = decodeFilterValue(value);\n }\n });\n\n return filters;\n}\n\n/**\n * Parse URL search params to context values\n */\nfunction parseUrlToContext(search: string): Record<string, unknown> {\n const params = new URLSearchParams(search);\n const context: Record<string, unknown> = {};\n\n params.forEach((value, key) => {\n if (key.startsWith(CONTEXT_PARAM_PREFIX)) {\n const contextKey = key.slice(CONTEXT_PARAM_PREFIX.length);\n context[contextKey] = decodeContextValue(value);\n }\n });\n\n return context;\n}\n\n/**\n * Parse drilldown metadata from URL\n */\nfunction parseUrlToDrilldownMetadata(search: string): DrilldownMetadata {\n const params = new URLSearchParams(search);\n const depthStr = params.get(`${DRILLDOWN_PARAM_PREFIX}depth`);\n const parent = params.get(`${DRILLDOWN_PARAM_PREFIX}parent`);\n const pathStr = params.get(`${DRILLDOWN_PARAM_PREFIX}path`);\n\n let path: string[] = [];\n if (pathStr) {\n try {\n path = JSON.parse(decodeURIComponent(pathStr));\n } catch {\n path = [];\n }\n }\n\n return {\n depth: depthStr ? parseInt(depthStr, 10) : 0,\n parent: parent || null,\n path,\n };\n}\n\n/**\n * Build URL search params from filter values\n */\nfunction filtersToUrlParams(filters: Record<string, unknown>, paramPrefix: string): URLSearchParams {\n const params = new URLSearchParams();\n\n Object.entries(filters).forEach(([filterId, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n const encoded = encodeFilterValue(value);\n if (encoded) {\n params.set(`${paramPrefix}${filterId}`, encoded);\n }\n }\n });\n\n return params;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useFilterUrlSync(options: UseFilterUrlSyncOptions = {}): FilterUrlSyncResult {\n const { enabled = true, paramPrefix = FILTER_PARAM_PREFIX, debounceMs = 300 } = options;\n\n // Use react-router's useLocation to react to URL changes\n const location = useLocation();\n\n const filterValues = useGlobalFilterValues();\n const setGlobalFilterValue = useDashboardStore((state) => state.setGlobalFilterValue);\n\n // Track if initial load from URL is done\n const initialLoadDone = useRef(false);\n // Track last synced values to avoid loops\n const lastSyncedValues = useRef<string>('');\n // Debounce timer\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Track the last pathname to detect same-route navigations (e.g. drilldown)\n const lastPathname = useRef(location.pathname);\n\n // Reset initialLoadDone when pathname changes (e.g. drilldown to different dashboard).\n // Without this, same-route navigation reuses the component and the filter_* URL params\n // from the drilldown URL are never loaded into the Zustand store.\n useEffect(() => {\n if (location.pathname !== lastPathname.current) {\n initialLoadDone.current = false;\n lastSyncedValues.current = '';\n lastPathname.current = location.pathname;\n }\n }, [location.pathname]);\n\n // Compute drilldown state from URL - now reactive to location.search changes\n const drilldownMetadata = useMemo(() => {\n if (typeof window === 'undefined') {\n return { depth: 0, parent: null, path: [] };\n }\n return parseUrlToDrilldownMetadata(location.search);\n }, [location.search]);\n\n const isInDrilldown = drilldownMetadata.depth > 0;\n const drilldownDepth = drilldownMetadata.depth;\n\n // Load filters from URL on initial mount\n useEffect(() => {\n if (!enabled || initialLoadDone.current) return;\n\n // Parse filter params\n const urlFilters = parseUrlToFilters(window.location.search, paramPrefix);\n\n if (Object.keys(urlFilters).length > 0) {\n // Set each filter value\n Object.entries(urlFilters).forEach(([filterId, value]) => {\n setGlobalFilterValue(filterId, value);\n });\n }\n\n // IMPORTANT: Set lastSyncedValues to current filterValues AFTER the sync effect runs\n // This prevents the race condition where sync effect runs with stale (empty) filterValues\n // and strips the URL params that we just loaded.\n // We use the URL filters as the initial synced values since that's what we loaded from.\n lastSyncedValues.current = JSON.stringify(urlFilters);\n\n initialLoadDone.current = true;\n }, [enabled, paramPrefix, setGlobalFilterValue]);\n\n // Sync filter values to URL when they change\n useEffect(() => {\n if (!enabled || !initialLoadDone.current) return;\n\n const currentValues = JSON.stringify(filterValues);\n\n // Skip if values haven't changed\n if (currentValues === lastSyncedValues.current) {\n return;\n }\n\n // Debounce URL updates\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n\n debounceTimer.current = setTimeout(() => {\n const params = filtersToUrlParams(filterValues, paramPrefix);\n\n // Get current URL without filter params (but keep other params like ctx_*, drilldown_*)\n const currentParams = new URLSearchParams(window.location.search);\n // Remove existing filter params only\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n // Merge with new filter params\n params.forEach((value, key) => {\n currentParams.set(key, value);\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n // Update URL without triggering navigation\n window.history.replaceState({ ...window.history.state, filters: filterValues }, '', newUrl);\n\n lastSyncedValues.current = currentValues;\n }, debounceMs);\n\n return () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n };\n }, [enabled, filterValues, paramPrefix, debounceMs]);\n\n // Manual sync to URL\n const syncToUrl = useCallback(() => {\n if (!enabled) return;\n\n const params = filtersToUrlParams(filterValues, paramPrefix);\n const currentParams = new URLSearchParams(window.location.search);\n\n // Remove existing filter params only\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n // Merge with new filter params\n params.forEach((value, key) => {\n currentParams.set(key, value);\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n window.history.replaceState({ ...window.history.state, filters: filterValues }, '', newUrl);\n\n lastSyncedValues.current = JSON.stringify(filterValues);\n }, [enabled, filterValues, paramPrefix]);\n\n // Get shareable URL\n const getShareableUrl = useCallback((): string => {\n const params = filtersToUrlParams(filterValues, paramPrefix);\n const baseUrl = `${window.location.origin}${window.location.pathname}`;\n\n // Preserve non-filter params (ctx_*, drilldown_*)\n const currentParams = new URLSearchParams(window.location.search);\n Array.from(currentParams.keys()).forEach((key) => {\n if (!key.startsWith(paramPrefix)) {\n params.set(key, currentParams.get(key) || '');\n }\n });\n\n const search = params.toString();\n return search ? `${baseUrl}?${search}` : baseUrl;\n }, [filterValues, paramPrefix]);\n\n // Clear all filter params from URL\n const clearUrlParams = useCallback(() => {\n const currentParams = new URLSearchParams(window.location.search);\n\n // Remove filter params only (keep ctx_*, drilldown_*)\n Array.from(currentParams.keys()).forEach((key) => {\n if (key.startsWith(paramPrefix)) {\n currentParams.delete(key);\n }\n });\n\n const finalSearch = currentParams.toString();\n const newUrl = finalSearch ? `${window.location.pathname}?${finalSearch}` : window.location.pathname;\n\n window.history.replaceState({}, '', newUrl);\n lastSyncedValues.current = '{}';\n }, [paramPrefix]);\n\n // Get context params from URL\n const getContextFromUrl = useCallback((): Record<string, unknown> => {\n return parseUrlToContext(window.location.search);\n }, []);\n\n // Get drilldown metadata from URL\n const getDrilldownMetadata = useCallback((): DrilldownMetadata => {\n return parseUrlToDrilldownMetadata(window.location.search);\n }, []);\n\n return {\n syncToUrl,\n getShareableUrl,\n clearUrlParams,\n getContextFromUrl,\n getDrilldownMetadata,\n isInDrilldown,\n drilldownDepth,\n };\n}\n\nexport default useFilterUrlSync;\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ import "../components/dashboard/index.js";
18
18
  import f from "../customWidget/CustomWidgetBuilderSheet.js";
19
19
  import "../customWidget/index.js";
20
20
  import { memo as p, useCallback as m, useEffect as h, useState as g } from "react";
21
- import { useParams as ee } from "react-router-dom";
21
+ import { useParams as ee } from "react-router";
22
22
  import { jsx as _, jsxs as v } from "react/jsx-runtime";
23
23
  //#region src/bigconsole/pages/DashboardBuilderPage.tsx
24
24
  function y() {