From 6c5c86fcb4c7de1305042b6d641d89924b9220ba Mon Sep 17 00:00:00 2001 From: AV77web Date: Wed, 24 Jun 2026 18:53:46 +0200 Subject: [PATCH] sidebar e navigazione; CRUD: table, dialog, badge; Toast per feedback operazioni --- package-lock.json | 32 ++ package.json | 3 + src/App.tsx | 23 +- src/components/ConfirmDeleteDialog.tsx | 50 +++ src/components/FormField.tsx | 24 ++ src/components/StatoConsegnaBadge.tsx | 16 + src/components/layout/AppSidebar.tsx | 67 ++++ src/components/layout/DashboardLayout.tsx | 15 + src/components/ui/alert-dialog.tsx | 185 +++++++++++ src/components/ui/badge.tsx | 52 +++ src/components/ui/dialog.tsx | 160 ++++++++++ src/components/ui/dropdown-menu.tsx | 268 ++++++++++++++++ src/components/ui/select.tsx | 201 ++++++++++++ src/components/ui/separator.tsx | 23 ++ src/components/ui/sonner.tsx | 16 + src/components/ui/table.tsx | 114 +++++++ src/components/ui/textarea.tsx | 18 ++ src/lib/errors.ts | 13 + src/pages/ClientiPage.tsx | 335 +++++++++++++++++++ src/pages/ConsegnePage.tsx | 371 ++++++++++++++++++++++ src/pages/Dashboard.tsx | 83 +++-- src/pages/UtentiPage.tsx | 331 +++++++++++++++++++ 22 files changed, 2379 insertions(+), 21 deletions(-) create mode 100644 src/components/ConfirmDeleteDialog.tsx create mode 100644 src/components/FormField.tsx create mode 100644 src/components/StatoConsegnaBadge.tsx create mode 100644 src/components/layout/AppSidebar.tsx create mode 100644 src/components/layout/DashboardLayout.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/lib/errors.ts create mode 100644 src/pages/ClientiPage.tsx create mode 100644 src/pages/ConsegnePage.tsx create mode 100644 src/pages/UtentiPage.tsx diff --git a/package-lock.json b/package-lock.json index c7ab063..5f3e324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,12 @@ "@tailwindcss/vite": "^4.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^1.21.0", + "next-themes": "^0.4.6", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.18.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.1" }, @@ -2542,6 +2545,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz", + "integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2599,6 +2611,16 @@ "dev": true, "license": "MIT" }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/node-releases": { "version": "2.0.48", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", @@ -2888,6 +2910,16 @@ "node": ">=8" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index 4b2d38c..c5255bb 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,12 @@ "@tailwindcss/vite": "^4.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^1.21.0", + "next-themes": "^0.4.6", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.18.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.1" }, diff --git a/src/App.tsx b/src/App.tsx index ca9b3dd..cff0f21 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,14 @@ import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import GuestRoute from "./components/GuestRoute"; import ProtectedRoute from "./components/ProtectedRoute"; +import DashboardLayout from "./components/layout/DashboardLayout"; +import { Toaster } from "./components/ui/sonner"; import { AuthProvider } from "./context/AuthContext"; +import ClientiPage from "./pages/ClientiPage"; +import ConsegnePage from "./pages/ConsegnePage"; import Dashboard from "./pages/Dashboard"; import Login from "./pages/Login"; +import UtentiPage from "./pages/UtentiPage"; export default function App() { return ( @@ -12,17 +17,29 @@ export default function App() { } /> - + } - /> + > + } /> + } /> + } /> + + + + } + /> + } /> } /> + ); } diff --git a/src/components/ConfirmDeleteDialog.tsx b/src/components/ConfirmDeleteDialog.tsx new file mode 100644 index 0000000..137d993 --- /dev/null +++ b/src/components/ConfirmDeleteDialog.tsx @@ -0,0 +1,50 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +export default function ConfirmDeleteDialog({ + open, + onOpenChange, + title, + description, + onConfirm, + loading = false, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: string; + onConfirm: () => void; + loading?: boolean; +}) { + return ( + + + + {title} + {description} + + + Annulla + { + e.preventDefault(); + onConfirm(); + }} + > + {loading ? "Eliminazione..." : "Elimina"} + + + + + ); +} diff --git a/src/components/FormField.tsx b/src/components/FormField.tsx new file mode 100644 index 0000000..e0d5a37 --- /dev/null +++ b/src/components/FormField.tsx @@ -0,0 +1,24 @@ +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +export default function FormField({ + id, + label, + error, + className, + children, +}: { + id: string; + label: string; + error?: string; + className?: string; + children: React.ReactNode; +}) { + return ( +
+ + {children} + {error &&

{error}

} +
+ ); +} diff --git a/src/components/StatoConsegnaBadge.tsx b/src/components/StatoConsegnaBadge.tsx new file mode 100644 index 0000000..cbbee7f --- /dev/null +++ b/src/components/StatoConsegnaBadge.tsx @@ -0,0 +1,16 @@ +import { Badge } from "@/components/ui/badge"; +import type { StatoConsegna } from "@/types"; + +const statoVariant: Record< + StatoConsegna, + "default" | "secondary" | "outline" | "destructive" +> = { + "da ritirare": "secondary", + "in consegna": "default", + "in giacenza": "outline", + "in deposito": "secondary", +}; + +export default function StatoConsegnaBadge({ stato }: { stato: StatoConsegna }) { + return {stato}; +} diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx new file mode 100644 index 0000000..d33f1cc --- /dev/null +++ b/src/components/layout/AppSidebar.tsx @@ -0,0 +1,67 @@ +import { LayoutDashboard, LogOut, Package, UserCog, Users } from "lucide-react"; +import { NavLink } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { useAuth } from "@/context/AuthContext"; +import { cn } from "@/lib/utils"; + +const navItems = [ + { to: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, + { to: "/clienti", label: "Clienti", icon: Users }, + { to: "/consegne", label: "Consegne", icon: Package }, + { to: "/utenti", label: "Utenti", icon: UserCog, adminOnly: true }, +] as const; + +export default function AppSidebar() { + const { user, logout } = useAuth(); + const isAdmin = user?.ruolo === "Amministratore"; + + return ( + + ); +} diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx new file mode 100644 index 0000000..410a7ac --- /dev/null +++ b/src/components/layout/DashboardLayout.tsx @@ -0,0 +1,15 @@ +import { Outlet } from "react-router-dom"; +import AppSidebar from "./AppSidebar"; + +export default function DashboardLayout() { + return ( +
+ +
+
+ +
+
+
+ ); +} diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..ead776d --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,185 @@ +import * as React from "react" +import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { + return +} + +function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { + return ( + + ) +} + +function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: AlertDialogPrimitive.Backdrop.Props) { + return ( + + ) +} + +function AlertDialogContent({ + className, + size = "default", + ...props +}: AlertDialogPrimitive.Popup.Props & { + size?: "default" | "sm" +}) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogMedia({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( +