/** * WidgetGrid - Draggable grid layout for widgets * Uses react-grid-layout for drag-and-drop functionality */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { useCallback, useMemo } from "react"; import GridLayout from "react-grid-layout"; import type { Layout, LayoutItem } from "react-grid-layout"; import type { WidgetPlacement } from "@mosaic/shared"; import { getWidgetByName } from "./WidgetRegistry"; import { BaseWidget } from "./BaseWidget"; import "react-grid-layout/css/styles.css"; // Simple classnames utility function cn(...classes: (string | undefined | null | false)[]): string { return classes.filter(Boolean).join(" "); } export interface WidgetGridProps { layout: WidgetPlacement[]; onLayoutChange: (layout: WidgetPlacement[]) => void; onRemoveWidget?: (widgetId: string) => void; isEditing?: boolean; className?: string; } export function WidgetGrid({ layout, onLayoutChange, onRemoveWidget, isEditing = false, className, }: WidgetGridProps): React.JSX.Element { // Convert WidgetPlacement to react-grid-layout Layout format const gridLayout: Layout = useMemo( () => layout.map((item): LayoutItem => { const layoutItem: LayoutItem = { i: item.i, x: item.x, y: item.y, w: item.w, h: item.h, static: !isEditing || (item.static ?? false), isDraggable: isEditing && item.isDraggable !== false, isResizable: isEditing && item.isResizable !== false, }; if (item.minW !== undefined) layoutItem.minW = item.minW; if (item.maxW !== undefined) layoutItem.maxW = item.maxW; if (item.minH !== undefined) layoutItem.minH = item.minH; if (item.maxH !== undefined) layoutItem.maxH = item.maxH; return layoutItem; }), [layout, isEditing] ); const handleLayoutChange = useCallback( (newLayout: Layout) => { const updatedLayout: WidgetPlacement[] = newLayout.map((item): WidgetPlacement => { const placement: WidgetPlacement = { i: item.i, x: item.x, y: item.y, w: item.w, h: item.h, }; if (item.minW !== undefined) placement.minW = item.minW; if (item.maxW !== undefined) placement.maxW = item.maxW; if (item.minH !== undefined) placement.minH = item.minH; if (item.maxH !== undefined) placement.maxH = item.maxH; if (item.static !== undefined) placement.static = item.static; if (item.isDraggable !== undefined) placement.isDraggable = item.isDraggable; if (item.isResizable !== undefined) placement.isResizable = item.isResizable; return placement; }); onLayoutChange(updatedLayout); }, [onLayoutChange] ); const handleRemoveWidget = useCallback( (widgetId: string) => { if (onRemoveWidget) { onRemoveWidget(widgetId); } }, [onRemoveWidget] ); // Empty state if (layout.length === 0) { return (

No widgets yet

Add widgets to customize your dashboard

); } return (
{layout.map((item) => { // Extract widget type from widget ID (format: "WidgetType-uuid") const widgetType = item.i.split("-")[0]!; const widgetDef = getWidgetByName(widgetType); if (!widgetDef) { return (
); } const WidgetComponent = widgetDef.component; return (
{ handleRemoveWidget(item.i); }, })} >
); })}
); }