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.
292 lines
15 KiB
TypeScript
292 lines
15 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
} from "@/components/ui/dialog"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Switch } from "@/components/ui/switch"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { WidgetConfig } from "@/hooks/useWidgetLayout"
|
|
import { Settings2 } from "lucide-react"
|
|
|
|
interface WidgetSettingsModalProps {
|
|
widget: WidgetConfig | null
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
onSave: (widgetId: string, settings: Record<string, any>, colSpan: number) => void
|
|
}
|
|
|
|
export function WidgetSettingsModal({ widget, open, onOpenChange, onSave }: WidgetSettingsModalProps) {
|
|
const [localSettings, setLocalSettings] = useState<Record<string, any>>({})
|
|
const [localColSpan, setLocalColSpan] = useState<number>(4)
|
|
|
|
// Initialize local settings when widget changes
|
|
const handleOpenChange = (isOpen: boolean) => {
|
|
if (isOpen && widget) {
|
|
setLocalSettings({ ...widget.settings })
|
|
setLocalColSpan(widget.colSpan || 4)
|
|
}
|
|
onOpenChange(isOpen)
|
|
}
|
|
|
|
const handleSave = () => {
|
|
if (widget) {
|
|
onSave(widget.id, localSettings, localColSpan)
|
|
onOpenChange(false)
|
|
}
|
|
}
|
|
|
|
const updateSetting = (key: string, value: any) => {
|
|
setLocalSettings(prev => ({ ...prev, [key]: value }))
|
|
}
|
|
|
|
if (!widget) return null
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<Settings2 className="h-5 w-5" />
|
|
{widget.title} Settings
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Customize how this widget displays on your dashboard.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<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">
|
|
{/* Recent Activity Settings */}
|
|
{widget.id === "recent-activity" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="itemCount">Number of items</Label>
|
|
<Select
|
|
value={String(localSettings.itemCount || 10)}
|
|
onValueChange={(v) => updateSetting("itemCount", parseInt(v))}
|
|
>
|
|
<SelectTrigger className="w-24">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="5">5</SelectItem>
|
|
<SelectItem value="10">10</SelectItem>
|
|
<SelectItem value="15">15</SelectItem>
|
|
<SelectItem value="20">20</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Top Products Settings */}
|
|
{widget.id === "top-products" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="itemCount">Number of products</Label>
|
|
<Select
|
|
value={String(localSettings.itemCount || 5)}
|
|
onValueChange={(v) => updateSetting("itemCount", parseInt(v))}
|
|
>
|
|
<SelectTrigger className="w-24">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="3">3</SelectItem>
|
|
<SelectItem value="5">5</SelectItem>
|
|
<SelectItem value="10">10</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="showRevenue">Show revenue</Label>
|
|
<Switch
|
|
id="showRevenue"
|
|
checked={localSettings.showRevenue ?? true}
|
|
onCheckedChange={(checked) => updateSetting("showRevenue", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Revenue Chart Settings */}
|
|
{widget.id === "revenue-chart" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="days">Time period</Label>
|
|
<Select
|
|
value={String(localSettings.days || 7)}
|
|
onValueChange={(v) => updateSetting("days", parseInt(v))}
|
|
>
|
|
<SelectTrigger className="w-32">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="7">Last 7 days</SelectItem>
|
|
<SelectItem value="14">Last 14 days</SelectItem>
|
|
<SelectItem value="30">Last 30 days</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="showComparison">Show comparison</Label>
|
|
<Switch
|
|
id="showComparison"
|
|
checked={localSettings.showComparison ?? false}
|
|
onCheckedChange={(checked) => updateSetting("showComparison", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Low Stock Settings */}
|
|
{widget.id === "low-stock" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="threshold">Stock threshold</Label>
|
|
<Input
|
|
id="threshold"
|
|
type="number"
|
|
className="w-24"
|
|
value={localSettings.threshold || 5}
|
|
onChange={(e) => updateSetting("threshold", parseInt(e.target.value) || 5)}
|
|
min={1}
|
|
max={100}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="itemCount">Max items to show</Label>
|
|
<Select
|
|
value={String(localSettings.itemCount || 5)}
|
|
onValueChange={(v) => updateSetting("itemCount", parseInt(v))}
|
|
>
|
|
<SelectTrigger className="w-24">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="3">3</SelectItem>
|
|
<SelectItem value="5">5</SelectItem>
|
|
<SelectItem value="10">10</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recent Customers Settings */}
|
|
{widget.id === "recent-customers" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="itemCount">Number of customers</Label>
|
|
<Select
|
|
value={String(localSettings.itemCount || 5)}
|
|
onValueChange={(v) => updateSetting("itemCount", parseInt(v))}
|
|
>
|
|
<SelectTrigger className="w-24">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="3">3</SelectItem>
|
|
<SelectItem value="5">5</SelectItem>
|
|
<SelectItem value="10">10</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="showSpent">Show amount spent</Label>
|
|
<Switch
|
|
id="showSpent"
|
|
checked={localSettings.showSpent ?? true}
|
|
onCheckedChange={(checked) => updateSetting("showSpent", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Pending Chats Settings */}
|
|
{widget.id === "pending-chats" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="showPreview">Show message preview</Label>
|
|
<Switch
|
|
id="showPreview"
|
|
checked={localSettings.showPreview ?? true}
|
|
onCheckedChange={(checked) => updateSetting("showPreview", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Overview Settings */}
|
|
{widget.id === "overview" && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="showChange">Show percentage change</Label>
|
|
<Switch
|
|
id="showChange"
|
|
checked={localSettings.showChange ?? false}
|
|
onCheckedChange={(checked) => updateSetting("showChange", checked)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Quick Actions - no settings */}
|
|
{widget.id === "quick-actions" && (
|
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
This widget has no customizable settings.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleSave}>
|
|
Save Changes
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|