@burdenoff/microfe-bigconsole 2026.730.1 → 2026.730.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.
Files changed (26) hide show
  1. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +160 -127
  2. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
  3. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +73 -68
  4. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  5. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +144 -0
  6. package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -0
  7. package/dist/bigconsole/components/dashboard/TemplatePicker/TemplatePicker.js +2 -904
  8. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +82 -0
  9. package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -0
  10. package/dist/bigconsole/components/dashboard/index.js +1 -2
  11. package/dist/bigconsole/components/widgets/WidgetWrapper.js +2 -1
  12. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  13. package/dist/bigconsole/components/widgets/states/EmptyState.js +3 -3
  14. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  15. package/dist/bigconsole/graphql/storeTemplates.js +27 -0
  16. package/dist/bigconsole/graphql/storeTemplates.js.map +1 -0
  17. package/dist/bigconsole/hooks/useDashboardTemplates.js +4 -301
  18. package/dist/bigconsole/hooks/useStoreDashboardTemplates.js +137 -0
  19. package/dist/bigconsole/hooks/useStoreDashboardTemplates.js.map +1 -0
  20. package/dist/generated/global-operations.js +49 -0
  21. package/dist/generated/global-operations.js.map +1 -0
  22. package/dist/generated/wspace-operations.js +127 -117
  23. package/dist/generated/wspace-operations.js.map +1 -1
  24. package/package.json +1 -1
  25. package/dist/bigconsole/components/dashboard/TemplatePicker/TemplatePicker.js.map +0 -1
  26. package/dist/bigconsole/hooks/useDashboardTemplates.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CreateDashboardDialog.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/CreateDashboardDialog.tsx"],"sourcesContent":["/**\n * CreateDashboardDialog Component\n *\n * Modal dialog for creating a new dashboard with name and description.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n * Phase 2: Added TemplatePicker integration for creating from templates.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect, useRef, type KeyboardEvent } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Plus, LayoutDashboard, Copy, X, Tag } from 'lucide-react';\nimport { TemplatePicker } from './TemplatePicker';\nimport type { DashboardTemplateListItem } from '../../hooks';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CreateDashboardDialogProps {\n isOpen: boolean;\n isCreating?: boolean;\n onClose: () => void;\n onCreate: (data: {\n name: string;\n description: string;\n category: string;\n tags?: string[];\n templateId?: string;\n }) => void | Promise<void>;\n /** Whether to show the template picker option */\n showTemplates?: boolean;\n}\n\ntype CreateMode = 'blank' | 'template';\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const CreateDashboardDialog: FC<CreateDashboardDialogProps> = memo(function CreateDashboardDialog({\n isOpen,\n isCreating = false,\n onClose,\n onCreate,\n showTemplates = true,\n}) {\n const [mode, setMode] = useState<CreateMode>('blank');\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [tags, setTags] = useState<string[]>([]);\n const [tagInput, setTagInput] = useState('');\n const [selectedTemplate, setSelectedTemplate] = useState<DashboardTemplateListItem | null>(null);\n const [error, setError] = useState<string | null>(null);\n const nameInputRef = useRef<HTMLInputElement>(null);\n\n // Reset form when dialog opens\n useEffect(() => {\n if (isOpen) {\n setMode('blank');\n setName('');\n setDescription('');\n setTags([]);\n setTagInput('');\n setSelectedTemplate(null);\n setError(null);\n setTimeout(() => {\n nameInputRef.current?.focus();\n }, 100);\n }\n }, [isOpen]);\n\n // Handle adding a tag\n const handleAddTag = useCallback(() => {\n const trimmedTag = tagInput.trim().toLowerCase();\n if (trimmedTag && !tags.includes(trimmedTag) && tags.length < 10) {\n setTags((prev) => [...prev, trimmedTag]);\n setTagInput('');\n }\n }, [tagInput, tags]);\n\n // Handle removing a tag\n const handleRemoveTag = useCallback((tagToRemove: string) => {\n setTags((prev) => prev.filter((tag) => tag !== tagToRemove));\n }, []);\n\n // Handle tag input key press\n const handleTagKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleAddTag();\n } else if (e.key === 'Backspace' && !tagInput && tags.length > 0) {\n // Remove last tag when backspace is pressed on empty input\n setTags((prev) => prev.slice(0, -1));\n }\n },\n [handleAddTag, tagInput, tags.length]\n );\n\n // Handle template selection\n const handleTemplateSelect = useCallback((template: DashboardTemplateListItem) => {\n setSelectedTemplate(template);\n // Pre-fill name and description from template\n setName(template.name);\n setDescription(template.description || '');\n }, []);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // For template mode without selection, show error\n if (mode === 'template' && !selectedTemplate) {\n setError('Please select a template');\n return;\n }\n\n const trimmedName = name.trim();\n if (!trimmedName) {\n setError('Dashboard name is required');\n nameInputRef.current?.focus();\n return;\n }\n\n if (trimmedName.length < 3) {\n setError('Dashboard name must be at least 3 characters');\n nameInputRef.current?.focus();\n return;\n }\n\n if (trimmedName.length > 100) {\n setError('Dashboard name must be less than 100 characters');\n nameInputRef.current?.focus();\n return;\n }\n\n setError(null);\n try {\n await onCreate({\n name: trimmedName,\n description: description.trim(),\n category: 'CUSTOM',\n tags: tags.length > 0 ? tags : undefined,\n templateId: selectedTemplate?.id,\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create dashboard. Please try again.');\n }\n },\n [name, description, tags, mode, selectedTemplate, onCreate]\n );\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !isCreating && onClose()}>\n <DialogContent\n style={{ maxWidth: mode === 'template' ? '900px' : '480px', padding: 0, transition: 'max-width 0.2s ease' }}\n >\n {/* Header */}\n <DialogHeader style={{ padding: 'var(--space-pagePadding) var(--space-pagePadding) 0' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n boxShadow: 'var(--shadow-elevation-2)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div>\n <DialogTitle\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n margin: 0,\n }}\n >\n Create New Dashboard\n </DialogTitle>\n <DialogDescription\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n }}\n >\n Create a new dashboard to visualize your data\n </DialogDescription>\n </div>\n </div>\n </DialogHeader>\n\n <form onSubmit={handleSubmit}>\n {/* Mode Selection Tabs */}\n {showTemplates && (\n <div style={{ padding: 'var(--space-pagePadding) var(--space-pagePadding) 0' }}>\n <div\n style={{\n display: 'flex',\n gap: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n <button\n type=\"button\"\n onClick={() => {\n setMode('blank');\n setSelectedTemplate(null);\n }}\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: mode === 'blank' ? 'var(--color-bg-surface)' : 'transparent',\n color: mode === 'blank' ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n fontWeight: mode === 'blank' ? 'var(--font-weight-semibold)' : 'var(--font-weight-regular)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n boxShadow: mode === 'blank' ? 'var(--shadow-elevation-1)' : 'none',\n transition: 'all 0.15s ease',\n }}\n >\n <Plus style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Blank Dashboard\n </button>\n <button\n type=\"button\"\n onClick={() => setMode('template')}\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: mode === 'template' ? 'var(--color-bg-surface)' : 'transparent',\n color: mode === 'template' ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n fontWeight: mode === 'template' ? 'var(--font-weight-semibold)' : 'var(--font-weight-regular)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n boxShadow: mode === 'template' ? 'var(--shadow-elevation-1)' : 'none',\n transition: 'all 0.15s ease',\n }}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n From Template\n </button>\n </div>\n </div>\n )}\n\n {/* Template Picker (when in template mode) */}\n {mode === 'template' && (\n <div style={{ padding: 'var(--space-pagePadding)', maxHeight: '400px', overflowY: 'auto' }}>\n <TemplatePicker selectedTemplateId={selectedTemplate?.id} onSelect={handleTemplateSelect} compact />\n </div>\n )}\n\n {/* Form Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n {/* Name Field */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <label\n htmlFor=\"dashboard-name\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Dashboard Name\n <span style={{ color: 'var(--color-status-error-text)', marginLeft: 'var(--space-controlGap)' }}>\n *\n </span>\n </label>\n <Input\n ref={nameInputRef}\n id=\"dashboard-name\"\n type=\"text\"\n value={name}\n onChange={(e) => {\n setName(e.target.value);\n if (error) setError(null);\n }}\n placeholder=\"Enter dashboard name\"\n disabled={isCreating}\n style={{\n height: 'var(--size-controlHeight-md)',\n fontSize: 'var(--font-size-body-sm)',\n borderColor: error ? 'var(--color-status-error-border)' : undefined,\n }}\n />\n {error && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n marginTop: 'var(--space-controlGap)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n {error}\n </p>\n )}\n </div>\n\n {/* Description Field */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <label\n htmlFor=\"dashboard-description\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Description\n <span\n style={{\n color: 'var(--color-text-muted)',\n fontWeight: 'var(--font-weight-regular)',\n marginLeft: 'var(--space-controlGap)',\n }}\n >\n (optional)\n </span>\n </label>\n <Textarea\n id=\"dashboard-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Enter a brief description for this dashboard\"\n rows={3}\n disabled={isCreating}\n style={{\n fontSize: 'var(--font-size-body-sm)',\n resize: 'none',\n }}\n />\n </div>\n\n {/* Tags Field */}\n <div>\n <label\n htmlFor=\"dashboard-tags\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <Tag style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Tags\n <span\n style={{\n color: 'var(--color-text-muted)',\n fontWeight: 'var(--font-weight-regular)',\n }}\n >\n (optional, max 10)\n </span>\n </label>\n {/* Tag chips display */}\n {tags.length > 0 && (\n <div\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {tags.map((tag) => (\n <Badge\n key={tag}\n variant=\"secondary\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n paddingRight: '4px',\n }}\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(tag)}\n disabled={isCreating}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: '2px',\n borderRadius: '50%',\n color: 'inherit',\n opacity: 0.7,\n }}\n onMouseOver={(e) => (e.currentTarget.style.opacity = '1')}\n onMouseOut={(e) => (e.currentTarget.style.opacity = '0.7')}\n >\n <X style={{ height: '12px', width: '12px' }} />\n </button>\n </Badge>\n ))}\n </div>\n )}\n {/* Tag input */}\n <div style={{ display: 'flex', gap: 'var(--space-controlGap)' }}>\n <Input\n id=\"dashboard-tags\"\n type=\"text\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n placeholder={tags.length >= 10 ? 'Max tags reached' : 'Type a tag and press Enter'}\n disabled={isCreating || tags.length >= 10}\n style={{\n flex: 1,\n height: 'var(--size-controlHeight-md)',\n fontSize: 'var(--font-size-body-sm)',\n }}\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleAddTag}\n disabled={isCreating || !tagInput.trim() || tags.length >= 10}\n style={{ height: 'var(--size-controlHeight-md)' }}\n >\n Add\n </Button>\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-xs)',\n color: 'var(--color-text-muted)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n Press Enter to add a tag, or click Add\n </p>\n </div>\n </div>\n\n {/* Footer */}\n <DialogFooter\n style={{\n padding: 'var(--space-formGap) var(--space-pagePadding)',\n borderTop: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-sunken)',\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 'var(--space-controlGap)',\n }}\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={isCreating}\n style={{ height: 'var(--size-controlHeight-md)' }}\n >\n Cancel\n </Button>\n <Button\n type=\"submit\"\n disabled={isCreating || !name.trim()}\n style={{\n height: 'var(--size-controlHeight-md)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n {isCreating ? (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Spinner style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Creating...\n </span>\n ) : (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Plus style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Create Dashboard\n </span>\n )}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default CreateDashboardDialog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateDashboardDialog.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/CreateDashboardDialog.tsx"],"sourcesContent":["/**\n * CreateDashboardDialog Component\n *\n * Modal dialog for creating a new dashboard with name and description.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n * Phase 2: Added TemplatePicker integration for creating from templates.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect, useRef, type KeyboardEvent } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Plus, LayoutDashboard, Copy, X, Tag } from 'lucide-react';\nimport { StoreTemplatePicker } from './StoreTemplatePicker';\nimport { useStoreDashboardTemplates, type StoreDashboardTemplate } from '../../hooks/useStoreDashboardTemplates';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CreateDashboardDialogProps {\n isOpen: boolean;\n isCreating?: boolean;\n onClose: () => void;\n onCreate: (data: {\n name: string;\n description: string;\n category: string;\n tags?: string[];\n templateId?: string;\n }) => void | Promise<void>;\n /** Whether to show the template picker option */\n showTemplates?: boolean;\n /**\n * Called after a store template has been installed. Receives the new\n * dashboard id and how many widgets still need a data source, so the host can\n * navigate and surface the \"connect your data\" prompts.\n */\n onTemplateInstalled?: (result: { dashboardId: string; widgetsNeedingData: number }) => void;\n}\n\ntype CreateMode = 'blank' | 'template';\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const CreateDashboardDialog: FC<CreateDashboardDialogProps> = memo(function CreateDashboardDialog({\n isOpen,\n isCreating = false,\n onClose,\n onCreate,\n showTemplates = true,\n onTemplateInstalled,\n}) {\n const {\n templates,\n loading: templatesLoading,\n error: templatesError,\n fetchTemplateContent,\n installTemplate,\n } = useStoreDashboardTemplates({ autoLoad: showTemplates });\n const [installing, setInstalling] = useState(false);\n const [mode, setMode] = useState<CreateMode>('blank');\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [tags, setTags] = useState<string[]>([]);\n const [tagInput, setTagInput] = useState('');\n const [selectedTemplate, setSelectedTemplate] = useState<StoreDashboardTemplate | null>(null);\n const [error, setError] = useState<string | null>(null);\n const nameInputRef = useRef<HTMLInputElement>(null);\n\n // Reset form when dialog opens\n useEffect(() => {\n if (isOpen) {\n setMode('blank');\n setName('');\n setDescription('');\n setTags([]);\n setTagInput('');\n setSelectedTemplate(null);\n setError(null);\n setTimeout(() => {\n nameInputRef.current?.focus();\n }, 100);\n }\n }, [isOpen]);\n\n // Handle adding a tag\n const handleAddTag = useCallback(() => {\n const trimmedTag = tagInput.trim().toLowerCase();\n if (trimmedTag && !tags.includes(trimmedTag) && tags.length < 10) {\n setTags((prev) => [...prev, trimmedTag]);\n setTagInput('');\n }\n }, [tagInput, tags]);\n\n // Handle removing a tag\n const handleRemoveTag = useCallback((tagToRemove: string) => {\n setTags((prev) => prev.filter((tag) => tag !== tagToRemove));\n }, []);\n\n // Handle tag input key press\n const handleTagKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleAddTag();\n } else if (e.key === 'Backspace' && !tagInput && tags.length > 0) {\n // Remove last tag when backspace is pressed on empty input\n setTags((prev) => prev.slice(0, -1));\n }\n },\n [handleAddTag, tagInput, tags.length]\n );\n\n // Handle template selection\n const handleTemplateSelect = useCallback((template: StoreDashboardTemplate) => {\n setSelectedTemplate(template);\n // Pre-fill name and description from template\n setName(template.name);\n setDescription(template.description || '');\n }, []);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // For template mode without selection, show error\n if (mode === 'template' && !selectedTemplate) {\n setError('Please select a template');\n return;\n }\n\n const trimmedName = name.trim();\n if (!trimmedName) {\n setError('Dashboard name is required');\n nameInputRef.current?.focus();\n return;\n }\n\n if (trimmedName.length < 3) {\n setError('Dashboard name must be at least 3 characters');\n nameInputRef.current?.focus();\n return;\n }\n\n if (trimmedName.length > 100) {\n setError('Dashboard name must be less than 100 characters');\n nameInputRef.current?.focus();\n return;\n }\n\n setError(null);\n\n // Store templates are installed here rather than through onCreate: the\n // install carries data-sink bindings and returns which slots are still\n // unconnected, neither of which the plain create path models.\n if (mode === 'template' && selectedTemplate) {\n setInstalling(true);\n try {\n const content = await fetchTemplateContent(selectedTemplate.id);\n if (!content) {\n setError('This template has no dashboard definition attached and cannot be installed.');\n return;\n }\n\n const result = await installTemplate({\n templateId: selectedTemplate.id,\n templateName: selectedTemplate.name,\n content,\n name: trimmedName,\n });\n\n if (!result.success) {\n setError(result.errorMessage ?? 'Failed to create dashboard from template.');\n return;\n }\n\n if (result.dashboardId) {\n onTemplateInstalled?.({\n dashboardId: result.dashboardId,\n widgetsNeedingData: result.widgetsNeedingData,\n });\n }\n onClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create dashboard from template.');\n } finally {\n setInstalling(false);\n }\n return;\n }\n\n try {\n await onCreate({\n name: trimmedName,\n description: description.trim(),\n category: 'CUSTOM',\n tags: tags.length > 0 ? tags : undefined,\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create dashboard. Please try again.');\n }\n },\n [\n name,\n description,\n tags,\n mode,\n selectedTemplate,\n onCreate,\n onClose,\n fetchTemplateContent,\n installTemplate,\n onTemplateInstalled,\n ]\n );\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !isCreating && onClose()}>\n <DialogContent\n style={{ maxWidth: mode === 'template' ? '900px' : '480px', padding: 0, transition: 'max-width 0.2s ease' }}\n >\n {/* Header */}\n <DialogHeader style={{ padding: 'var(--space-pagePadding) var(--space-pagePadding) 0' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n boxShadow: 'var(--shadow-elevation-2)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div>\n <DialogTitle\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n margin: 0,\n }}\n >\n Create New Dashboard\n </DialogTitle>\n <DialogDescription\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n }}\n >\n Create a new dashboard to visualize your data\n </DialogDescription>\n </div>\n </div>\n </DialogHeader>\n\n <form onSubmit={handleSubmit}>\n {/* Mode Selection Tabs */}\n {showTemplates && (\n <div style={{ padding: 'var(--space-pagePadding) var(--space-pagePadding) 0' }}>\n <div\n style={{\n display: 'flex',\n gap: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n <button\n type=\"button\"\n onClick={() => {\n setMode('blank');\n setSelectedTemplate(null);\n }}\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: mode === 'blank' ? 'var(--color-bg-surface)' : 'transparent',\n color: mode === 'blank' ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n fontWeight: mode === 'blank' ? 'var(--font-weight-semibold)' : 'var(--font-weight-regular)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n boxShadow: mode === 'blank' ? 'var(--shadow-elevation-1)' : 'none',\n transition: 'all 0.15s ease',\n }}\n >\n <Plus style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Blank Dashboard\n </button>\n <button\n type=\"button\"\n onClick={() => setMode('template')}\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-controlGap) var(--space-formGap)',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: mode === 'template' ? 'var(--color-bg-surface)' : 'transparent',\n color: mode === 'template' ? 'var(--color-text-primary)' : 'var(--color-text-secondary)',\n fontWeight: mode === 'template' ? 'var(--font-weight-semibold)' : 'var(--font-weight-regular)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n boxShadow: mode === 'template' ? 'var(--shadow-elevation-1)' : 'none',\n transition: 'all 0.15s ease',\n }}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n From Template\n </button>\n </div>\n </div>\n )}\n\n {/* Template Picker (when in template mode) */}\n {mode === 'template' && (\n <div style={{ padding: 'var(--space-pagePadding)', maxHeight: '400px', overflowY: 'auto' }}>\n <StoreTemplatePicker\n templates={templates}\n loading={templatesLoading}\n error={templatesError}\n selectedTemplateId={selectedTemplate?.id}\n onSelect={handleTemplateSelect}\n />\n </div>\n )}\n\n {/* Form Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n {/* Name Field */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <label\n htmlFor=\"dashboard-name\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Dashboard Name\n <span style={{ color: 'var(--color-status-error-text)', marginLeft: 'var(--space-controlGap)' }}>\n *\n </span>\n </label>\n <Input\n ref={nameInputRef}\n id=\"dashboard-name\"\n type=\"text\"\n value={name}\n onChange={(e) => {\n setName(e.target.value);\n if (error) setError(null);\n }}\n placeholder=\"Enter dashboard name\"\n disabled={isCreating || installing}\n style={{\n height: 'var(--size-controlHeight-md)',\n fontSize: 'var(--font-size-body-sm)',\n borderColor: error ? 'var(--color-status-error-border)' : undefined,\n }}\n />\n {error && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n marginTop: 'var(--space-controlGap)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n {error}\n </p>\n )}\n </div>\n\n {/* Description Field */}\n <div style={{ marginBottom: 'var(--space-formGap)' }}>\n <label\n htmlFor=\"dashboard-description\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Description\n <span\n style={{\n color: 'var(--color-text-muted)',\n fontWeight: 'var(--font-weight-regular)',\n marginLeft: 'var(--space-controlGap)',\n }}\n >\n (optional)\n </span>\n </label>\n <Textarea\n id=\"dashboard-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Enter a brief description for this dashboard\"\n rows={3}\n disabled={isCreating || installing}\n style={{\n fontSize: 'var(--font-size-body-sm)',\n resize: 'none',\n }}\n />\n </div>\n\n {/* Tags Field */}\n <div>\n <label\n htmlFor=\"dashboard-tags\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <Tag style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Tags\n <span\n style={{\n color: 'var(--color-text-muted)',\n fontWeight: 'var(--font-weight-regular)',\n }}\n >\n (optional, max 10)\n </span>\n </label>\n {/* Tag chips display */}\n {tags.length > 0 && (\n <div\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {tags.map((tag) => (\n <Badge\n key={tag}\n variant=\"secondary\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n paddingRight: '4px',\n }}\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => handleRemoveTag(tag)}\n disabled={isCreating || installing}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: '2px',\n borderRadius: '50%',\n color: 'inherit',\n opacity: 0.7,\n }}\n onMouseOver={(e) => (e.currentTarget.style.opacity = '1')}\n onMouseOut={(e) => (e.currentTarget.style.opacity = '0.7')}\n >\n <X style={{ height: '12px', width: '12px' }} />\n </button>\n </Badge>\n ))}\n </div>\n )}\n {/* Tag input */}\n <div style={{ display: 'flex', gap: 'var(--space-controlGap)' }}>\n <Input\n id=\"dashboard-tags\"\n type=\"text\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n placeholder={tags.length >= 10 ? 'Max tags reached' : 'Type a tag and press Enter'}\n disabled={isCreating || installing || tags.length >= 10}\n style={{\n flex: 1,\n height: 'var(--size-controlHeight-md)',\n fontSize: 'var(--font-size-body-sm)',\n }}\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleAddTag}\n disabled={isCreating || installing || !tagInput.trim() || tags.length >= 10}\n style={{ height: 'var(--size-controlHeight-md)' }}\n >\n Add\n </Button>\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-xs)',\n color: 'var(--color-text-muted)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n Press Enter to add a tag, or click Add\n </p>\n </div>\n </div>\n\n {/* Footer */}\n <DialogFooter\n style={{\n padding: 'var(--space-formGap) var(--space-pagePadding)',\n borderTop: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-sunken)',\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 'var(--space-controlGap)',\n }}\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={onClose}\n disabled={isCreating || installing}\n style={{ height: 'var(--size-controlHeight-md)' }}\n >\n Cancel\n </Button>\n <Button\n type=\"submit\"\n disabled={isCreating || installing || !name.trim()}\n style={{\n height: 'var(--size-controlHeight-md)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n {isCreating || installing ? (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Spinner style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Creating...\n </span>\n ) : (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Plus style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n Create Dashboard\n </span>\n )}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default CreateDashboardDialog;\n"],"mappings":"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"}
@@ -15,36 +15,37 @@ import u from "./DashboardToolbar.js";
15
15
  import d from "./GlobalFiltersBar/GlobalFiltersBar.js";
16
16
  import "./GlobalFiltersBar/index.js";
17
17
  import ee from "./LiveBoardBanner.js";
18
- import te from "./PageTabsManager/PageTabsManager.js";
18
+ import te from "./TemplateSetupBanner.js";
19
+ import ne from "./PageTabsManager/PageTabsManager.js";
19
20
  import "./PageTabsManager/index.js";
20
- import ne from "./ShareDialog/ShareDialog.js";
21
+ import re from "./ShareDialog/ShareDialog.js";
21
22
  import "./ShareDialog/index.js";
22
- import re from "./WidgetPalette.js";
23
- import { WidgetInspector as ie } from "../builder/WidgetInspector.js";
23
+ import ie from "./WidgetPalette.js";
24
+ import { WidgetInspector as ae } from "../builder/WidgetInspector.js";
24
25
  import "../builder/index.js";
25
- import { calculatePositionFromDrop as ae, findNextAvailablePosition as oe } from "../../utils/widgetPositioning.js";
26
- import { memo as f, useCallback as p, useEffect as se, useMemo as ce, useRef as m, useState as h } from "react";
26
+ import { calculatePositionFromDrop as oe, findNextAvailablePosition as se } from "../../utils/widgetPositioning.js";
27
+ import { memo as f, useCallback as p, useEffect as ce, useMemo as le, useRef as m, useState as h } from "react";
27
28
  import { jsx as g, jsxs as _ } from "react/jsx-runtime";
28
29
  //#region src/bigconsole/components/dashboard/DashboardCanvas.tsx
29
- var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: ue, isSaving: de = !1, onExport: fe, pages: b = [], activePageId: pe, onPageChange: me, onPagesReorder: x, onPageAdd: he, onPageRename: ge, onPageDuplicate: _e, onPageDelete: ve, showFilters: ye = !0, showPageTabs: be = !0, enableCrossFilter: xe = !0 }) {
30
- let S = m(null), C = m(null), [w, Se] = h(1200), [Ce, T] = h(!1), [E, D] = h(null), [O, k] = h(!1), [A, j] = h(null), M = t((e) => e.viewMode === "edit"), { workspaceId: N, apiGatewayUrl: P, authToken: F } = r(), { installedWidgets: we, loading: Te } = c({
30
+ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: ue, onSave: de, isSaving: b = !1, onExport: fe, pages: x = [], activePageId: pe, onPageChange: me, onPagesReorder: S, onPageAdd: he, onPageRename: ge, onPageDuplicate: _e, onPageDelete: ve, showFilters: ye = !0, showPageTabs: be = !0, enableCrossFilter: xe = !0 }) {
31
+ let C = m(null), w = m(null), [T, Se] = h(1200), [Ce, E] = h(!1), [D, O] = h(null), [k, A] = h(!1), [we, j] = h(null), M = t((e) => e.viewMode === "edit"), { workspaceId: N, apiGatewayUrl: P, authToken: F } = r(), { installedWidgets: Te, loading: Ee } = c({
31
32
  workspaceId: N,
32
33
  apiGatewayUrl: P,
33
34
  authToken: F,
34
35
  enabled: M
35
- }), { definitions: Ee } = a({
36
+ }), { definitions: De } = a({
36
37
  workspaceId: N,
37
38
  apiGatewayUrl: P,
38
39
  authToken: F,
39
40
  enabled: M
40
- }), I = t((e) => e.zoomLevel), L = t((e) => e.sidebarCollapsed), R = t((e) => e.toggleSidebar), z = !L, B = t((e) => e.setGlobalFilterValues), De = p((e) => {
41
+ }), I = t((e) => e.zoomLevel), L = t((e) => e.sidebarCollapsed), R = t((e) => e.toggleSidebar), z = !L, B = t((e) => e.setGlobalFilterValues), Oe = p((e) => {
41
42
  B(e);
42
- }, [B]), { createWidget: V } = o(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = ce(() => {
43
+ }, [B]), { createWidget: V } = o(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = le(() => {
43
44
  let e = Array.from(H.values());
44
45
  return v ? e.filter((e) => e.pageId === v) : e;
45
- }, [H, v]), Oe = e((e) => e.selectedWidgetId), W = e((e) => e.selectWidget), G = e((e) => e.addWidget), K = e((e) => e.updateWidgetPosition), q = e((e) => e.configPanelOpen), ke = e((e) => e.closeConfigPanel);
46
- se(() => {
47
- let e = S.current;
46
+ }, [H, v]), ke = e((e) => e.selectedWidgetId), W = e((e) => e.selectWidget), G = e((e) => e.addWidget), K = e((e) => e.updateWidgetPosition), q = e((e) => e.configPanelOpen), Ae = e((e) => e.closeConfigPanel);
47
+ ce(() => {
48
+ let e = C.current;
48
49
  if (!e) return;
49
50
  let t = () => {
50
51
  Se(e.offsetWidth);
@@ -55,7 +56,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
55
56
  }
56
57
  return window.addEventListener("resize", t), () => window.removeEventListener("resize", t);
57
58
  }, []);
58
- let Ae = p((e) => {
59
+ let J = p((e) => {
59
60
  e.forEach((e) => {
60
61
  K(e.i, {
61
62
  x: e.x,
@@ -64,17 +65,17 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
64
65
  height: e.h
65
66
  });
66
67
  });
67
- }, [K]), J = p((e) => {
68
+ }, [K]), je = p((e) => {
68
69
  W(e);
69
70
  }, [W]), Y = p(async (e, t) => {
70
- D(null);
71
+ O(null);
71
72
  let n = t?.definition || (e.type ? i(e.type) : null), r = {
72
73
  width: n?.defaultSize?.width ?? 4,
73
74
  height: n?.defaultSize?.height ?? 4
74
75
  }, a;
75
76
  if (t?.position) a = t.position;
76
77
  else {
77
- let { x: e, y: t } = oe(U, r, { cols: 12 });
78
+ let { x: e, y: t } = se(U, r, { cols: 12 });
78
79
  a = {
79
80
  x: e,
80
81
  y: t,
@@ -102,7 +103,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
102
103
  W(t.id);
103
104
  return;
104
105
  }
105
- D("Could not create the widget. Check the connection and try again.");
106
+ O("Could not create the widget. Check the connection and try again.");
106
107
  return;
107
108
  }
108
109
  let s = {
@@ -135,12 +136,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
135
136
  G,
136
137
  W,
137
138
  U
138
- ]), je = p((e) => {
139
- k(!0), j(e);
140
- }, []), Me = p(() => {
141
- k(!1), j(null);
142
- }, []), Ne = p((e) => {
143
- e.preventDefault(), k(!1), j(null);
139
+ ]), Me = p((e) => {
140
+ A(!0), j(e);
141
+ }, []), Ne = p(() => {
142
+ A(!1), j(null);
143
+ }, []), Pe = p((e) => {
144
+ e.preventDefault(), A(!1), j(null);
144
145
  try {
145
146
  let t = e.dataTransfer.getData("application/json");
146
147
  if (!t) return;
@@ -154,12 +155,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
154
155
  defaultConfig: r.defaultConfig,
155
156
  defaultSize: r.defaultSize || a.defaultSize
156
157
  });
157
- let s = S.current?.getBoundingClientRect();
158
+ let s = C.current?.getBoundingClientRect();
158
159
  if (!s) return;
159
160
  let c = {
160
161
  width: a.defaultSize.width,
161
162
  height: a.defaultSize.height
162
- }, { x: l, y: u } = ae(e.clientX, e.clientY, s, w, c, { cols: 12 }), d = {
163
+ }, { x: l, y: u } = oe(e.clientX, e.clientY, s, T, c, { cols: 12 }), d = {
163
164
  x: l,
164
165
  y: u,
165
166
  width: c.width,
@@ -176,9 +177,9 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
176
177
  } catch (e) {
177
178
  console.error("Failed to handle widget drop:", e);
178
179
  }
179
- }, [w, Y]), Pe = p((e) => {
180
+ }, [T, Y]), Fe = p((e) => {
180
181
  e.preventDefault(), e.dataTransfer.dropEffect = "copy";
181
- }, []), Fe = p((e, t) => {
182
+ }, []), Ie = p((e, t) => {
182
183
  let n = t || i(e);
183
184
  Y({
184
185
  type: e,
@@ -191,8 +192,8 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
191
192
  L && R(), window.requestAnimationFrame(() => {
192
193
  document.querySelector("input[placeholder=\"Search widgets...\"]")?.focus();
193
194
  });
194
- }, [L, R]), { exportPNG: Q, exportPDF: $, isExporting: Ie } = s({
195
- containerRef: C,
195
+ }, [L, R]), { exportPNG: Q, exportPDF: $, isExporting: Le } = s({
196
+ containerRef: w,
196
197
  name: y || "dashboard"
197
198
  });
198
199
  return /* @__PURE__ */ _("div", {
@@ -204,9 +205,9 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
204
205
  onAddWidget: Z,
205
206
  onTogglePalette: X,
206
207
  isPaletteOpen: z,
207
- onPaste: le,
208
- onSave: ue,
209
- isSaving: de,
208
+ onPaste: ue,
209
+ onSave: de,
210
+ isSaving: b,
210
211
  onExport: fe,
211
212
  onExportImage: p(() => {
212
213
  Q();
@@ -214,35 +215,39 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
214
215
  onExportPdf: p(() => {
215
216
  $();
216
217
  }, [$]),
217
- isExportingImage: Ie,
218
- onShare: () => T(!0)
218
+ isExportingImage: Le,
219
+ onShare: () => E(!0)
219
220
  }),
220
- E && /* @__PURE__ */ _("div", {
221
+ D && /* @__PURE__ */ _("div", {
221
222
  className: "flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text",
222
223
  role: "alert",
223
- children: [/* @__PURE__ */ g("span", { children: E }), /* @__PURE__ */ g("button", {
224
+ children: [/* @__PURE__ */ g("span", { children: D }), /* @__PURE__ */ g("button", {
224
225
  type: "button",
225
226
  className: "font-medium underline underline-offset-2",
226
- onClick: () => D(null),
227
+ onClick: () => O(null),
227
228
  children: "Dismiss"
228
229
  })]
229
230
  }),
231
+ /* @__PURE__ */ g(te, {
232
+ widgets: U,
233
+ isEditMode: M
234
+ }),
230
235
  /* @__PURE__ */ g(ee, {
231
236
  widgets: U,
232
237
  isEditMode: M
233
238
  }),
234
239
  ye && /* @__PURE__ */ g(d, {
235
240
  dashboardId: f,
236
- onFilterChange: De,
241
+ onFilterChange: Oe,
237
242
  collapsible: !0
238
243
  }),
239
- be && (M || b.length > 0) && /* @__PURE__ */ g(te, {
240
- pages: b,
241
- activePageId: pe || b[0]?.id,
244
+ be && (M || x.length > 0) && /* @__PURE__ */ g(ne, {
245
+ pages: x,
246
+ activePageId: pe || x[0]?.id,
242
247
  editable: M,
243
248
  onPageSelect: me || (() => {}),
244
- onPagesReorder: x ? (e) => {
245
- x(e.map((e) => b.find((t) => t.id === e)).filter(Boolean));
249
+ onPagesReorder: S ? (e) => {
250
+ S(e.map((e) => x.find((t) => t.id === e)).filter(Boolean));
246
251
  } : void 0,
247
252
  onPageCreate: he,
248
253
  onPageRename: ge,
@@ -255,30 +260,30 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
255
260
  marginRight: q ? "var(--bc-inspector-width, 420px)" : void 0,
256
261
  transition: "margin-right 0.2s ease"
257
262
  },
258
- children: [M && /* @__PURE__ */ g(re, {
263
+ children: [M && /* @__PURE__ */ g(ie, {
259
264
  isOpen: z,
260
265
  onClose: X,
261
- onWidgetSelect: Fe,
262
- onDragStart: je,
263
- onDragEnd: Me,
264
- installedWidgets: we,
265
- installedWidgetsLoading: Te,
266
- definitions: Ee
266
+ onWidgetSelect: Ie,
267
+ onDragStart: Me,
268
+ onDragEnd: Ne,
269
+ installedWidgets: Te,
270
+ installedWidgetsLoading: Ee,
271
+ definitions: De
267
272
  }), /* @__PURE__ */ _("div", {
268
- ref: S,
273
+ ref: C,
269
274
  className: "flex-1 min-w-0 overflow-auto bg-bg-sunken",
270
- onDrop: Ne,
271
- onDragOver: Pe,
275
+ onDrop: Pe,
276
+ onDragOver: Fe,
272
277
  onClick: () => {
273
278
  M && W(null);
274
279
  },
275
280
  style: {
276
281
  position: "relative",
277
- border: O ? "2px dashed var(--color-action-primary-bg)" : "none",
278
- backgroundColor: O ? "var(--color-bg-hover)" : void 0,
282
+ border: k ? "2px dashed var(--color-action-primary-bg)" : "none",
283
+ backgroundColor: k ? "var(--color-bg-hover)" : void 0,
279
284
  transition: "border-color 0.2s, background-color 0.2s"
280
285
  },
281
- children: [O && /* @__PURE__ */ g("div", {
286
+ children: [k && /* @__PURE__ */ g("div", {
282
287
  style: {
283
288
  position: "absolute",
284
289
  top: "50%",
@@ -300,12 +305,12 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
300
305
  },
301
306
  children: [
302
307
  "Drop to add ",
303
- /* @__PURE__ */ g("strong", { children: A }),
308
+ /* @__PURE__ */ g("strong", { children: we }),
304
309
  " widget"
305
310
  ]
306
311
  })
307
312
  }), /* @__PURE__ */ g("div", {
308
- ref: C,
313
+ ref: w,
309
314
  className: "min-h-full origin-top-left transition-transform",
310
315
  style: {
311
316
  transform: `scale(${I / 100})`,
@@ -317,10 +322,10 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
317
322
  children: /* @__PURE__ */ g(l, {
318
323
  widgets: U,
319
324
  isEditMode: M,
320
- width: w,
321
- onLayoutChange: Ae,
322
- onWidgetSelect: J,
323
- selectedWidgetId: Oe
325
+ width: T,
326
+ onLayoutChange: J,
327
+ onWidgetSelect: je,
328
+ selectedWidgetId: ke
324
329
  })
325
330
  }) : /* @__PURE__ */ _("div", {
326
331
  className: "flex flex-col items-center justify-center min-h-[400px] p-8",
@@ -370,15 +375,15 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
370
375
  })]
371
376
  })]
372
377
  }),
373
- /* @__PURE__ */ g(ie, {
378
+ /* @__PURE__ */ g(ae, {
374
379
  isOpen: q,
375
- onClose: ke
380
+ onClose: Ae
376
381
  }),
377
- /* @__PURE__ */ g(ne, {
382
+ /* @__PURE__ */ g(re, {
378
383
  dashboardId: f,
379
384
  dashboardName: y || "Dashboard",
380
385
  isOpen: Ce,
381
- onClose: () => T(!1)
386
+ onClose: () => E(!1)
382
387
  })
383
388
  ]
384
389
  });
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError('Could not create the widget. Check the connection and try again.');\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n document.querySelector<HTMLInputElement>('input[placeholder=\"Search widgets...\"]')?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div\n className=\"flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text\"\n role=\"alert\"\n >\n <span>{widgetCreateError}</span>\n <button\n type=\"button\"\n className=\"font-medium underline underline-offset-2\"\n onClick={() => setWidgetCreateError(null)}\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n className=\"flex flex-1 min-w-0 overflow-hidden\"\n style={{\n marginRight: configPanelOpen ? 'var(--bc-inspector-width, 420px)' : undefined,\n transition: 'margin-right 0.2s ease',\n }}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-sunken\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the &quot;Add Widget&quot; button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={openWidgetPalette}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { TemplateSetupBanner } from './TemplateSetupBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError('Could not create the widget. Check the connection and try again.');\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n document.querySelector<HTMLInputElement>('input[placeholder=\"Search widgets...\"]')?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div\n className=\"flex items-center justify-between gap-3 border-b border-status-error-border bg-status-error-bg px-4 py-2 text-sm text-status-error-text\"\n role=\"alert\"\n >\n <span>{widgetCreateError}</span>\n <button\n type=\"button\"\n className=\"font-medium underline underline-offset-2\"\n onClick={() => setWidgetCreateError(null)}\n >\n Dismiss\n </button>\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <TemplateSetupBanner widgets={widgetsArray} isEditMode={isEditMode} />\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n className=\"flex flex-1 min-w-0 overflow-hidden\"\n style={{\n marginRight: configPanelOpen ? 'var(--bc-inspector-width, 420px)' : undefined,\n transition: 'margin-right 0.2s ease',\n }}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-sunken\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the &quot;Add Widget&quot; button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={openWidgetPalette}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}