'use client' import { useCallback, useEffect, useState } from 'react' import { GridLayout, useContainerWidth, type LayoutItem } from 'react-grid-layout' import 'react-grid-layout/css/styles.css' import { useAuth } from '@/contexts/auth-context' import { dashboard, type WidgetLayoutItem } from '@/lib/api/dashboard' import { WIDGET_REGISTRY } from '@/lib/widgets/registry' import { ROLE_DEFAULT_LAYOUTS } from '@/lib/widgets/defaults' import { getApiError } from '@/lib/api' import { toast } from '@/components/ui/toast' import { WidgetContainer } from './widget-container' import { WidgetPicker } from './widget-picker' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { HugeiconsIcon } from '@hugeicons/react' import { PencilEdit01Icon, Tick02Icon, Cancel01Icon, Add01Icon, Refresh01Icon, } from '@hugeicons/core-free-icons' // ─── Layout helpers ─────────────────────────────────────────────────────────── function toGridItems(items: WidgetLayoutItem[], editable: boolean): LayoutItem[] { return items.map(item => { const def = WIDGET_REGISTRY[item.widgetType] return { i: item.i, x: item.x, y: item.y, w: item.w, h: item.h, minW: def?.minW ?? 2, minH: def?.minH ?? 2, ...(def?.maxW ? { maxW: def.maxW } : {}), isDraggable: editable, isResizable: editable, } }) } function applyGridChanges(updated: readonly LayoutItem[], current: WidgetLayoutItem[]): WidgetLayoutItem[] { const typeMap = Object.fromEntries(current.map(item => [item.i, item.widgetType])) return updated .filter(l => typeMap[l.i]) .map(l => ({ i: l.i, widgetType: typeMap[l.i], x: l.x, y: l.y, w: l.w, h: l.h })) } // ─── Component ──────────────────────────────────────────────────────────────── export function DashboardCanvas() { const { user } = useAuth() const { width, containerRef } = useContainerWidth() const [savedLayout, setSavedLayout] = useState([]) const [editingLayout, setEditingLayout] = useState([]) const [isEditing, setIsEditing] = useState(false) const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [pickerOpen, setPickerOpen] = useState(false) const roleDefault = ROLE_DEFAULT_LAYOUTS[user.role] ?? [] // ─── Load ────────────────────────────────────────────────────────────────── const loadPreferences = useCallback(async () => { setIsLoading(true) try { const res = await dashboard.getPreferences() setSavedLayout(res.layout) } catch { setSavedLayout(roleDefault) } finally { setIsLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [user.role]) useEffect(() => { loadPreferences() }, [loadPreferences]) // ─── Edit mode ───────────────────────────────────────────────────────────── function startEditing() { setEditingLayout([...savedLayout]) setIsEditing(true) } function cancelEditing() { setEditingLayout([]) setIsEditing(false) } async function saveLayout() { setIsSaving(true) try { await dashboard.savePreferences(editingLayout) setSavedLayout(editingLayout) setIsEditing(false) toast.add({ title: 'Dashboard saved', type: 'success' }) } catch (err) { toast.add({ title: 'Failed to save', description: getApiError(err), type: 'error' }) } finally { setIsSaving(false) } } function resetToDefaults() { setEditingLayout([...roleDefault]) } // ─── Grid mutations ──────────────────────────────────────────────────────── function onGridChange(updated: readonly LayoutItem[]) { if (!isEditing) return setEditingLayout(prev => applyGridChanges(updated, prev)) } function removeWidget(id: string) { setEditingLayout(prev => prev.filter(item => item.i !== id)) } function addWidget(widgetType: string) { const def = WIDGET_REGISTRY[widgetType] if (!def) return const newItem: WidgetLayoutItem = { i: `${widgetType}-${Date.now()}`, widgetType, x: 0, y: Infinity, w: def.defaultW, h: def.defaultH, } setEditingLayout(prev => [...prev, newItem]) } // ─── Render ──────────────────────────────────────────────────────────────── const activeLayout = isEditing ? editingLayout : savedLayout if (isLoading) { return (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) } return (
{/* Header */}

Dashboard

{isEditing ? 'Drag to reposition · Resize from the corner · Add or remove widgets' : `Welcome back, ${user.fullName}`}

{isEditing ? ( <> ) : ( )}
{/* Grid */} {activeLayout.length === 0 ? (

Your dashboard is empty

Click Customize, then Add widget to get started.

{!isEditing && ( )}
) : (
{activeLayout.map(item => (
removeWidget(item.i)} />
))}
)} {/* Widget Picker */} setPickerOpen(false)} role={user.role} currentLayout={editingLayout} onAdd={addWidget} />
) }