@burdenoff/microfe-bigconsole 2026.716.2 → 2026.716.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"SavedViewsDropdown.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.tsx"],"sourcesContent":["/**\n * SavedViewsDropdown Component\n *\n * Dropdown for managing and switching between saved dashboard views.\n */\n\nimport React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useDashboardViews, type DashboardView } from '../../../hooks';\nimport { SEMANTIC_COLORS } from '../../../utils/semanticTokens';\n\n// ============================================================================\n// Solid Background Colors (for portal-rendered dropdowns)\n// ============================================================================\n\n// Hook to detect dark mode and provide solid background color\nfunction useSolidBackground() {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n const checkDarkMode = () => {\n setIsDark(document.documentElement.classList.contains('dark'));\n };\n checkDarkMode();\n\n const observer = new MutationObserver(checkDarkMode);\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n }, []);\n\n // Return solid opaque colors - no transparency\n return isDark ? '#1f2937' : '#ffffff';\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SavedViewsDropdownProps {\n dashboardId: string;\n currentFilters?: Record<string, unknown>;\n currentLayout?: Record<string, unknown>;\n onViewLoad?: (view: DashboardView) => void;\n className?: string;\n}\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function SavedViewsDropdown({\n dashboardId,\n currentFilters,\n currentLayout,\n onViewLoad,\n className = '',\n}: SavedViewsDropdownProps) {\n const {\n views,\n currentView,\n loading,\n fetchViews,\n saveCurrentView,\n loadView,\n updateView,\n deleteView,\n setAsDefault,\n clearCurrentView,\n } = useDashboardViews();\n\n const [isOpen, setIsOpen] = useState(false);\n const [showSaveDialog, setShowSaveDialog] = useState(false);\n const [newViewName, setNewViewName] = useState('');\n const [newViewDescription, setNewViewDescription] = useState('');\n const [saveAsDefault, setSaveAsDefault] = useState(false);\n const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });\n\n const buttonRef = useRef<HTMLButtonElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Get solid background color for portal-rendered dropdown (no transparency)\n const solidBgColor = useSolidBackground();\n\n // Fetch views on mount\n useEffect(() => {\n if (dashboardId) {\n fetchViews(dashboardId);\n }\n }, [dashboardId, fetchViews]);\n\n // Update dropdown position when opening\n useEffect(() => {\n if (isOpen && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n const dropdownWidth = 280;\n\n // Calculate left position - align left edge with button, but ensure it doesn't go off screen\n let leftPos = rect.left;\n\n // If dropdown would go off the right edge of screen, align right edge with button instead\n if (leftPos + dropdownWidth > window.innerWidth - 16) {\n leftPos = rect.right - dropdownWidth;\n }\n\n // Ensure minimum left margin\n leftPos = Math.max(16, leftPos);\n\n setDropdownPosition({\n top: rect.bottom + 4,\n left: leftPos,\n });\n }\n }, [isOpen]);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n const target = event.target as Node;\n if (\n buttonRef.current &&\n !buttonRef.current.contains(target) &&\n dropdownRef.current &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [isOpen]);\n\n // Handle view selection\n const handleSelectView = useCallback(\n (viewId: string) => {\n const view = views.find((v) => v.id === viewId);\n if (view) {\n loadView(viewId);\n if (onViewLoad) {\n onViewLoad(view);\n }\n }\n setIsOpen(false);\n },\n [views, loadView, onViewLoad]\n );\n\n // Handle save new view\n const handleSaveView = useCallback(async () => {\n if (!newViewName.trim()) return;\n\n await saveCurrentView({\n dashboardId,\n name: newViewName.trim(),\n description: newViewDescription.trim() || undefined,\n filters: currentFilters,\n layout: currentLayout,\n isDefault: saveAsDefault,\n });\n\n setNewViewName('');\n setNewViewDescription('');\n setSaveAsDefault(false);\n setShowSaveDialog(false);\n }, [dashboardId, newViewName, newViewDescription, currentFilters, currentLayout, saveAsDefault, saveCurrentView]);\n\n // Handle update current view\n const handleUpdateCurrentView = useCallback(async () => {\n if (!currentView) return;\n\n await updateView({\n id: currentView.id,\n filters: currentFilters,\n layout: currentLayout,\n });\n }, [currentView, currentFilters, currentLayout, updateView]);\n\n // Handle delete view\n const handleDeleteView = useCallback(\n async (viewId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n if (window.confirm('Are you sure you want to delete this view?')) {\n await deleteView(viewId);\n }\n },\n [deleteView]\n );\n\n // Handle set as default\n const handleSetDefault = useCallback(\n async (viewId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n await setAsDefault(viewId);\n },\n [setAsDefault]\n );\n\n // Clear current view\n const handleClearView = useCallback(() => {\n clearCurrentView();\n setIsOpen(false);\n }, [clearCurrentView]);\n\n // Dropdown Menu rendered via portal\n const dropdownMenu = isOpen\n ? createPortal(\n <div\n ref={dropdownRef}\n style={{\n position: 'fixed',\n top: dropdownPosition.top,\n left: Math.max(16, dropdownPosition.left), // Ensure minimum left margin\n minWidth: '280px',\n backgroundColor: solidBgColor, // Solid opaque background - no transparency\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '8px',\n boxShadow: '0 8px 24px rgba(0,0,0,0.15), 0 4px 8px rgba(0,0,0,0.1)',\n zIndex: 99998,\n overflow: 'hidden',\n }}\n >\n {/* Header */}\n <div\n style={{\n padding: '12px 16px',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <span style={{ fontSize: '13px', fontWeight: 600, color: SEMANTIC_COLORS.textPrimary }}>Saved Views</span>\n <button\n onClick={() => setShowSaveDialog(true)}\n style={{\n padding: '4px 10px',\n backgroundColor: SEMANTIC_COLORS.actionPrimaryBg,\n color: 'white',\n border: 'none',\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n + Save Current\n </button>\n </div>\n\n {/* Views List */}\n <div style={{ maxHeight: '300px', overflowY: 'auto' }}>\n {loading && views.length === 0 ? (\n <div style={{ padding: '16px', color: SEMANTIC_COLORS.textSecondary, fontSize: '13px' }}>\n Loading views...\n </div>\n ) : views.length === 0 ? (\n <div style={{ padding: '16px', color: SEMANTIC_COLORS.textSecondary, fontSize: '13px' }}>\n No saved views yet. Save your current view to get started.\n </div>\n ) : (\n <>\n {/* Reset to Default Option */}\n <button\n onClick={handleClearView}\n style={{\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n padding: '10px 16px',\n backgroundColor: !currentView ? SEMANTIC_COLORS.bgSecondary : SEMANTIC_COLORS.bgPrimary,\n border: 'none',\n cursor: 'pointer',\n textAlign: 'left',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: SEMANTIC_COLORS.textPrimary }}>\n Default View\n </div>\n <div style={{ fontSize: '11px', color: SEMANTIC_COLORS.textSecondary }}>\n Reset to original dashboard state\n </div>\n </div>\n </button>\n\n {/* Saved Views */}\n {views.map((view) => (\n <div\n key={view.id}\n onClick={() => handleSelectView(view.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 16px',\n backgroundColor:\n currentView?.id === view.id ? SEMANTIC_COLORS.bgSecondary : SEMANTIC_COLORS.bgPrimary,\n cursor: 'pointer',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <div style={{ flex: 1 }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>\n <span\n style={{\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n {view.name}\n </span>\n {view.isDefault && (\n <span\n style={{\n fontSize: '9px',\n padding: '2px 4px',\n backgroundColor: SEMANTIC_COLORS.actionPrimaryBgSubtle,\n color: SEMANTIC_COLORS.actionPrimaryBg,\n borderRadius: '4px',\n textTransform: 'uppercase',\n fontWeight: 600,\n }}\n >\n Default\n </span>\n )}\n </div>\n {view.description && (\n <div style={{ fontSize: '11px', color: SEMANTIC_COLORS.textSecondary, marginTop: '2px' }}>\n {view.description}\n </div>\n )}\n <div style={{ fontSize: '10px', color: SEMANTIC_COLORS.textSecondary, marginTop: '2px' }}>\n {new Date(view.updatedAt).toLocaleDateString()}\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>\n {!view.isDefault && (\n <button\n onClick={(e) => handleSetDefault(view.id, e)}\n title=\"Set as default\"\n style={{\n padding: '4px 6px',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n color: SEMANTIC_COLORS.textSecondary,\n fontSize: '11px',\n }}\n >\n Set Default\n </button>\n )}\n <button\n onClick={(e) => handleDeleteView(view.id, e)}\n title=\"Delete view\"\n style={{\n padding: '4px 6px',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n color: SEMANTIC_COLORS.statusErrorText,\n fontSize: '11px',\n }}\n >\n Delete\n </button>\n </div>\n </div>\n ))}\n </>\n )}\n </div>\n\n {/* Update Current View Button */}\n {currentView && (\n <div\n style={{\n padding: '12px 16px',\n borderTop: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n backgroundColor: SEMANTIC_COLORS.bgSecondary,\n }}\n >\n <button\n onClick={handleUpdateCurrentView}\n style={{\n width: '100%',\n padding: '8px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Update \"{currentView.name}\" with current state\n </button>\n </div>\n )}\n </div>,\n document.body\n )\n : null;\n\n // Save Dialog rendered via portal\n const saveDialog = showSaveDialog\n ? createPortal(\n <div\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 99999,\n isolation: 'isolate',\n }}\n onClick={() => setShowSaveDialog(false)}\n >\n <div\n style={{\n width: '400px',\n backgroundColor: solidBgColor, // Solid opaque background - no transparency\n borderRadius: '8px',\n boxShadow: '0 4px 24px rgba(0, 0, 0, 0.3)',\n overflow: 'hidden',\n position: 'relative',\n zIndex: 100000,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n style={{\n padding: '16px 20px',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: SEMANTIC_COLORS.textPrimary }}>\n Save Current View\n </h3>\n </div>\n <div style={{ padding: '20px' }}>\n <div style={{ marginBottom: '16px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '6px',\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n View Name *\n </label>\n <input\n type=\"text\"\n value={newViewName}\n onChange={(e) => setNewViewName(e.target.value)}\n placeholder=\"e.g., Q4 Analysis, Sales Overview\"\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n fontSize: '14px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n color: SEMANTIC_COLORS.textPrimary,\n boxSizing: 'border-box',\n }}\n />\n </div>\n <div style={{ marginBottom: '16px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '6px',\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Description\n </label>\n <textarea\n value={newViewDescription}\n onChange={(e) => setNewViewDescription(e.target.value)}\n placeholder=\"Optional description...\"\n rows={2}\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n fontSize: '14px',\n resize: 'vertical',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n color: SEMANTIC_COLORS.textPrimary,\n boxSizing: 'border-box',\n }}\n />\n </div>\n <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={saveAsDefault} onChange={(e) => setSaveAsDefault(e.target.checked)} />\n <span style={{ fontSize: '13px', color: SEMANTIC_COLORS.textPrimary }}>Set as default view</span>\n </label>\n </div>\n <div\n style={{\n padding: '16px 20px',\n borderTop: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n display: 'flex',\n justifyContent: 'flex-end',\n gap: '12px',\n backgroundColor: SEMANTIC_COLORS.bgSecondary,\n }}\n >\n <button\n onClick={() => setShowSaveDialog(false)}\n style={{\n padding: '10px 20px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '14px',\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Cancel\n </button>\n <button\n onClick={handleSaveView}\n disabled={!newViewName.trim()}\n style={{\n padding: '10px 20px',\n backgroundColor: newViewName.trim() ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.bgSecondary,\n color: newViewName.trim() ? 'white' : SEMANTIC_COLORS.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: newViewName.trim() ? 'pointer' : 'not-allowed',\n fontSize: '14px',\n fontWeight: 500,\n }}\n >\n Save View\n </button>\n </div>\n </div>\n </div>,\n document.body\n )\n : null;\n\n return (\n <div className={className} style={{ position: 'relative', display: 'inline-block' }}>\n {/* Trigger Button */}\n <button\n ref={buttonRef}\n onClick={() => setIsOpen(!isOpen)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n padding: '6px 12px',\n backgroundColor: currentView ? SEMANTIC_COLORS.actionPrimaryBgSubtle : SEMANTIC_COLORS.bgSecondary,\n border: `1px solid ${currentView ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n color: currentView ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.textPrimary,\n }}\n >\n <span style={{ fontWeight: 500 }}>{currentView ? currentView.name : 'Views'}</span>\n <span style={{ fontSize: '10px' }}>{isOpen ? '▲' : '▼'}</span>\n </button>\n\n {/* Dropdown Menu - rendered via portal */}\n {dropdownMenu}\n\n {/* Save Dialog - rendered via portal */}\n {saveDialog}\n </div>\n );\n}\n\nexport default SavedViewsDropdown;\n"],"mappings":"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"}
1
+ {"version":3,"file":"SavedViewsDropdown.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.tsx"],"sourcesContent":["/**\n * SavedViewsDropdown Component\n *\n * Dropdown for managing and switching between saved dashboard views.\n */\n\nimport React, { useState, useEffect, useCallback, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useDashboardViews, type DashboardView } from '../../../hooks';\nimport { SEMANTIC_COLORS } from '../../../utils/semanticTokens';\nimport { reportBigConsoleQuotaError } from '../../../utils/quota';\n\n// ============================================================================\n// Solid Background Colors (for portal-rendered dropdowns)\n// ============================================================================\n\n// Hook to detect dark mode and provide solid background color\nfunction useSolidBackground() {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n const checkDarkMode = () => {\n setIsDark(document.documentElement.classList.contains('dark'));\n };\n checkDarkMode();\n\n const observer = new MutationObserver(checkDarkMode);\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n }, []);\n\n // Return solid opaque colors - no transparency\n return isDark ? '#1f2937' : '#ffffff';\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface SavedViewsDropdownProps {\n dashboardId: string;\n currentFilters?: Record<string, unknown>;\n currentLayout?: Record<string, unknown>;\n onViewLoad?: (view: DashboardView) => void;\n className?: string;\n}\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function SavedViewsDropdown({\n dashboardId,\n currentFilters,\n currentLayout,\n onViewLoad,\n className = '',\n}: SavedViewsDropdownProps) {\n const {\n views,\n currentView,\n loading,\n fetchViews,\n saveCurrentView,\n loadView,\n updateView,\n deleteView,\n setAsDefault,\n clearCurrentView,\n } = useDashboardViews();\n\n const [isOpen, setIsOpen] = useState(false);\n const [showSaveDialog, setShowSaveDialog] = useState(false);\n const [newViewName, setNewViewName] = useState('');\n const [newViewDescription, setNewViewDescription] = useState('');\n const [saveAsDefault, setSaveAsDefault] = useState(false);\n const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });\n\n const buttonRef = useRef<HTMLButtonElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Get solid background color for portal-rendered dropdown (no transparency)\n const solidBgColor = useSolidBackground();\n\n // Fetch views on mount\n useEffect(() => {\n if (dashboardId) {\n fetchViews(dashboardId);\n }\n }, [dashboardId, fetchViews]);\n\n // Update dropdown position when opening\n useEffect(() => {\n if (isOpen && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n const dropdownWidth = 280;\n\n // Calculate left position - align left edge with button, but ensure it doesn't go off screen\n let leftPos = rect.left;\n\n // If dropdown would go off the right edge of screen, align right edge with button instead\n if (leftPos + dropdownWidth > window.innerWidth - 16) {\n leftPos = rect.right - dropdownWidth;\n }\n\n // Ensure minimum left margin\n leftPos = Math.max(16, leftPos);\n\n setDropdownPosition({\n top: rect.bottom + 4,\n left: leftPos,\n });\n }\n }, [isOpen]);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n const target = event.target as Node;\n if (\n buttonRef.current &&\n !buttonRef.current.contains(target) &&\n dropdownRef.current &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [isOpen]);\n\n // Handle view selection\n const handleSelectView = useCallback(\n (viewId: string) => {\n const view = views.find((v) => v.id === viewId);\n if (view) {\n loadView(viewId);\n if (onViewLoad) {\n onViewLoad(view);\n }\n }\n setIsOpen(false);\n },\n [views, loadView, onViewLoad]\n );\n\n // Handle save new view\n const handleSaveView = useCallback(async () => {\n if (!newViewName.trim()) return;\n\n try {\n await saveCurrentView({\n dashboardId,\n name: newViewName.trim(),\n description: newViewDescription.trim() || undefined,\n filters: currentFilters,\n layout: currentLayout,\n isDefault: saveAsDefault,\n });\n } catch (err) {\n if (reportBigConsoleQuotaError(err, 'session')) {\n setShowSaveDialog(false);\n setIsOpen(false);\n return;\n }\n throw err;\n }\n\n setNewViewName('');\n setNewViewDescription('');\n setSaveAsDefault(false);\n setShowSaveDialog(false);\n }, [dashboardId, newViewName, newViewDescription, currentFilters, currentLayout, saveAsDefault, saveCurrentView]);\n\n // Handle update current view\n const handleUpdateCurrentView = useCallback(async () => {\n if (!currentView) return;\n\n await updateView({\n id: currentView.id,\n filters: currentFilters,\n layout: currentLayout,\n });\n }, [currentView, currentFilters, currentLayout, updateView]);\n\n // Handle delete view\n const handleDeleteView = useCallback(\n async (viewId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n if (window.confirm('Are you sure you want to delete this view?')) {\n await deleteView(viewId);\n }\n },\n [deleteView]\n );\n\n // Handle set as default\n const handleSetDefault = useCallback(\n async (viewId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n await setAsDefault(viewId);\n },\n [setAsDefault]\n );\n\n // Clear current view\n const handleClearView = useCallback(() => {\n clearCurrentView();\n setIsOpen(false);\n }, [clearCurrentView]);\n\n // Dropdown Menu rendered via portal\n const dropdownMenu = isOpen\n ? createPortal(\n <div\n ref={dropdownRef}\n style={{\n position: 'fixed',\n top: dropdownPosition.top,\n left: Math.max(16, dropdownPosition.left), // Ensure minimum left margin\n minWidth: '280px',\n backgroundColor: solidBgColor, // Solid opaque background - no transparency\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '8px',\n boxShadow: '0 8px 24px rgba(0,0,0,0.15), 0 4px 8px rgba(0,0,0,0.1)',\n zIndex: 99998,\n overflow: 'hidden',\n }}\n >\n {/* Header */}\n <div\n style={{\n padding: '12px 16px',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <span style={{ fontSize: '13px', fontWeight: 600, color: SEMANTIC_COLORS.textPrimary }}>Saved Views</span>\n <button\n onClick={() => setShowSaveDialog(true)}\n style={{\n padding: '4px 10px',\n backgroundColor: SEMANTIC_COLORS.actionPrimaryBg,\n color: 'white',\n border: 'none',\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n + Save Current\n </button>\n </div>\n\n {/* Views List */}\n <div style={{ maxHeight: '300px', overflowY: 'auto' }}>\n {loading && views.length === 0 ? (\n <div style={{ padding: '16px', color: SEMANTIC_COLORS.textSecondary, fontSize: '13px' }}>\n Loading views...\n </div>\n ) : views.length === 0 ? (\n <div style={{ padding: '16px', color: SEMANTIC_COLORS.textSecondary, fontSize: '13px' }}>\n No saved views yet. Save your current view to get started.\n </div>\n ) : (\n <>\n {/* Reset to Default Option */}\n <button\n onClick={handleClearView}\n style={{\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n padding: '10px 16px',\n backgroundColor: !currentView ? SEMANTIC_COLORS.bgSecondary : SEMANTIC_COLORS.bgPrimary,\n border: 'none',\n cursor: 'pointer',\n textAlign: 'left',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: SEMANTIC_COLORS.textPrimary }}>\n Default View\n </div>\n <div style={{ fontSize: '11px', color: SEMANTIC_COLORS.textSecondary }}>\n Reset to original dashboard state\n </div>\n </div>\n </button>\n\n {/* Saved Views */}\n {views.map((view) => (\n <div\n key={view.id}\n onClick={() => handleSelectView(view.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 16px',\n backgroundColor:\n currentView?.id === view.id ? SEMANTIC_COLORS.bgSecondary : SEMANTIC_COLORS.bgPrimary,\n cursor: 'pointer',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <div style={{ flex: 1 }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>\n <span\n style={{\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n {view.name}\n </span>\n {view.isDefault && (\n <span\n style={{\n fontSize: '9px',\n padding: '2px 4px',\n backgroundColor: SEMANTIC_COLORS.actionPrimaryBgSubtle,\n color: SEMANTIC_COLORS.actionPrimaryBg,\n borderRadius: '4px',\n textTransform: 'uppercase',\n fontWeight: 600,\n }}\n >\n Default\n </span>\n )}\n </div>\n {view.description && (\n <div style={{ fontSize: '11px', color: SEMANTIC_COLORS.textSecondary, marginTop: '2px' }}>\n {view.description}\n </div>\n )}\n <div style={{ fontSize: '10px', color: SEMANTIC_COLORS.textSecondary, marginTop: '2px' }}>\n {new Date(view.updatedAt).toLocaleDateString()}\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>\n {!view.isDefault && (\n <button\n onClick={(e) => handleSetDefault(view.id, e)}\n title=\"Set as default\"\n style={{\n padding: '4px 6px',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n color: SEMANTIC_COLORS.textSecondary,\n fontSize: '11px',\n }}\n >\n Set Default\n </button>\n )}\n <button\n onClick={(e) => handleDeleteView(view.id, e)}\n title=\"Delete view\"\n style={{\n padding: '4px 6px',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n color: SEMANTIC_COLORS.statusErrorText,\n fontSize: '11px',\n }}\n >\n Delete\n </button>\n </div>\n </div>\n ))}\n </>\n )}\n </div>\n\n {/* Update Current View Button */}\n {currentView && (\n <div\n style={{\n padding: '12px 16px',\n borderTop: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n backgroundColor: SEMANTIC_COLORS.bgSecondary,\n }}\n >\n <button\n onClick={handleUpdateCurrentView}\n style={{\n width: '100%',\n padding: '8px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Update \"{currentView.name}\" with current state\n </button>\n </div>\n )}\n </div>,\n document.body\n )\n : null;\n\n // Save Dialog rendered via portal\n const saveDialog = showSaveDialog\n ? createPortal(\n <div\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 99999,\n isolation: 'isolate',\n }}\n onClick={() => setShowSaveDialog(false)}\n >\n <div\n style={{\n width: '400px',\n backgroundColor: solidBgColor, // Solid opaque background - no transparency\n borderRadius: '8px',\n boxShadow: '0 4px 24px rgba(0, 0, 0, 0.3)',\n overflow: 'hidden',\n position: 'relative',\n zIndex: 100000,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n style={{\n padding: '16px 20px',\n borderBottom: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n }}\n >\n <h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: SEMANTIC_COLORS.textPrimary }}>\n Save Current View\n </h3>\n </div>\n <div style={{ padding: '20px' }}>\n <div style={{ marginBottom: '16px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '6px',\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n View Name *\n </label>\n <input\n type=\"text\"\n value={newViewName}\n onChange={(e) => setNewViewName(e.target.value)}\n placeholder=\"e.g., Q4 Analysis, Sales Overview\"\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n fontSize: '14px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n color: SEMANTIC_COLORS.textPrimary,\n boxSizing: 'border-box',\n }}\n />\n </div>\n <div style={{ marginBottom: '16px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '6px',\n fontSize: '13px',\n fontWeight: 500,\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Description\n </label>\n <textarea\n value={newViewDescription}\n onChange={(e) => setNewViewDescription(e.target.value)}\n placeholder=\"Optional description...\"\n rows={2}\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n fontSize: '14px',\n resize: 'vertical',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n color: SEMANTIC_COLORS.textPrimary,\n boxSizing: 'border-box',\n }}\n />\n </div>\n <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={saveAsDefault} onChange={(e) => setSaveAsDefault(e.target.checked)} />\n <span style={{ fontSize: '13px', color: SEMANTIC_COLORS.textPrimary }}>Set as default view</span>\n </label>\n </div>\n <div\n style={{\n padding: '16px 20px',\n borderTop: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n display: 'flex',\n justifyContent: 'flex-end',\n gap: '12px',\n backgroundColor: SEMANTIC_COLORS.bgSecondary,\n }}\n >\n <button\n onClick={() => setShowSaveDialog(false)}\n style={{\n padding: '10px 20px',\n backgroundColor: SEMANTIC_COLORS.bgPrimary,\n border: `1px solid ${SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '14px',\n color: SEMANTIC_COLORS.textPrimary,\n }}\n >\n Cancel\n </button>\n <button\n onClick={handleSaveView}\n disabled={!newViewName.trim()}\n style={{\n padding: '10px 20px',\n backgroundColor: newViewName.trim() ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.bgSecondary,\n color: newViewName.trim() ? 'white' : SEMANTIC_COLORS.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: newViewName.trim() ? 'pointer' : 'not-allowed',\n fontSize: '14px',\n fontWeight: 500,\n }}\n >\n Save View\n </button>\n </div>\n </div>\n </div>,\n document.body\n )\n : null;\n\n return (\n <div className={className} style={{ position: 'relative', display: 'inline-block' }}>\n {/* Trigger Button */}\n <button\n ref={buttonRef}\n onClick={() => setIsOpen(!isOpen)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n padding: '6px 12px',\n backgroundColor: currentView ? SEMANTIC_COLORS.actionPrimaryBgSubtle : SEMANTIC_COLORS.bgSecondary,\n border: `1px solid ${currentView ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.borderDefault}`,\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n color: currentView ? SEMANTIC_COLORS.actionPrimaryBg : SEMANTIC_COLORS.textPrimary,\n }}\n >\n <span style={{ fontWeight: 500 }}>{currentView ? currentView.name : 'Views'}</span>\n <span style={{ fontSize: '10px' }}>{isOpen ? '▲' : '▼'}</span>\n </button>\n\n {/* Dropdown Menu - rendered via portal */}\n {dropdownMenu}\n\n {/* Save Dialog - rendered via portal */}\n {saveDialog}\n </div>\n );\n}\n\nexport default SavedViewsDropdown;\n"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
1
  import { useDashboardStore as e } from "../store/dashboardStore.js";
2
2
  import "../store/index.js";
3
3
  import { CloneDashboardDocument as t, CreateDashboardDocument as n, DeleteDashboardDocument as r, GetDashboardDocument as i, ListDashboardsDocument as a, UpdateDashboardDocument as o } from "../../generated/wspace-operations.js";
4
- import { useCallback as s, useEffect as c, useState as l } from "react";
5
- import { useApolloClient as u } from "@apollo/client/react";
4
+ import { createBigConsoleQuotaError as s, isBigConsoleQuotaError as c } from "../utils/quota.js";
5
+ import { useCallback as l, useEffect as u, useState as d } from "react";
6
+ import { useApolloClient as f } from "@apollo/client/react";
6
7
  //#region src/bigconsole/hooks/useDashboardOperations.ts
7
- function d(e) {
8
+ function p(e) {
8
9
  let t = e.pages?.edges?.map((t) => ({
9
10
  id: t.node.id,
10
11
  dashboardId: t.node.dashboardId || e.id,
@@ -39,7 +40,7 @@ function d(e) {
39
40
  updatedAt: e.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
40
41
  };
41
42
  }
42
- function f(e) {
43
+ function m(e) {
43
44
  return {
44
45
  id: e.id,
45
46
  name: e.name || "Untitled Dashboard",
@@ -53,10 +54,10 @@ function f(e) {
53
54
  pageCount: e.pages?.totalCount ?? 0
54
55
  };
55
56
  }
56
- function p() {
57
- let [p, m] = l(!1), [h, g] = l(null), [_, v] = l([]), y = u(), b = e((e) => e.currentDashboard), x = e((e) => e.setCurrentDashboard), S = s(async (e) => {
57
+ function h() {
58
+ let [h, g] = d(!1), [_, v] = d(null), [y, b] = d([]), x = f(), S = e((e) => e.currentDashboard), C = e((e) => e.setCurrentDashboard), w = l(async (e) => {
58
59
  try {
59
- m(!0), g(null), v(((await y.query({
60
+ g(!0), v(null), b(((await x.query({
60
61
  query: a,
61
62
  variables: {
62
63
  first: 50,
@@ -64,34 +65,34 @@ function p() {
64
65
  includeArchived: !1
65
66
  },
66
67
  fetchPolicy: "network-only"
67
- })).data?.listDashboards?.edges || []).map((e) => f(e.node)));
68
+ })).data?.listDashboards?.edges || []).map((e) => m(e.node)));
68
69
  } catch (e) {
69
- g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch dashboards"));
70
+ v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch dashboards"));
70
71
  } finally {
71
- m(!1);
72
+ g(!1);
72
73
  }
73
- }, [y]), C = s(async (e, t) => {
74
+ }, [x]), T = l(async (e, t) => {
74
75
  let n = t?.silent ?? !1;
75
76
  try {
76
- n || (m(!0), g(null));
77
- let t = await y.query({
77
+ n || (g(!0), v(null));
78
+ let t = await x.query({
78
79
  query: i,
79
80
  variables: { id: e },
80
81
  fetchPolicy: "network-only"
81
82
  });
82
83
  if (t.data?.getDashboard) {
83
- let e = d(t.data.getDashboard);
84
- return x(e), e;
84
+ let e = p(t.data.getDashboard);
85
+ return C(e), e;
85
86
  }
86
87
  return null;
87
88
  } catch (e) {
88
- return n || g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch dashboard")), null;
89
+ return n || v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to fetch dashboard")), null;
89
90
  } finally {
90
- n || m(!1);
91
+ n || g(!1);
91
92
  }
92
- }, [y, x]), w = s(async (e) => {
93
+ }, [x, C]), E = l(async (e) => {
93
94
  try {
94
- m(!0), g(null);
95
+ g(!0), v(null);
95
96
  let t = {
96
97
  name: e.name,
97
98
  description: e.description,
@@ -100,62 +101,64 @@ function p() {
100
101
  createDefaultPage: !0
101
102
  };
102
103
  e.tags && e.tags.length > 0 && (t.metadata = { tags: e.tags });
103
- let r = await y.mutate({
104
+ let r = await x.mutate({
104
105
  mutation: n,
105
106
  variables: { input: t }
106
107
  });
108
+ if (c(r)) throw s(r);
107
109
  if (r.data?.createBigConsoleDashboard) {
108
- let e = d(r.data.createBigConsoleDashboard);
109
- return await S(), e;
110
+ let e = p(r.data.createBigConsoleDashboard);
111
+ return await w(), e;
110
112
  }
111
113
  return null;
112
114
  } catch (e) {
113
- return g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to create dashboard")), null;
115
+ if (c(e)) throw e;
116
+ return v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to create dashboard")), null;
114
117
  } finally {
115
- m(!1);
118
+ g(!1);
116
119
  }
117
- }, [y, S]), T = s(async (e) => {
120
+ }, [x, w]), D = l(async (e) => {
118
121
  try {
119
- m(!0), g(null);
120
- let t = await y.mutate({
122
+ g(!0), v(null);
123
+ let t = await x.mutate({
121
124
  mutation: o,
122
125
  variables: { input: e }
123
126
  });
124
127
  if (t.data?.updateBigConsoleDashboard) {
125
- let n = d(t.data.updateBigConsoleDashboard);
126
- return b?.id === e.id && x(n), await S(), n;
128
+ let n = p(t.data.updateBigConsoleDashboard);
129
+ return S?.id === e.id && C(n), await w(), n;
127
130
  }
128
131
  return null;
129
132
  } catch (e) {
130
- return g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to update dashboard")), null;
133
+ return v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to update dashboard")), null;
131
134
  } finally {
132
- m(!1);
135
+ g(!1);
133
136
  }
134
137
  }, [
135
- y,
136
- b?.id,
137
138
  x,
138
- S
139
- ]), E = s(async (e) => {
139
+ S?.id,
140
+ C,
141
+ w
142
+ ]), O = l(async (e) => {
140
143
  try {
141
- return m(!0), g(null), (await y.mutate({
144
+ return g(!0), v(null), (await x.mutate({
142
145
  mutation: r,
143
146
  variables: { id: e }
144
- })).data?.deleteBigConsoleDashboard ? (b?.id === e && x(null), await S(), !0) : !1;
147
+ })).data?.deleteBigConsoleDashboard ? (S?.id === e && C(null), await w(), !0) : !1;
145
148
  } catch (e) {
146
- return g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to delete dashboard")), !1;
149
+ return v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to delete dashboard")), !1;
147
150
  } finally {
148
- m(!1);
151
+ g(!1);
149
152
  }
150
153
  }, [
151
- y,
152
- b?.id,
153
154
  x,
154
- S
155
- ]), D = s(async (e, n) => {
155
+ S?.id,
156
+ C,
157
+ w
158
+ ]), k = l(async (e, n) => {
156
159
  try {
157
- m(!0), g(null);
158
- let r = await y.mutate({
160
+ g(!0), v(null);
161
+ let r = await x.mutate({
159
162
  mutation: t,
160
163
  variables: { input: {
161
164
  sourceDashboardId: e,
@@ -163,36 +166,38 @@ function p() {
163
166
  includeWidgets: !0
164
167
  } }
165
168
  });
169
+ if (c(r)) throw s(r);
166
170
  if (r.data?.cloneDashboard) {
167
- let e = d(r.data.cloneDashboard);
168
- return await S(), e;
171
+ let e = p(r.data.cloneDashboard);
172
+ return await w(), e;
169
173
  }
170
174
  return null;
171
175
  } catch (e) {
172
- return g(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to clone dashboard")), null;
176
+ if (c(e)) throw e;
177
+ return v(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to clone dashboard")), null;
173
178
  } finally {
174
- m(!1);
179
+ g(!1);
175
180
  }
176
- }, [y, S]), O = s(async () => {
177
- await S();
178
- }, [S]);
179
- return c(() => {
180
- S();
181
- }, [S]), {
182
- loading: p,
183
- error: h,
184
- dashboards: _,
185
- currentDashboard: b,
186
- fetchDashboards: S,
187
- fetchDashboard: C,
188
- createDashboard: w,
189
- updateDashboard: T,
190
- deleteDashboard: E,
191
- cloneDashboard: D,
192
- refetch: O
181
+ }, [x, w]), A = l(async () => {
182
+ await w();
183
+ }, [w]);
184
+ return u(() => {
185
+ w();
186
+ }, [w]), {
187
+ loading: h,
188
+ error: _,
189
+ dashboards: y,
190
+ currentDashboard: S,
191
+ fetchDashboards: w,
192
+ fetchDashboard: T,
193
+ createDashboard: E,
194
+ updateDashboard: D,
195
+ deleteDashboard: O,
196
+ cloneDashboard: k,
197
+ refetch: A
193
198
  };
194
199
  }
195
200
  //#endregion
196
- export { p as default };
201
+ export { h as default };
197
202
 
198
203
  //# sourceMappingURL=useDashboardOperations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useDashboardOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useDashboardOperations.ts"],"sourcesContent":["/**\n * useDashboardOperations Hook\n *\n * Provides CRUD operations for dashboards using GraphQL.\n */\n\nimport { useCallback, useState, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type { Dashboard, DashboardLayoutConfig } from '../types';\nimport { useDashboardStore } from '../store';\nimport {\n ListDashboardsDocument,\n GetDashboardDocument,\n CreateDashboardDocument,\n UpdateDashboardDocument,\n DeleteDashboardDocument,\n CloneDashboardDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardListItem {\n id: string;\n name: string;\n description?: string;\n category?: string;\n isPublic?: boolean;\n viewCount?: number;\n widgetCount?: number;\n createdAt: string;\n updatedAt: string;\n pageCount: number;\n}\n\ninterface CreateDashboardInput {\n name: string;\n description?: string;\n category?: string;\n layoutType?: string;\n tags?: string[];\n}\n\ninterface UpdateDashboardInput {\n id: string;\n name?: string;\n description?: string;\n category?: string;\n isPublic?: boolean;\n}\n\ninterface FetchDashboardOptions {\n /**\n * Silent refetch for live-canvas polling: do NOT toggle the global `loading`\n * flag (which would flash the page-level spinner) and do NOT surface a\n * transient fetch error (which would replace the board with the error screen).\n * The last good dashboard stays on screen if a poll fails.\n */\n silent?: boolean;\n}\n\ninterface DashboardOperationsResult {\n loading: boolean;\n error: Error | null;\n dashboards: DashboardListItem[];\n currentDashboard: Dashboard | null;\n\n // Operations\n fetchDashboards: (search?: string) => Promise<void>;\n fetchDashboard: (id: string, options?: FetchDashboardOptions) => Promise<Dashboard | null>;\n createDashboard: (input: CreateDashboardInput) => Promise<Dashboard | null>;\n updateDashboard: (input: UpdateDashboardInput) => Promise<Dashboard | null>;\n deleteDashboard: (id: string) => Promise<boolean>;\n cloneDashboard: (sourceDashboardId: string, name: string) => Promise<Dashboard | null>;\n refetch: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper: Normalize dashboard from GraphQL response\n// ============================================================================\n\n// Raw GraphQL response shape for dashboard data\ninterface RawDashboardData {\n id: string;\n name?: string;\n description?: string;\n workspaceId?: string;\n isPublic?: boolean;\n isTemplate?: boolean;\n layout?: Record<string, unknown>;\n category?: string;\n viewCount?: number;\n widgetCount?: number;\n createdBy?: string;\n ownerId?: string;\n createdAt?: string;\n updatedAt?: string;\n pages?: {\n edges?: Array<{\n node: {\n id: string;\n dashboardId?: string;\n name?: string;\n order?: number;\n layout?: Record<string, unknown>;\n gridColumns?: number;\n createdAt?: string;\n updatedAt?: string;\n };\n }>;\n totalCount?: number;\n };\n}\n\nfunction normalizeDashboard(data: RawDashboardData): Dashboard {\n const pages =\n data.pages?.edges?.map((edge) => ({\n id: edge.node.id,\n dashboardId: edge.node.dashboardId || data.id,\n name: edge.node.name,\n slug: edge.node.name?.toLowerCase().replace(/\\s+/g, '-') || '',\n order: edge.node.order || 0,\n layoutConfig: edge.node.layout || {},\n gridColumns: edge.node.gridColumns || 12,\n widgets: [], // Widgets loaded separately via useWidgetOperations\n createdAt: edge.node.createdAt || new Date().toISOString(),\n updatedAt: edge.node.updatedAt || new Date().toISOString(),\n })) || [];\n\n // GetDashboard caps pages at first:10 to stay under the gateway cost-limit\n // (see DashboardFullFields fragment / BOFF-2720). Surface a warning instead of\n // silently dropping pages if a dashboard ever exceeds the cap.\n const totalPages = data.pages?.totalCount ?? pages.length;\n if (totalPages > pages.length) {\n console.warn(\n `[bigconsole] Dashboard ${data.id} has ${totalPages} pages but only ${pages.length} were loaded ` +\n `(GetDashboard pages cap). Pages beyond the cap are not shown — see BOFF-2720.`\n );\n }\n\n return {\n id: data.id,\n workspaceId: data.workspaceId || '',\n name: data.name || 'Untitled Dashboard',\n description: data.description,\n slug: data.name?.toLowerCase().replace(/\\s+/g, '-') || '',\n isPublished: data.isPublic || false,\n isTemplate: data.isTemplate || false,\n layoutConfig: (data.layout as unknown as DashboardLayoutConfig) || {\n columns: 12,\n rowHeight: 100,\n gap: 10,\n margin: [10, 10],\n containerPadding: [10, 10],\n },\n pages,\n translations: [],\n createdBy: data.createdBy || data.ownerId || '',\n createdAt: data.createdAt || new Date().toISOString(),\n updatedAt: data.updatedAt || new Date().toISOString(),\n };\n}\n\nfunction normalizeDashboardListItem(data: RawDashboardData): DashboardListItem {\n // The list query selects only pages.totalCount (no edges) to stay under the\n // gateway Armor cost-limit — see DashboardListItem fragment / BOFF-2719.\n return {\n id: data.id,\n name: data.name || 'Untitled Dashboard',\n description: data.description,\n category: data.category,\n isPublic: data.isPublic,\n viewCount: data.viewCount,\n widgetCount: data.widgetCount,\n createdAt: data.createdAt || new Date().toISOString(),\n updatedAt: data.updatedAt || new Date().toISOString(),\n pageCount: data.pages?.totalCount ?? 0,\n };\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useDashboardOperations(): DashboardOperationsResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [dashboards, setDashboards] = useState<DashboardListItem[]>([]);\n\n // Get Apollo Client - will throw if not in ApolloProvider context\n const apolloClient = useApolloClient();\n\n // Store actions\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n\n // Fetch dashboards list\n const fetchDashboards = useCallback(\n async (search?: string) => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.query<{ listDashboards?: { edges?: Array<{ node: RawDashboardData }> } }>({\n query: ListDashboardsDocument,\n variables: {\n first: 50,\n search,\n includeArchived: false,\n },\n fetchPolicy: 'network-only',\n });\n\n const edges = result.data?.listDashboards?.edges || [];\n const dashboardsList = edges.map((edge) => normalizeDashboardListItem(edge.node));\n setDashboards(dashboardsList);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch dashboards');\n setError(error);\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Fetch single dashboard\n const fetchDashboard = useCallback(\n async (id: string, options?: FetchDashboardOptions): Promise<Dashboard | null> => {\n const silent = options?.silent ?? false;\n try {\n if (!silent) {\n setLoading(true);\n setError(null);\n }\n\n const result = await apolloClient.query<{ getDashboard?: RawDashboardData }>({\n query: GetDashboardDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.data?.getDashboard) {\n const dashboard = normalizeDashboard(result.data.getDashboard);\n setCurrentDashboard(dashboard);\n return dashboard;\n }\n\n return null;\n } catch (err) {\n // Silent (live-poll) failures must not wipe the board or flip the page to\n // the error screen — keep the last good dashboard on screen.\n if (!silent) {\n const error = err instanceof Error ? err : new Error('Failed to fetch dashboard');\n setError(error);\n }\n return null;\n } finally {\n if (!silent) {\n setLoading(false);\n }\n }\n },\n [apolloClient, setCurrentDashboard]\n );\n\n // Create dashboard\n const createDashboard = useCallback(\n async (input: CreateDashboardInput): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Build mutation input\n // Note: Tags require Tag IDs from the Tags service. For now, tags are stored in metadata.\n // Full tag integration would require querying/creating tags via the Tags service first.\n const mutationInput: Record<string, unknown> = {\n name: input.name,\n description: input.description,\n category: input.category || 'CUSTOM',\n layout: input.layoutType || 'GRID',\n createDefaultPage: true,\n };\n\n // Store tags in metadata until full Tags service integration\n if (input.tags && input.tags.length > 0) {\n mutationInput.metadata = {\n tags: input.tags,\n };\n }\n\n const result = await apolloClient.mutate<{ createBigConsoleDashboard?: RawDashboardData }>({\n mutation: CreateDashboardDocument,\n variables: {\n input: mutationInput,\n },\n });\n\n if (result.data?.createBigConsoleDashboard) {\n const newDashboard = normalizeDashboard(result.data.createBigConsoleDashboard);\n await fetchDashboards();\n return newDashboard;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, fetchDashboards]\n );\n\n // Update dashboard\n const updateDashboard = useCallback(\n async (input: UpdateDashboardInput): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ updateBigConsoleDashboard?: RawDashboardData }>({\n mutation: UpdateDashboardDocument,\n variables: { input },\n });\n\n if (result.data?.updateBigConsoleDashboard) {\n const updatedDashboard = normalizeDashboard(result.data.updateBigConsoleDashboard);\n if (currentDashboard?.id === input.id) {\n setCurrentDashboard(updatedDashboard);\n }\n await fetchDashboards();\n return updatedDashboard;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, currentDashboard?.id, setCurrentDashboard, fetchDashboards]\n );\n\n // Delete dashboard\n const deleteDashboard = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ deleteBigConsoleDashboard?: boolean }>({\n mutation: DeleteDashboardDocument,\n variables: { id },\n });\n\n if (result.data?.deleteBigConsoleDashboard) {\n if (currentDashboard?.id === id) {\n setCurrentDashboard(null);\n }\n await fetchDashboards();\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete dashboard');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, currentDashboard?.id, setCurrentDashboard, fetchDashboards]\n );\n\n // Clone dashboard (duplicate including widgets)\n const cloneDashboard = useCallback(\n async (sourceDashboardId: string, name: string): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ cloneDashboard?: RawDashboardData }>({\n mutation: CloneDashboardDocument,\n variables: {\n input: { sourceDashboardId, name, includeWidgets: true },\n },\n });\n\n if (result.data?.cloneDashboard) {\n const cloned = normalizeDashboard(result.data.cloneDashboard);\n await fetchDashboards();\n return cloned;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to clone dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, fetchDashboards]\n );\n\n // Refetch\n const refetch = useCallback(async () => {\n await fetchDashboards();\n }, [fetchDashboards]);\n\n // Fetch on mount\n useEffect(() => {\n fetchDashboards();\n }, [fetchDashboards]);\n\n return {\n loading,\n error,\n dashboards,\n currentDashboard,\n fetchDashboards,\n fetchDashboard,\n createDashboard,\n updateDashboard,\n deleteDashboard,\n cloneDashboard,\n refetch,\n };\n}\n\nexport default useDashboardOperations;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useDashboardOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useDashboardOperations.ts"],"sourcesContent":["/**\n * useDashboardOperations Hook\n *\n * Provides CRUD operations for dashboards using GraphQL.\n */\n\nimport { useCallback, useState, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type { Dashboard, DashboardLayoutConfig } from '../types';\nimport { useDashboardStore } from '../store';\nimport { createBigConsoleQuotaError, isBigConsoleQuotaError } from '../utils/quota';\nimport {\n ListDashboardsDocument,\n GetDashboardDocument,\n CreateDashboardDocument,\n UpdateDashboardDocument,\n DeleteDashboardDocument,\n CloneDashboardDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardListItem {\n id: string;\n name: string;\n description?: string;\n category?: string;\n isPublic?: boolean;\n viewCount?: number;\n widgetCount?: number;\n createdAt: string;\n updatedAt: string;\n pageCount: number;\n}\n\ninterface CreateDashboardInput {\n name: string;\n description?: string;\n category?: string;\n layoutType?: string;\n tags?: string[];\n}\n\ninterface UpdateDashboardInput {\n id: string;\n name?: string;\n description?: string;\n category?: string;\n isPublic?: boolean;\n}\n\ninterface FetchDashboardOptions {\n /**\n * Silent refetch for live-canvas polling: do NOT toggle the global `loading`\n * flag (which would flash the page-level spinner) and do NOT surface a\n * transient fetch error (which would replace the board with the error screen).\n * The last good dashboard stays on screen if a poll fails.\n */\n silent?: boolean;\n}\n\ninterface DashboardOperationsResult {\n loading: boolean;\n error: Error | null;\n dashboards: DashboardListItem[];\n currentDashboard: Dashboard | null;\n\n // Operations\n fetchDashboards: (search?: string) => Promise<void>;\n fetchDashboard: (id: string, options?: FetchDashboardOptions) => Promise<Dashboard | null>;\n createDashboard: (input: CreateDashboardInput) => Promise<Dashboard | null>;\n updateDashboard: (input: UpdateDashboardInput) => Promise<Dashboard | null>;\n deleteDashboard: (id: string) => Promise<boolean>;\n cloneDashboard: (sourceDashboardId: string, name: string) => Promise<Dashboard | null>;\n refetch: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper: Normalize dashboard from GraphQL response\n// ============================================================================\n\n// Raw GraphQL response shape for dashboard data\ninterface RawDashboardData {\n id: string;\n name?: string;\n description?: string;\n workspaceId?: string;\n isPublic?: boolean;\n isTemplate?: boolean;\n layout?: Record<string, unknown>;\n category?: string;\n viewCount?: number;\n widgetCount?: number;\n createdBy?: string;\n ownerId?: string;\n createdAt?: string;\n updatedAt?: string;\n pages?: {\n edges?: Array<{\n node: {\n id: string;\n dashboardId?: string;\n name?: string;\n order?: number;\n layout?: Record<string, unknown>;\n gridColumns?: number;\n createdAt?: string;\n updatedAt?: string;\n };\n }>;\n totalCount?: number;\n };\n}\n\nfunction normalizeDashboard(data: RawDashboardData): Dashboard {\n const pages =\n data.pages?.edges?.map((edge) => ({\n id: edge.node.id,\n dashboardId: edge.node.dashboardId || data.id,\n name: edge.node.name,\n slug: edge.node.name?.toLowerCase().replace(/\\s+/g, '-') || '',\n order: edge.node.order || 0,\n layoutConfig: edge.node.layout || {},\n gridColumns: edge.node.gridColumns || 12,\n widgets: [], // Widgets loaded separately via useWidgetOperations\n createdAt: edge.node.createdAt || new Date().toISOString(),\n updatedAt: edge.node.updatedAt || new Date().toISOString(),\n })) || [];\n\n // GetDashboard caps pages at first:10 to stay under the gateway cost-limit\n // (see DashboardFullFields fragment / BOFF-2720). Surface a warning instead of\n // silently dropping pages if a dashboard ever exceeds the cap.\n const totalPages = data.pages?.totalCount ?? pages.length;\n if (totalPages > pages.length) {\n console.warn(\n `[bigconsole] Dashboard ${data.id} has ${totalPages} pages but only ${pages.length} were loaded ` +\n `(GetDashboard pages cap). Pages beyond the cap are not shown — see BOFF-2720.`\n );\n }\n\n return {\n id: data.id,\n workspaceId: data.workspaceId || '',\n name: data.name || 'Untitled Dashboard',\n description: data.description,\n slug: data.name?.toLowerCase().replace(/\\s+/g, '-') || '',\n isPublished: data.isPublic || false,\n isTemplate: data.isTemplate || false,\n layoutConfig: (data.layout as unknown as DashboardLayoutConfig) || {\n columns: 12,\n rowHeight: 100,\n gap: 10,\n margin: [10, 10],\n containerPadding: [10, 10],\n },\n pages,\n translations: [],\n createdBy: data.createdBy || data.ownerId || '',\n createdAt: data.createdAt || new Date().toISOString(),\n updatedAt: data.updatedAt || new Date().toISOString(),\n };\n}\n\nfunction normalizeDashboardListItem(data: RawDashboardData): DashboardListItem {\n // The list query selects only pages.totalCount (no edges) to stay under the\n // gateway Armor cost-limit — see DashboardListItem fragment / BOFF-2719.\n return {\n id: data.id,\n name: data.name || 'Untitled Dashboard',\n description: data.description,\n category: data.category,\n isPublic: data.isPublic,\n viewCount: data.viewCount,\n widgetCount: data.widgetCount,\n createdAt: data.createdAt || new Date().toISOString(),\n updatedAt: data.updatedAt || new Date().toISOString(),\n pageCount: data.pages?.totalCount ?? 0,\n };\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useDashboardOperations(): DashboardOperationsResult {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [dashboards, setDashboards] = useState<DashboardListItem[]>([]);\n\n // Get Apollo Client - will throw if not in ApolloProvider context\n const apolloClient = useApolloClient();\n\n // Store actions\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n\n // Fetch dashboards list\n const fetchDashboards = useCallback(\n async (search?: string) => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.query<{ listDashboards?: { edges?: Array<{ node: RawDashboardData }> } }>({\n query: ListDashboardsDocument,\n variables: {\n first: 50,\n search,\n includeArchived: false,\n },\n fetchPolicy: 'network-only',\n });\n\n const edges = result.data?.listDashboards?.edges || [];\n const dashboardsList = edges.map((edge) => normalizeDashboardListItem(edge.node));\n setDashboards(dashboardsList);\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch dashboards');\n setError(error);\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n // Fetch single dashboard\n const fetchDashboard = useCallback(\n async (id: string, options?: FetchDashboardOptions): Promise<Dashboard | null> => {\n const silent = options?.silent ?? false;\n try {\n if (!silent) {\n setLoading(true);\n setError(null);\n }\n\n const result = await apolloClient.query<{ getDashboard?: RawDashboardData }>({\n query: GetDashboardDocument,\n variables: { id },\n fetchPolicy: 'network-only',\n });\n\n if (result.data?.getDashboard) {\n const dashboard = normalizeDashboard(result.data.getDashboard);\n setCurrentDashboard(dashboard);\n return dashboard;\n }\n\n return null;\n } catch (err) {\n // Silent (live-poll) failures must not wipe the board or flip the page to\n // the error screen — keep the last good dashboard on screen.\n if (!silent) {\n const error = err instanceof Error ? err : new Error('Failed to fetch dashboard');\n setError(error);\n }\n return null;\n } finally {\n if (!silent) {\n setLoading(false);\n }\n }\n },\n [apolloClient, setCurrentDashboard]\n );\n\n // Create dashboard\n const createDashboard = useCallback(\n async (input: CreateDashboardInput): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Build mutation input\n // Note: Tags require Tag IDs from the Tags service. For now, tags are stored in metadata.\n // Full tag integration would require querying/creating tags via the Tags service first.\n const mutationInput: Record<string, unknown> = {\n name: input.name,\n description: input.description,\n category: input.category || 'CUSTOM',\n layout: input.layoutType || 'GRID',\n createDefaultPage: true,\n };\n\n // Store tags in metadata until full Tags service integration\n if (input.tags && input.tags.length > 0) {\n mutationInput.metadata = {\n tags: input.tags,\n };\n }\n\n const result = await apolloClient.mutate<{ createBigConsoleDashboard?: RawDashboardData }>({\n mutation: CreateDashboardDocument,\n variables: {\n input: mutationInput,\n },\n });\n\n if (isBigConsoleQuotaError(result)) {\n throw createBigConsoleQuotaError(result);\n }\n\n if (result.data?.createBigConsoleDashboard) {\n const newDashboard = normalizeDashboard(result.data.createBigConsoleDashboard);\n await fetchDashboards();\n return newDashboard;\n }\n\n return null;\n } catch (err) {\n if (isBigConsoleQuotaError(err)) {\n throw err;\n }\n const error = err instanceof Error ? err : new Error('Failed to create dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, fetchDashboards]\n );\n\n // Update dashboard\n const updateDashboard = useCallback(\n async (input: UpdateDashboardInput): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ updateBigConsoleDashboard?: RawDashboardData }>({\n mutation: UpdateDashboardDocument,\n variables: { input },\n });\n\n if (result.data?.updateBigConsoleDashboard) {\n const updatedDashboard = normalizeDashboard(result.data.updateBigConsoleDashboard);\n if (currentDashboard?.id === input.id) {\n setCurrentDashboard(updatedDashboard);\n }\n await fetchDashboards();\n return updatedDashboard;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, currentDashboard?.id, setCurrentDashboard, fetchDashboards]\n );\n\n // Delete dashboard\n const deleteDashboard = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ deleteBigConsoleDashboard?: boolean }>({\n mutation: DeleteDashboardDocument,\n variables: { id },\n });\n\n if (result.data?.deleteBigConsoleDashboard) {\n if (currentDashboard?.id === id) {\n setCurrentDashboard(null);\n }\n await fetchDashboards();\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete dashboard');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, currentDashboard?.id, setCurrentDashboard, fetchDashboards]\n );\n\n // Clone dashboard (duplicate including widgets)\n const cloneDashboard = useCallback(\n async (sourceDashboardId: string, name: string): Promise<Dashboard | null> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<{ cloneDashboard?: RawDashboardData }>({\n mutation: CloneDashboardDocument,\n variables: {\n input: { sourceDashboardId, name, includeWidgets: true },\n },\n });\n\n if (isBigConsoleQuotaError(result)) {\n throw createBigConsoleQuotaError(result);\n }\n\n if (result.data?.cloneDashboard) {\n const cloned = normalizeDashboard(result.data.cloneDashboard);\n await fetchDashboards();\n return cloned;\n }\n\n return null;\n } catch (err) {\n if (isBigConsoleQuotaError(err)) {\n throw err;\n }\n const error = err instanceof Error ? err : new Error('Failed to clone dashboard');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, fetchDashboards]\n );\n\n // Refetch\n const refetch = useCallback(async () => {\n await fetchDashboards();\n }, [fetchDashboards]);\n\n // Fetch on mount\n useEffect(() => {\n fetchDashboards();\n }, [fetchDashboards]);\n\n return {\n loading,\n error,\n dashboards,\n currentDashboard,\n fetchDashboards,\n fetchDashboard,\n createDashboard,\n updateDashboard,\n deleteDashboard,\n cloneDashboard,\n refetch,\n };\n}\n\nexport default useDashboardOperations;\n"],"mappings":"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"}