Some checks failed
Build Frontend / build (push) Failing after 7s
Introduces a modular dashboard system with draggable, configurable widgets including revenue, low stock, recent customers, and pending chats. Adds a dashboard editor for layout customization, widget visibility, and settings. Refactors dashboard content to use the new widget system and improves UI consistency and interactivity.
122 lines
4.3 KiB
TypeScript
122 lines
4.3 KiB
TypeScript
"use client"
|
|
|
|
import React from "react"
|
|
import { useSortable } from "@dnd-kit/sortable"
|
|
import { CSS } from "@dnd-kit/utilities"
|
|
import { GripVertical, Settings, X, Eye, EyeOff } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { cn } from "@/lib/utils/styles"
|
|
import { WidgetConfig } from "@/hooks/useWidgetLayout"
|
|
|
|
interface DraggableWidgetProps {
|
|
widget: WidgetConfig
|
|
children: React.ReactNode
|
|
isEditMode: boolean
|
|
onRemove?: () => void
|
|
onConfigure?: () => void
|
|
onToggleVisibility?: () => void
|
|
}
|
|
|
|
export function DraggableWidget({
|
|
widget,
|
|
children,
|
|
isEditMode,
|
|
onRemove,
|
|
onConfigure,
|
|
onToggleVisibility
|
|
}: DraggableWidgetProps) {
|
|
const {
|
|
attributes,
|
|
listeners,
|
|
setNodeRef,
|
|
transform,
|
|
transition,
|
|
isDragging,
|
|
} = useSortable({ id: widget.id })
|
|
|
|
const style = {
|
|
transform: CSS.Transform.toString(transform),
|
|
transition,
|
|
opacity: isDragging ? 0.5 : 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 (
|
|
<div
|
|
ref={setNodeRef}
|
|
style={style}
|
|
className={cn(
|
|
"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",
|
|
isDragging && "z-50 shadow-2xl"
|
|
)}
|
|
>
|
|
{isEditMode && (
|
|
<>
|
|
{/* Edit Mode Overlay */}
|
|
<div className="absolute inset-0 rounded-lg border-2 border-dashed border-primary/30 pointer-events-none z-10 group-hover:border-primary/60 transition-colors" />
|
|
|
|
{/* Drag Handle */}
|
|
<div
|
|
{...attributes}
|
|
{...listeners}
|
|
className="absolute top-2 left-2 z-20 cursor-grab active:cursor-grabbing bg-background/90 backdrop-blur-sm rounded-md p-1.5 shadow-md border border-border opacity-0 group-hover:opacity-100 transition-opacity"
|
|
>
|
|
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
|
</div>
|
|
|
|
{/* Widget Title Badge */}
|
|
<div className="absolute top-2 left-1/2 -translate-x-1/2 z-20 bg-primary text-primary-foreground text-xs font-medium px-2 py-1 rounded-full shadow-md opacity-0 group-hover:opacity-100 transition-opacity">
|
|
{widget.title}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="absolute top-2 right-2 z-20 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
{onConfigure && (
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="h-7 w-7 shadow-md"
|
|
onClick={onConfigure}
|
|
>
|
|
<Settings className="h-3.5 w-3.5" />
|
|
</Button>
|
|
)}
|
|
{onToggleVisibility && (
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="h-7 w-7 shadow-md"
|
|
onClick={onToggleVisibility}
|
|
>
|
|
{widget.visible ? (
|
|
<EyeOff className="h-3.5 w-3.5" />
|
|
) : (
|
|
<Eye className="h-3.5 w-3.5" />
|
|
)}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Widget Content */}
|
|
<div className={cn(
|
|
"h-full transition-transform",
|
|
isDragging && "scale-[1.02]"
|
|
)}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|