feat(dashboard): adiciona widgets movíveis e ocultáveis

- Cria SortableWidget com @dnd-kit para drag-and-drop
- Cria DashboardGridEditable com modo de edição
- Cria WidgetSettingsDialog para gerenciar visibilidade
- Cria server actions para persistir preferências
This commit is contained in:
Felipe Coutinho
2026-01-20 16:36:33 +00:00
parent d209b7401c
commit 540b250a47
4 changed files with 513 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Switch } from "@/components/ui/switch";
import { widgetsConfig } from "@/lib/dashboard/widgets/widgets-config";
import { RiRefreshLine, RiSettings4Line } from "@remixicon/react";
import { useState } from "react";
type WidgetSettingsDialogProps = {
hiddenWidgets: string[];
onToggleWidget: (widgetId: string) => void;
onReset: () => void;
};
export function WidgetSettingsDialog({
hiddenWidgets,
onToggleWidget,
onReset,
}: WidgetSettingsDialogProps) {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="gap-2">
<RiSettings4Line className="size-4" />
Widgets
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Configurar Widgets</DialogTitle>
<DialogDescription>
Escolha quais widgets deseja exibir no seu dashboard.
</DialogDescription>
</DialogHeader>
<div className="max-h-[400px] overflow-y-auto py-4">
<div className="space-y-3">
{widgetsConfig.map((widget) => {
const isVisible = !hiddenWidgets.includes(widget.id);
return (
<div
key={widget.id}
className="flex items-center justify-between gap-4 rounded-lg border p-3"
>
<div className="flex items-center gap-3 min-w-0">
<span className="text-primary shrink-0">{widget.icon}</span>
<div className="min-w-0">
<p className="text-sm font-medium truncate">
{widget.title}
</p>
<p className="text-xs text-muted-foreground truncate">
{widget.subtitle}
</p>
</div>
</div>
<Switch
checked={isVisible}
onCheckedChange={() => onToggleWidget(widget.id)}
/>
</div>
);
})}
</div>
</div>
<DialogFooter>
<Button
variant="outline"
size="sm"
onClick={onReset}
className="gap-2"
>
<RiRefreshLine className="size-4" />
Restaurar Padrão
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}