import {Box, Text, Flex, IconButton, Spinner, Popover} from "@chakra-ui/react"; import {X, MoreVertical} from "lucide-react"; import {Responsive, WidthProvider} from "react-grid-layout"; import {useDashboardStore} from "@/store/useDashboardStore.jsx"; import {Suspense, useEffect, useRef} from 'react'; import {useColorModeValue} from "@/components/ui/color-mode"; // 1. Import database messenger import useAuthStore from "@/store/authStore"; // 2. Import registry import {WIDGET_REGISTRY} from '@/constants/widgetRegistry.jsx'; // React-Grid-Layout requires this wrapper to automatically calculate screen width const ResponsiveGridLayout = WidthProvider(Responsive); export default function MyDashboard() { // 🌗 Let's adjust for our light and dark mode const frameBg = useColorModeValue("gray.100", "gray.800"); const titleColor = useColorModeValue("gray.800", "gray.100"); const iconColor = useColorModeValue("gray.600", "gray.400"); const iconHoverBg = useColorModeValue("gray.100", "gray.700"); // ☁️ Database Connection const {user} = useAuthStore(); // ☁️ Local UI Connections const widgets = useDashboardStore(state => state.widgets); const isEditing = useDashboardStore(state => state.isEditing); const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions); const removeWidget = useDashboardStore(state => state.removeWidget); const setWidgets = useDashboardStore(state => state.setWidgets); // The injection action // 🔒 Safety lock to prevent infinite re-renders const hasHydrated = useRef(false); // ========================================== // INITIAL LOAD: Grab data from Django // ========================================== useEffect(() => { if (user?.preferences?.dashboard_layout && !hasHydrated.current) { const savedLayout = user.preferences.dashboard_layout; // Safety check: Only inject if it's actually an array of widgets. // (Prevents a crash if MariaDB returns a default empty object {}) if (Array.isArray(savedLayout)) { setWidgets(savedLayout); } hasHydrated.current = true; } }, [user, setWidgets]); // 🛠️ Format the data for RGL const rglLayout = widgets.map(w => ({ i: w.id, x: w.x, y: w.y, w: w.w, h: w.h, isResizable: false // 🚫 The magic lock! No resizing allowed globally. })); // 3. THE COMPONENT MAPPER const renderWidget = (widget) => { const registryEntry = WIDGET_REGISTRY[widget.type]; if (registryEntry && registryEntry.component) { const Component = registryEntry.component; // 1. Check if this widget type has a settings component registered const SettingsUI = registryEntry.settingsComponent; return ( // The "Frame" that holds both the Title and the Chart {/* THE TITLE BAR */} {registryEntry.name} {/* THE SETTINGS ELLIPSIS */} {SettingsUI ? ( {/* NEW: The Positioner lifts the content out of the Flexbox flow */} }> ) : ( )} {/* THE CHART CANVAS */} {/* Pass the settings object into the actual chart component */} {/* Pass widget ID to allow updating settings in widget component rather than just in settings component */} ); } // Fallback if someone asks for a widget that doesn't exist return ( Error Widget '{widget.type}' not found. ); }; return ( updateLayoutPositions(newLayout)} // Fires when user drops a widget compactType="vertical" // Auto-packs widgets to the top > {/* 3. 🎨 Paint the Widgets */} {widgets.map(widget => ( {/* 4. THE DELETE BUTTON */} {isEditing && ( removeWidget(widget.id)} onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches > )} {/* 4. SUSPENSE BOUNDARY wrapping the render function */} }> {renderWidget(widget)} ))} {/* Empty State Helper */} {widgets.length === 0 && ( Your dashboard is empty. Click Settings to add widgets. )} ); }