Remove widget resizing and edit mode from dashboard
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.
This commit is contained in:
g
2026-01-12 11:13:25 +00:00
parent 9acd18955e
commit a6e6cd0757
5 changed files with 23 additions and 103 deletions

View File

@@ -6,7 +6,7 @@ import QuickActions from "./quick-actions"
import RecentActivity from "./recent-activity" import RecentActivity from "./recent-activity"
import { WidgetSettings } from "./widget-settings" import { WidgetSettings } from "./widget-settings"
import { WidgetSettingsModal } from "./widget-settings-modal" import { WidgetSettingsModal } from "./widget-settings-modal"
import { DashboardEditor, EditDashboardButton } from "./dashboard-editor" import { DashboardEditor } from "./dashboard-editor"
import { DraggableWidget } from "./draggable-widget" import { DraggableWidget } from "./draggable-widget"
import RevenueWidget from "./revenue-widget" import RevenueWidget from "./revenue-widget"
import LowStockWidget from "./low-stock-widget" import LowStockWidget from "./low-stock-widget"
@@ -46,9 +46,8 @@ export default function Content({ username, orderStats }: ContentProps) {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { toast } = useToast(); const { toast } = useToast();
const { widgets, toggleWidget, moveWidget, reorderWidgets, resetLayout, isWidgetVisible, updateWidgetSettings, updateWidgetColSpan } = useWidgetLayout(); const { widgets, toggleWidget, moveWidget, reorderWidgets, resetLayout, isWidgetVisible, updateWidgetSettings } = useWidgetLayout();
const [configuredWidget, setConfiguredWidget] = useState<WidgetConfig | null>(null); const [configuredWidget, setConfiguredWidget] = useState<WidgetConfig | null>(null);
const [isEditMode, setIsEditMode] = useState(false);
// Initialize with a default quote to match server-side rendering, then randomize on client // Initialize with a default quote to match server-side rendering, then randomize on client
const [randomQuote, setRandomQuote] = useState({ text: "Loading wisdom...", author: "..." }); const [randomQuote, setRandomQuote] = useState({ text: "Loading wisdom...", author: "..." });
@@ -232,10 +231,6 @@ export default function Content({ username, orderStats }: ContentProps) {
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<EditDashboardButton
isEditMode={isEditMode}
onToggle={() => setIsEditMode(!isEditMode)}
/>
<WidgetSettings <WidgetSettings
widgets={widgets} widgets={widgets}
onToggle={toggleWidget} onToggle={toggleWidget}
@@ -248,30 +243,24 @@ export default function Content({ username, orderStats }: ContentProps) {
<DashboardEditor <DashboardEditor
widgets={widgets} widgets={widgets}
isEditMode={isEditMode} isEditMode={false}
onToggleEditMode={() => setIsEditMode(false)} onToggleEditMode={() => { }}
onReorder={reorderWidgets} onReorder={reorderWidgets}
onReset={resetLayout} onReset={resetLayout}
> >
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 auto-rows-min"> <div className="space-y-10">
{widgets.map((widget) => { {widgets.map((widget) => {
if (!widget.visible && !isEditMode) return null; if (!widget.visible) return null;
return ( return (
<DraggableWidget <DraggableWidget
key={widget.id} key={widget.id}
widget={widget} widget={widget}
isEditMode={isEditMode} isEditMode={false}
onConfigure={() => setConfiguredWidget(widget)} onConfigure={() => setConfiguredWidget(widget)}
onToggleVisibility={() => toggleWidget(widget.id)} onToggleVisibility={() => toggleWidget(widget.id)}
> >
{!widget.visible && isEditMode ? (
<div className="opacity-40 grayscale pointer-events-none h-full">
{renderWidget(widget)} {renderWidget(widget)}
</div>
) : (
renderWidget(widget)
)}
</DraggableWidget> </DraggableWidget>
); );
})} })}
@@ -283,9 +272,8 @@ export default function Content({ username, orderStats }: ContentProps) {
widget={configuredWidget} widget={configuredWidget}
open={!!configuredWidget} open={!!configuredWidget}
onOpenChange={(open) => !open && setConfiguredWidget(null)} onOpenChange={(open) => !open && setConfiguredWidget(null)}
onSave={(widgetId, settings, colSpan) => { onSave={(widgetId, settings) => {
updateWidgetSettings(widgetId, settings); updateWidgetSettings(widgetId, settings);
if (colSpan !== undefined) updateWidgetColSpan(widgetId, colSpan);
}} }}
/> />
</div> </div>

View File

@@ -15,7 +15,7 @@ import {
import { import {
SortableContext, SortableContext,
sortableKeyboardCoordinates, sortableKeyboardCoordinates,
rectSortingStrategy, verticalListSortingStrategy,
arrayMove, arrayMove,
} from "@dnd-kit/sortable" } from "@dnd-kit/sortable"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
@@ -81,7 +81,7 @@ export function DashboardEditor({
> >
<SortableContext <SortableContext
items={widgets.map(w => w.id)} items={widgets.map(w => w.id)}
strategy={rectSortingStrategy} strategy={verticalListSortingStrategy}
> >
{children} {children}
</SortableContext> </SortableContext>
@@ -126,32 +126,3 @@ export function DashboardEditor({
) )
} }
// Edit button component to add to the header
export function EditDashboardButton({
isEditMode,
onToggle
}: {
isEditMode: boolean
onToggle: () => void
}) {
return (
<Button
variant={isEditMode ? "default" : "outline"}
size="sm"
className="h-8 gap-2"
onClick={onToggle}
>
{isEditMode ? (
<>
<X className="h-4 w-4" />
<span className="hidden sm:inline">Cancel</span>
</>
) : (
<>
<Edit3 className="h-4 w-4" />
<span className="hidden sm:inline">Edit Layout</span>
</>
)}
</Button>
)
}

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import React from "react" import React, { useState, useEffect, useRef } from "react"
import { useSortable } from "@dnd-kit/sortable" import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities" import { CSS } from "@dnd-kit/utilities"
import { GripVertical, Settings, X, Eye, EyeOff } from "lucide-react" import { GripVertical, Settings, X, Eye, EyeOff } from "lucide-react"
@@ -41,21 +41,12 @@ export function DraggableWidget({
zIndex: isDragging ? 1000 : 1, zIndex: isDragging ? 1000 : 1,
} }
const colSpanClasses = {
1: "lg:col-span-1",
2: "lg:col-span-2",
3: "lg:col-span-3",
4: "lg:col-span-4",
}[widget.colSpan || 4] || "lg:col-span-4"
return ( return (
<div <div
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
className={cn( className={cn(
"relative group", "relative group",
colSpanClasses,
"md:col-span-2", // Default to 2 columns on tablet
isEditMode && "ring-2 ring-primary ring-offset-2 ring-offset-background rounded-lg", isEditMode && "ring-2 ring-primary ring-offset-2 ring-offset-background rounded-lg",
isDragging && "z-50 shadow-2xl" isDragging && "z-50 shadow-2xl"
)} )}

View File

@@ -28,25 +28,23 @@ interface WidgetSettingsModalProps {
widget: WidgetConfig | null widget: WidgetConfig | null
open: boolean open: boolean
onOpenChange: (open: boolean) => void onOpenChange: (open: boolean) => void
onSave: (widgetId: string, settings: Record<string, any>, colSpan: number) => void onSave: (widgetId: string, settings: Record<string, any>) => void
} }
export function WidgetSettingsModal({ widget, open, onOpenChange, onSave }: WidgetSettingsModalProps) { export function WidgetSettingsModal({ widget, open, onOpenChange, onSave }: WidgetSettingsModalProps) {
const [localSettings, setLocalSettings] = useState<Record<string, any>>({}) const [localSettings, setLocalSettings] = useState<Record<string, any>>({})
const [localColSpan, setLocalColSpan] = useState<number>(4)
// Initialize local settings when widget changes // Initialize local settings when widget changes
const handleOpenChange = (isOpen: boolean) => { const handleOpenChange = (isOpen: boolean) => {
if (isOpen && widget) { if (isOpen && widget) {
setLocalSettings({ ...widget.settings }) setLocalSettings({ ...widget.settings })
setLocalColSpan(widget.colSpan || 4)
} }
onOpenChange(isOpen) onOpenChange(isOpen)
} }
const handleSave = () => { const handleSave = () => {
if (widget) { if (widget) {
onSave(widget.id, localSettings, localColSpan) onSave(widget.id, localSettings)
onOpenChange(false) onOpenChange(false)
} }
} }
@@ -72,27 +70,6 @@ export function WidgetSettingsModal({ widget, open, onOpenChange, onSave }: Widg
<ScrollArea className="max-h-[60vh] -mr-4 pr-4"> <ScrollArea className="max-h-[60vh] -mr-4 pr-4">
<div className="space-y-6 py-4"> <div className="space-y-6 py-4">
{/* Resize Selection */}
<div className="space-y-3 pb-6 border-b border-border/40">
<Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Widget Display</Label>
<div className="flex items-center justify-between">
<Label htmlFor="colSpan" className="text-sm font-medium">Widget Width</Label>
<Select
value={String(localColSpan)}
onValueChange={(v) => setLocalColSpan(parseInt(v))}
>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Small (1/4)</SelectItem>
<SelectItem value="2">Medium (1/2)</SelectItem>
<SelectItem value="3">Large (3/4)</SelectItem>
<SelectItem value="4">Full Width</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-4"> <div className="space-y-4">
{/* Recent Activity Settings */} {/* Recent Activity Settings */}

View File

@@ -50,22 +50,21 @@ export interface WidgetConfig {
title: string title: string
visible: boolean visible: boolean
order: number order: number
colSpan: number // 1, 2, 3, 4 (full)
settings?: Record<string, any> settings?: Record<string, any>
} }
const DEFAULT_WIDGETS: WidgetConfig[] = [ const DEFAULT_WIDGETS: WidgetConfig[] = [
{ id: "quick-actions", title: "Quick Actions", visible: true, order: 0, colSpan: 4 }, { id: "quick-actions", title: "Quick Actions", visible: true, order: 0 },
{ id: "overview", title: "Overview", visible: true, order: 1, colSpan: 4, settings: { showChange: false } }, { id: "overview", title: "Overview", visible: true, order: 1, settings: { showChange: false } },
{ id: "recent-activity", title: "Recent Activity", visible: true, order: 2, colSpan: 2, settings: { itemCount: 10 } }, { id: "recent-activity", title: "Recent Activity", visible: true, order: 2, settings: { itemCount: 10 } },
{ id: "top-products", title: "Top Products", visible: true, order: 3, colSpan: 2, settings: { itemCount: 5, showRevenue: true } }, { 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, colSpan: 2, settings: { days: 7, showComparison: false } }, { 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, colSpan: 2, settings: { threshold: 5, itemCount: 5 } }, { 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, colSpan: 2, settings: { itemCount: 5, showSpent: true } }, { 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, colSpan: 2, settings: { showPreview: true } }, { id: "pending-chats", title: "Pending Chats", visible: false, order: 7, settings: { showPreview: true } },
] ]
const STORAGE_KEY = "dashboard-widget-layout-v3" const STORAGE_KEY = "dashboard-widget-layout-v4"
/** /**
* useWidgetLayout - Persist and manage dashboard widget visibility, order, and settings * useWidgetLayout - Persist and manage dashboard widget visibility, order, and settings
@@ -140,11 +139,6 @@ export function useWidgetLayout() {
) )
}, []) }, [])
const updateWidgetColSpan = useCallback((widgetId: string, colSpan: number) => {
setWidgets(prev =>
prev.map(w => w.id === widgetId ? { ...w, colSpan } : w)
)
}, [])
const getWidgetSettings = useCallback(<T extends Record<string, any>>(widgetId: string): T | undefined => { const getWidgetSettings = useCallback(<T extends Record<string, any>>(widgetId: string): T | undefined => {
return widgets.find(w => w.id === widgetId)?.settings as T | undefined return widgets.find(w => w.id === widgetId)?.settings as T | undefined
@@ -185,7 +179,6 @@ export function useWidgetLayout() {
moveWidget, moveWidget,
reorderWidgets, reorderWidgets,
updateWidgetSettings, updateWidgetSettings,
updateWidgetColSpan,
getWidgetSettings, getWidgetSettings,
resetLayout, resetLayout,
getVisibleWidgets, getVisibleWidgets,