Some checks failed
Build Frontend / build (push) Failing after 7s
Eliminated the ability to resize dashboard widgets by removing colSpan from WidgetConfig, related UI, and logic. Removed edit mode functionality and the EditDashboardButton, simplifying the dashboard layout and widget management. Updated drag-and-drop strategy to vertical list and incremented the storage key version.
189 lines
6.4 KiB
TypeScript
189 lines
6.4 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, useCallback } from "react"
|
|
|
|
// Per-widget settings types
|
|
export interface RecentActivitySettings {
|
|
itemCount: number // 5, 10, 15
|
|
}
|
|
|
|
export interface TopProductsSettings {
|
|
itemCount: number // 3, 5, 10
|
|
showRevenue: boolean
|
|
}
|
|
|
|
export interface OverviewSettings {
|
|
showChange: boolean // Show % change from previous period
|
|
}
|
|
|
|
export interface RevenueChartSettings {
|
|
days: number // 7, 14, 30
|
|
showComparison: boolean
|
|
}
|
|
|
|
export interface LowStockSettings {
|
|
threshold: number // Show items with stock below this
|
|
itemCount: number
|
|
}
|
|
|
|
export interface RecentCustomersSettings {
|
|
itemCount: number
|
|
showSpent: boolean
|
|
}
|
|
|
|
export interface PendingChatsSettings {
|
|
showPreview: boolean
|
|
}
|
|
|
|
export type WidgetSettings =
|
|
| { type: "quick-actions" }
|
|
| { type: "overview"; settings: OverviewSettings }
|
|
| { type: "recent-activity"; settings: RecentActivitySettings }
|
|
| { type: "top-products"; settings: TopProductsSettings }
|
|
| { type: "revenue-chart"; settings: RevenueChartSettings }
|
|
| { type: "low-stock"; settings: LowStockSettings }
|
|
| { type: "recent-customers"; settings: RecentCustomersSettings }
|
|
| { type: "pending-chats"; settings: PendingChatsSettings }
|
|
|
|
export interface WidgetConfig {
|
|
id: string
|
|
title: string
|
|
visible: boolean
|
|
order: number
|
|
settings?: Record<string, any>
|
|
}
|
|
|
|
const DEFAULT_WIDGETS: WidgetConfig[] = [
|
|
{ id: "quick-actions", title: "Quick Actions", visible: true, order: 0 },
|
|
{ id: "overview", title: "Overview", visible: true, order: 1, settings: { showChange: false } },
|
|
{ id: "recent-activity", title: "Recent Activity", visible: true, order: 2, settings: { itemCount: 10 } },
|
|
{ id: "top-products", title: "Top Products", visible: true, order: 3, settings: { itemCount: 5, showRevenue: true } },
|
|
{ id: "revenue-chart", title: "Revenue Chart", visible: false, order: 4, settings: { days: 7, showComparison: false } },
|
|
{ id: "low-stock", title: "Low Stock Alerts", visible: false, order: 5, settings: { threshold: 5, itemCount: 5 } },
|
|
{ id: "recent-customers", title: "Recent Customers", visible: false, order: 6, settings: { itemCount: 5, showSpent: true } },
|
|
{ id: "pending-chats", title: "Pending Chats", visible: false, order: 7, settings: { showPreview: true } },
|
|
]
|
|
|
|
const STORAGE_KEY = "dashboard-widget-layout-v4"
|
|
|
|
/**
|
|
* useWidgetLayout - Persist and manage dashboard widget visibility, order, and settings
|
|
*/
|
|
export function useWidgetLayout() {
|
|
const [widgets, setWidgets] = useState<WidgetConfig[]>(DEFAULT_WIDGETS)
|
|
const [isLoaded, setIsLoaded] = useState(false)
|
|
|
|
// Load from localStorage on mount
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return
|
|
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY)
|
|
if (stored) {
|
|
const parsed = JSON.parse(stored) as WidgetConfig[]
|
|
// Merge with defaults to handle new widgets added in future
|
|
const merged = DEFAULT_WIDGETS.map(defaultWidget => {
|
|
const savedWidget = parsed.find(w => w.id === defaultWidget.id)
|
|
return savedWidget
|
|
? { ...defaultWidget, ...savedWidget, settings: { ...defaultWidget.settings, ...savedWidget.settings } }
|
|
: defaultWidget
|
|
})
|
|
setWidgets(merged.sort((a, b) => a.order - b.order))
|
|
}
|
|
} catch (e) {
|
|
console.warn("Failed to load widget layout:", e)
|
|
}
|
|
setIsLoaded(true)
|
|
}, [])
|
|
|
|
// Save to localStorage whenever widgets change
|
|
useEffect(() => {
|
|
if (!isLoaded || typeof window === "undefined") return
|
|
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(widgets))
|
|
} catch (e) {
|
|
console.warn("Failed to save widget layout:", e)
|
|
}
|
|
}, [widgets, isLoaded])
|
|
|
|
const toggleWidget = useCallback((widgetId: string) => {
|
|
setWidgets(prev =>
|
|
prev.map(w => w.id === widgetId ? { ...w, visible: !w.visible } : w)
|
|
)
|
|
}, [])
|
|
|
|
const moveWidget = useCallback((widgetId: string, direction: "up" | "down") => {
|
|
setWidgets(prev => {
|
|
const index = prev.findIndex(w => w.id === widgetId)
|
|
if (index === -1) return prev
|
|
|
|
const newIndex = direction === "up" ? index - 1 : index + 1
|
|
if (newIndex < 0 || newIndex >= prev.length) return prev
|
|
|
|
const newWidgets = [...prev]
|
|
const [widget] = newWidgets.splice(index, 1)
|
|
newWidgets.splice(newIndex, 0, widget)
|
|
|
|
// Update order values
|
|
return newWidgets.map((w, i) => ({ ...w, order: i }))
|
|
})
|
|
}, [])
|
|
|
|
const updateWidgetSettings = useCallback((widgetId: string, newSettings: Record<string, any>) => {
|
|
setWidgets(prev =>
|
|
prev.map(w => w.id === widgetId
|
|
? { ...w, settings: { ...w.settings, ...newSettings } }
|
|
: w
|
|
)
|
|
)
|
|
}, [])
|
|
|
|
|
|
const getWidgetSettings = useCallback(<T extends Record<string, any>>(widgetId: string): T | undefined => {
|
|
return widgets.find(w => w.id === widgetId)?.settings as T | undefined
|
|
}, [widgets])
|
|
|
|
const resetLayout = useCallback(() => {
|
|
setWidgets(DEFAULT_WIDGETS)
|
|
}, [])
|
|
|
|
const getVisibleWidgets = useCallback(() => {
|
|
return widgets.filter(w => w.visible).sort((a, b) => a.order - b.order)
|
|
}, [widgets])
|
|
|
|
const isWidgetVisible = useCallback((widgetId: string) => {
|
|
return widgets.find(w => w.id === widgetId)?.visible ?? true
|
|
}, [widgets])
|
|
|
|
// Reorder widgets by moving activeId to overId's position
|
|
const reorderWidgets = useCallback((activeId: string, overId: string) => {
|
|
setWidgets(prev => {
|
|
const oldIndex = prev.findIndex(w => w.id === activeId)
|
|
const newIndex = prev.findIndex(w => w.id === overId)
|
|
|
|
if (oldIndex === -1 || newIndex === -1) return prev
|
|
|
|
const newWidgets = [...prev]
|
|
const [removed] = newWidgets.splice(oldIndex, 1)
|
|
newWidgets.splice(newIndex, 0, removed)
|
|
|
|
// Update order values
|
|
return newWidgets.map((w, i) => ({ ...w, order: i }))
|
|
})
|
|
}, [])
|
|
|
|
return {
|
|
widgets,
|
|
toggleWidget,
|
|
moveWidget,
|
|
reorderWidgets,
|
|
updateWidgetSettings,
|
|
getWidgetSettings,
|
|
resetLayout,
|
|
getVisibleWidgets,
|
|
isWidgetVisible,
|
|
isLoaded
|
|
}
|
|
}
|