From b7fd15ffcd2866d4832a78e92d38c2fe726cba55 Mon Sep 17 00:00:00 2001
From: AV77web
Date: Sun, 31 May 2026 17:41:45 +0200
Subject: [PATCH] inserita la gestione degli utenti
---
src/app/(main)/layout.tsx | 29 +-
src/app/(management)/layout.tsx | 28 +-
src/app/(management)/stabilimenti/page.tsx | 16 +-
src/app/(management)/utenti/page.tsx | 29 +-
.../[idmacchina]/[idart]/route.ts | 9 +-
.../api/art-macchine-parti/assegna/route.ts | 9 +-
src/app/api/macchine/[id]/route.ts | 10 +-
src/app/api/macchine/route.ts | 8 +-
src/app/api/magazzini/[id]/route.ts | 15 +-
src/app/api/magazzini/route.ts | 8 +-
src/app/api/movimenti/route.ts | 14 +-
src/app/api/stabilimenti/[id]/route.ts | 15 +-
src/app/api/stabilimenti/route.ts | 8 +-
src/app/api/utenti/[id]/route.ts | 88 ++++
src/app/api/utenti/route.ts | 36 ++
src/auth.ts | 16 +-
src/components/AppShell.tsx | 38 ++
src/components/ArticleCard.tsx | 7 +-
src/components/ArticlesList.tsx | 5 +
src/components/ArticoliMacchinaModal.tsx | 7 +-
src/components/MacchineTree.tsx | 54 ++-
src/components/Navbar.tsx | 128 +++---
src/components/TreeNode.tsx | 17 +-
src/components/UtentiManager.tsx | 389 ++++++++++++++++++
src/contexts/PermissionsContext.tsx | 37 ++
src/lib/auth/requireSession.ts | 62 +++
src/lib/permissions.ts | 72 ++++
src/lib/users/index.ts | 187 +++++++++
src/types/user.ts | 20 +-
29 files changed, 1145 insertions(+), 216 deletions(-)
create mode 100644 src/app/api/utenti/[id]/route.ts
create mode 100644 src/app/api/utenti/route.ts
create mode 100644 src/components/AppShell.tsx
create mode 100644 src/components/UtentiManager.tsx
create mode 100644 src/contexts/PermissionsContext.tsx
create mode 100644 src/lib/auth/requireSession.ts
create mode 100644 src/lib/permissions.ts
create mode 100644 src/lib/users/index.ts
diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index 73f7760..67475a9 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -1,28 +1,5 @@
-import { redirect } from "next/navigation";
+import AppShell from "@/src/components/AppShell";
-import { auth } from "@/src/auth";
-import SignOutButton from "@/src/components/auth/SignOutButton";
-import Navbar from "@/src/components/Navbar";
-import { StabilimentoProvider } from "@/src/contexts/StabilimentoContext";
-
-export default async function MainLayout({
- children,
-
-}: {
- children: React.ReactNode;
-}) {
- const session = await auth();
-
- if (!session?.user) {
- redirect("/login");
- }
-
- return (
-
-
- } />
- {children}
-
-
- );
+export default function MainLayout({ children }: { children: React.ReactNode }) {
+ return {children};
}
diff --git a/src/app/(management)/layout.tsx b/src/app/(management)/layout.tsx
index 1573611..263a174 100644
--- a/src/app/(management)/layout.tsx
+++ b/src/app/(management)/layout.tsx
@@ -1,27 +1,5 @@
-import { redirect } from "next/navigation";
+import AppShell from "@/src/components/AppShell";
-import { auth } from "@/src/auth";
-import SignOutButton from "@/src/components/auth/SignOutButton";
-import Navbar from "@/src/components/Navbar";
-import { StabilimentoProvider } from "@/src/contexts/StabilimentoContext";
-
-export default async function ManagementLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const session = await auth();
-
- if (!session?.user) {
- redirect("/login");
- }
-
- return (
-
-
- } />
- {children}
-
-
- );
+export default function ManagementLayout({ children }: { children: React.ReactNode }) {
+ return {children};
}
diff --git a/src/app/(management)/stabilimenti/page.tsx b/src/app/(management)/stabilimenti/page.tsx
index 4afb86e..2a1a12a 100644
--- a/src/app/(management)/stabilimenti/page.tsx
+++ b/src/app/(management)/stabilimenti/page.tsx
@@ -1,8 +1,20 @@
-"use client";
+import { redirect } from "next/navigation";
+import { auth } from "@/src/auth";
import StabilimentiManager from "@/src/components/StabilimentiManager";
+import { getPermissions, normalizeRole } from "@/src/lib/permissions";
+
+export default async function StabilimentiPage() {
+ const session = await auth();
+ if (!session?.user) {
+ redirect("/login");
+ }
+
+ const ruolo = normalizeRole(session.user.ruolo);
+ if (!ruolo || !getPermissions(ruolo).canManageStabilimenti) {
+ redirect("/");
+ }
-export default function StabilimentiPage() {
return (
diff --git a/src/app/(management)/utenti/page.tsx b/src/app/(management)/utenti/page.tsx
index 1cf70ad..b5d074f 100644
--- a/src/app/(management)/utenti/page.tsx
+++ b/src/app/(management)/utenti/page.tsx
@@ -1,8 +1,23 @@
-import Link from "next/link";
+import { redirect } from "next/navigation";
-export default function Utenti() {
- return <>
-
Utenti
-
Utenti
- >
-}
\ No newline at end of file
+import { auth } from "@/src/auth";
+import UtentiManager from "@/src/components/UtentiManager";
+import { getPermissions, normalizeRole } from "@/src/lib/permissions";
+
+export default async function UtentiPage() {
+ const session = await auth();
+ if (!session?.user) {
+ redirect("/login");
+ }
+
+ const ruolo = normalizeRole(session.user.ruolo);
+ if (!ruolo || !getPermissions(ruolo).canManageUtenti) {
+ redirect("/");
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/app/api/art-macchine-parti/[idmacchina]/[idart]/route.ts b/src/app/api/art-macchine-parti/[idmacchina]/[idart]/route.ts
index 5e36ebd..bdac23b 100644
--- a/src/app/api/art-macchine-parti/[idmacchina]/[idart]/route.ts
+++ b/src/app/api/art-macchine-parti/[idmacchina]/[idart]/route.ts
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
-import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import { rimuoviArticoloDaMacchina } from "@/src/lib/artMacchineParti";
interface RouteParams {
@@ -8,11 +8,8 @@ interface RouteParams {
}
export async function DELETE(request: Request, { params }: RouteParams) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canRemoveArticoloMacchina);
+ if (session instanceof NextResponse) return session;
const { idmacchina, idart } = await params;
const macchinaId = parseInt(idmacchina, 10);
diff --git a/src/app/api/art-macchine-parti/assegna/route.ts b/src/app/api/art-macchine-parti/assegna/route.ts
index bc51065..f888593 100644
--- a/src/app/api/art-macchine-parti/assegna/route.ts
+++ b/src/app/api/art-macchine-parti/assegna/route.ts
@@ -1,14 +1,11 @@
import { NextResponse } from "next/server";
-import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import { assegnaArticoloMacchina } from "@/src/lib/artMacchineParti";
export async function POST(request: Request) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canAssegnaArticoliMacchina);
+ if (session instanceof NextResponse) return session;
try {
const body = await request.json();
diff --git a/src/app/api/macchine/[id]/route.ts b/src/app/api/macchine/[id]/route.ts
index 8c9dd77..1eba494 100644
--- a/src/app/api/macchine/[id]/route.ts
+++ b/src/app/api/macchine/[id]/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import {
getMacchinaById,
updateMacchina,
@@ -47,11 +48,8 @@ export async function GET(request: Request, { params }: RouteParams) {
}
export async function PUT(request: Request, { params }: RouteParams) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canEditMacchine);
+ if (session instanceof NextResponse) return session;
const { id } = await params;
const macchinaId = parseInt(id, 10);
@@ -116,7 +114,7 @@ export async function DELETE(request: Request, { params }: RouteParams) {
// Body vuoto o non JSON, continua senza magazzino
}
- const user = session.user.email || session.user.name || "unknown";
+ const user = session.user.email || session.user.utente || "unknown";
const deleted = await deleteMacchina(macchinaId, idMagazzinoRientro, user);
if (!deleted) {
diff --git a/src/app/api/macchine/route.ts b/src/app/api/macchine/route.ts
index afad7f9..17b3e99 100644
--- a/src/app/api/macchine/route.ts
+++ b/src/app/api/macchine/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import {
listMacchine,
createMacchina,
@@ -28,11 +29,8 @@ export async function GET() {
}
export async function POST(request: Request) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canEditMacchine);
+ if (session instanceof NextResponse) return session;
try {
const body = await request.json();
diff --git a/src/app/api/magazzini/[id]/route.ts b/src/app/api/magazzini/[id]/route.ts
index d473712..1d44779 100644
--- a/src/app/api/magazzini/[id]/route.ts
+++ b/src/app/api/magazzini/[id]/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import { getMagazzinoById, updateMagazzino, deleteMagazzino } from "@/src/lib/stabilimenti";
interface RouteContext {
@@ -40,11 +41,8 @@ export async function GET(_request: Request, context: RouteContext) {
}
export async function PUT(request: Request, context: RouteContext) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
const magazzino = await updateMagazzino(
@@ -61,11 +59,8 @@ export async function PUT(request: Request, context: RouteContext) {
}
export async function DELETE(_request: Request, context: RouteContext) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
await deleteMagazzino(await readId(context.params));
diff --git a/src/app/api/magazzini/route.ts b/src/app/api/magazzini/route.ts
index 599e6be..e063f03 100644
--- a/src/app/api/magazzini/route.ts
+++ b/src/app/api/magazzini/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import { listMagazzini, createMagazzino } from "@/src/lib/stabilimenti";
export async function GET(request: Request) {
@@ -29,11 +30,8 @@ export async function GET(request: Request) {
}
export async function POST(request: Request) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
const magazzino = await createMagazzino(await request.json());
diff --git a/src/app/api/movimenti/route.ts b/src/app/api/movimenti/route.ts
index 16fac46..2a2decd 100644
--- a/src/app/api/movimenti/route.ts
+++ b/src/app/api/movimenti/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { forbid, requireSession } from "@/src/lib/auth/requireSession";
import { listMovimenti, createMovimento } from "@/src/lib/movimenti";
import { getArticleById } from "@/src/lib/articles";
import type { TipoMovimento } from "@/src/types/movimento";
@@ -35,11 +36,8 @@ export async function GET(request: Request) {
}
export async function POST(request: Request) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requireSession();
+ if (session instanceof NextResponse) return session;
try {
const body = await request.json();
@@ -72,6 +70,10 @@ export async function POST(request: Request) {
);
}
+ if (tipo === "scarico" && !session.permissions.canScaricoArticoli) {
+ return forbid("Scarico non consentito per il tuo ruolo");
+ }
+
const articolo = await getArticleById(id_articolo);
if (!articolo) {
return NextResponse.json(
@@ -81,7 +83,7 @@ export async function POST(request: Request) {
}
const tipoMovimentoDB = tipo === "carico" ? 1 : 2;
- const utente = session.user.name || session.user.email || "Sistema";
+ const utente = session.user.utente || session.user.name || session.user.email || "Sistema";
const movimentoId = await createMovimento({
id_articolo,
diff --git a/src/app/api/stabilimenti/[id]/route.ts b/src/app/api/stabilimenti/[id]/route.ts
index 3bff566..cef75aa 100644
--- a/src/app/api/stabilimenti/[id]/route.ts
+++ b/src/app/api/stabilimenti/[id]/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import {
getStabilimentoById,
updateStabilimento,
@@ -44,11 +45,8 @@ export async function GET(_request: Request, context: RouteContext) {
}
export async function PUT(request: Request, context: RouteContext) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
const stabilimento = await updateStabilimento(
@@ -65,11 +63,8 @@ export async function PUT(request: Request, context: RouteContext) {
}
export async function DELETE(_request: Request, context: RouteContext) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
await deleteStabilimento(await readId(context.params));
diff --git a/src/app/api/stabilimenti/route.ts b/src/app/api/stabilimenti/route.ts
index 926f05d..f5b286a 100644
--- a/src/app/api/stabilimenti/route.ts
+++ b/src/app/api/stabilimenti/route.ts
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/src/auth";
+import { requirePermission } from "@/src/lib/auth/requireSession";
import {
listStabilimenti,
getStabilimentiWithMagazzini,
@@ -35,11 +36,8 @@ export async function GET(request: Request) {
}
export async function POST(request: Request) {
- const session = await auth();
-
- if (!session?.user) {
- return NextResponse.json({ error: "Non autorizzato" }, { status: 401 });
- }
+ const session = await requirePermission((p) => p.canManageStabilimenti);
+ if (session instanceof NextResponse) return session;
try {
const stabilimento = await createStabilimento(await request.json());
diff --git a/src/app/api/utenti/[id]/route.ts b/src/app/api/utenti/[id]/route.ts
new file mode 100644
index 0000000..18e017f
--- /dev/null
+++ b/src/app/api/utenti/[id]/route.ts
@@ -0,0 +1,88 @@
+import { NextResponse } from "next/server";
+
+import { forbid, requirePermission } from "@/src/lib/auth/requireSession";
+import { deleteUser, getUserById, updateUser } from "@/src/lib/users";
+
+type RouteContext = { params: Promise<{ id: string }> };
+
+export async function GET(_request: Request, context: RouteContext) {
+ const session = await requirePermission((p) => p.canManageUtenti);
+ if (session instanceof NextResponse) return session;
+
+ const { id } = await context.params;
+ const userId = parseInt(id, 10);
+
+ if (!Number.isInteger(userId) || userId <= 0) {
+ return NextResponse.json({ error: "ID non valido" }, { status: 400 });
+ }
+
+ try {
+ const utente = await getUserById(userId);
+ if (!utente) {
+ return NextResponse.json({ error: "Utente non trovato" }, { status: 404 });
+ }
+ return NextResponse.json({ utente });
+ } catch (error) {
+ console.error("[API utenti/id] Errore GET:", error);
+ return NextResponse.json({ error: "Errore nel recupero" }, { status: 500 });
+ }
+}
+
+export async function PUT(request: Request, context: RouteContext) {
+ const session = await requirePermission((p) => p.canManageUtenti);
+ if (session instanceof NextResponse) return session;
+
+ const { id } = await context.params;
+ const userId = parseInt(id, 10);
+ const currentId = parseInt(session.user.id, 10);
+
+ if (!Number.isInteger(userId) || userId <= 0) {
+ return NextResponse.json({ error: "ID non valido" }, { status: 400 });
+ }
+
+ try {
+ const body = await request.json();
+
+ if (userId === currentId && body.ruolo && body.ruolo !== session.user.ruolo) {
+ return forbid("Non puoi modificare il tuo ruolo.");
+ }
+ if (userId === currentId && body.attivo === 0) {
+ return forbid("Non puoi disattivare il tuo account.");
+ }
+
+ const utente = await updateUser(userId, body);
+ return NextResponse.json({ utente });
+ } catch (error) {
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : "Errore aggiornamento" },
+ { status: 400 },
+ );
+ }
+}
+
+export async function DELETE(_request: Request, context: RouteContext) {
+ const session = await requirePermission((p) => p.canManageUtenti);
+ if (session instanceof NextResponse) return session;
+
+ const { id } = await context.params;
+ const userId = parseInt(id, 10);
+ const currentId = parseInt(session.user.id, 10);
+
+ if (!Number.isInteger(userId) || userId <= 0) {
+ return NextResponse.json({ error: "ID non valido" }, { status: 400 });
+ }
+
+ if (userId === currentId) {
+ return forbid("Non puoi eliminare il tuo account.");
+ }
+
+ try {
+ await deleteUser(userId);
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : "Errore eliminazione" },
+ { status: 400 },
+ );
+ }
+}
diff --git a/src/app/api/utenti/route.ts b/src/app/api/utenti/route.ts
new file mode 100644
index 0000000..06d3cea
--- /dev/null
+++ b/src/app/api/utenti/route.ts
@@ -0,0 +1,36 @@
+import { NextResponse } from "next/server";
+
+import { requirePermission } from "@/src/lib/auth/requireSession";
+import { createUser, listUsers } from "@/src/lib/users";
+
+export async function GET() {
+ const session = await requirePermission((p) => p.canManageUtenti);
+ if (session instanceof NextResponse) return session;
+
+ try {
+ const utenti = await listUsers();
+ return NextResponse.json({ utenti });
+ } catch (error) {
+ console.error("[API utenti] Errore GET:", error);
+ return NextResponse.json(
+ { error: "Errore nel recupero degli utenti" },
+ { status: 500 },
+ );
+ }
+}
+
+export async function POST(request: Request) {
+ const session = await requirePermission((p) => p.canManageUtenti);
+ if (session instanceof NextResponse) return session;
+
+ try {
+ const body = await request.json();
+ const utente = await createUser(body);
+ return NextResponse.json({ utente }, { status: 201 });
+ } catch (error) {
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : "Errore creazione utente" },
+ { status: 400 },
+ );
+ }
+}
diff --git a/src/auth.ts b/src/auth.ts
index a0aa73b..5fff024 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -4,7 +4,8 @@ import Credentials from "next-auth/providers/credentials";
import type { ResultSetHeader, RowDataPacket } from "mysql2";
import { db } from "@/src/lib/db";
-import type { DbUser, UserRole } from "@/src/types/user";
+import { normalizeRole } from "@/src/lib/permissions";
+import type { DbUser } from "@/src/types/user";
type DbUserRow = DbUser & RowDataPacket;
@@ -34,10 +35,6 @@ function authError(message: string, error: unknown, details?: Record
{
let rows: DbUserRow[];
@@ -66,7 +63,8 @@ async function findActiveUser(identifier: string): Promise {
return null;
}
- if (!isUserRole(user.ruolo)) {
+ const ruolo = normalizeRole(user.ruolo);
+ if (!ruolo) {
authLog("Utente trovato con ruolo non valido", {
userId: user.id,
ruolo: user.ruolo,
@@ -77,10 +75,10 @@ async function findActiveUser(identifier: string): Promise {
authLog("Utente attivo trovato", {
userId: user.id,
utente: user.utente,
- ruolo: user.ruolo,
+ ruolo,
});
- return user;
+ return { ...user, ruolo };
}
async function updateLastLogin(userId: number): Promise {
@@ -191,7 +189,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
},
session({ session, token }) {
session.user.id = token.id ?? "";
- session.user.ruolo = token.ruolo ?? "user";
+ session.user.ruolo = token.ruolo ?? "operatore";
session.user.utente = token.utente ?? session.user.name ?? "";
return session;
diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx
new file mode 100644
index 0000000..dfaffdb
--- /dev/null
+++ b/src/components/AppShell.tsx
@@ -0,0 +1,38 @@
+import { redirect } from "next/navigation";
+
+import { auth } from "@/src/auth";
+import SignOutButton from "@/src/components/auth/SignOutButton";
+import Navbar from "@/src/components/Navbar";
+import { PermissionsProvider } from "@/src/contexts/PermissionsContext";
+import { StabilimentoProvider } from "@/src/contexts/StabilimentoContext";
+import { getPermissions, normalizeRole } from "@/src/lib/permissions";
+
+export default async function AppShell({ children }: { children: React.ReactNode }) {
+ const session = await auth();
+
+ if (!session?.user) {
+ redirect("/login");
+ }
+
+ const ruolo = normalizeRole(session.user.ruolo);
+ if (!ruolo) {
+ redirect("/login");
+ }
+
+ const permissions = getPermissions(ruolo);
+
+ return (
+
+
+
+ }
+ />
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/ArticleCard.tsx b/src/components/ArticleCard.tsx
index 02fa1b5..2f466a0 100644
--- a/src/components/ArticleCard.tsx
+++ b/src/components/ArticleCard.tsx
@@ -11,6 +11,7 @@ interface ArticleCardProps {
onEdit: (article: Article) => void;
onDelete: (article: Article) => void;
onMovimento?: (article: Article, tipo: TipoMovimento) => void;
+ allowScarico?: boolean;
draggable?: boolean;
}
@@ -21,6 +22,7 @@ export default function ArticleCard({
onEdit,
onDelete,
onMovimento,
+ allowScarico = true,
draggable = true,
}: ArticleCardProps) {
const [deleting, setDeleting] = useState(false);
@@ -166,8 +168,9 @@ export default function ArticleCard({
-
+ {!readOnly && (
+
+ )}
{/* Indicatore clonazione */}
@@ -448,7 +454,7 @@ export default function MacchineTree() {
)}
{/* Indicatore copia */}
- {copiedNodeId && (
+ {!readOnly && copiedNodeId && (
Nodo copiato. Clicca su "Incolla" per inserirlo.
@@ -487,15 +493,19 @@ export default function MacchineTree() {
Nessuna macchina in "{selectedStabilimento.descrizione}"
-
- Clicca sul pulsante + per aggiungere la prima macchina
-
-
+ {!readOnly && (
+ <>
+
+ Clicca sul pulsante + per aggiungere la prima macchina
+
+
+ >
+ )}
) : (
@@ -515,7 +525,8 @@ export default function MacchineTree() {
onCopy={handleCopy}
onPaste={handlePaste}
onShowArticles={handleShowArticles}
- onDropArticle={handleDropArticle}
+ onDropArticle={readOnly ? undefined : handleDropArticle}
+ readOnly={readOnly}
/>
))}
@@ -609,6 +620,7 @@ export default function MacchineTree() {
macchinaNome={showArticlesModal.macchinaNome}
macchinaStabilimentoId={showArticlesModal.macchinaStabilimentoId}
articoli={showArticlesModal.articoli}
+ canRemove={permissions.canRemoveArticoloMacchina}
onClose={() => setShowArticlesModal(null)}
onArticoloRimosso={async () => {
await fetchArticoliPerMacchina();
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 6da4e0e..dd9c2ca 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -2,74 +2,88 @@
// File: Navbar.tsx
// componente Navbar
// @author: "villari.andrea@libero.it"
-// @version: "1.0.0 2026-05-07"
+// @version: "1.1.0 2026-05-31"
//====================================
-"use client"
-import { ReactNode} from "react";
-import Link from "next/link";
+"use client";
+
+import { ReactNode } from "react";
+import Link from "next/link";
import Image from "next/image";
import GlobalStabilimentoSelector from "./GlobalStabilimentoSelector";
+import type { AppPermissions } from "@/src/lib/permissions";
interface NavLink {
- href: string;
- label: string;
+ href: string;
+ label: string;
+ visible: (p: AppPermissions) => boolean;
}
const navLinks: NavLink[] = [
- { href: "/", label: "Home"},
- { href: "/movimenti", label: "Movimenti"},
- { href: "/articoli", label: "Articoli"},
- { href: "/macchine", label: "Macchine"},
- { href: "/stabilimenti", label: "Stabilimenti"},
- { href: "/utenti", label: "Utenti"},
+ { href: "/", label: "Home", visible: () => true },
+ { href: "/movimenti", label: "Movimenti", visible: () => true },
+ { href: "/articoli", label: "Articoli", visible: () => true },
+ { href: "/macchine", label: "Macchine", visible: () => true },
+ {
+ href: "/stabilimenti",
+ label: "Stabilimenti",
+ visible: (p) => p.canManageStabilimenti,
+ },
+ { href: "/utenti", label: "Utenti", visible: (p) => p.canManageUtenti },
];
-export default function Navbar({ actions }: { actions?: ReactNode }) {
- return (
-
+ );
}
diff --git a/src/components/TreeNode.tsx b/src/components/TreeNode.tsx
index a7ecece..701783c 100644
--- a/src/components/TreeNode.tsx
+++ b/src/components/TreeNode.tsx
@@ -57,6 +57,7 @@ interface TreeNodeProps {
onPaste: (targetId: number) => void;
onShowArticles?: (nodeId: number) => void;
onDropArticle?: (macchinaId: number, article: unknown) => void;
+ readOnly?: boolean;
}
export default function TreeNode({
@@ -74,6 +75,7 @@ export default function TreeNode({
onPaste,
onShowArticles,
onDropArticle,
+ readOnly = false,
}: TreeNodeProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [isDragOver, setIsDragOver] = useState(false);
@@ -100,10 +102,11 @@ export default function TreeNode({
const totalQuantity = getTotalQuantity(node);
const handleDrop = (e: DragEvent) => {
+ if (readOnly) return;
e.preventDefault();
e.stopPropagation();
setIsDragOver(false);
-
+
try {
const data = e.dataTransfer.getData("application/json");
if (data && onDropArticle) {
@@ -136,6 +139,7 @@ export default function TreeNode({
};
const handleDragEnter = (e: DragEvent) => {
+ if (readOnly) return;
e.preventDefault();
e.stopPropagation();
setIsDragOver(true);
@@ -171,10 +175,10 @@ export default function TreeNode({
${getNodeTypeBg()}
${isDragOver ? "ring-2 ring-blue-500 bg-blue-900/50 scale-[1.02]" : ""}
hover:bg-zinc-800/70`}
- onDragEnter={handleDragEnter}
- onDragOver={handleDragOver}
- onDragLeave={handleDragLeave}
- onDrop={handleDrop}
+ onDragEnter={readOnly ? undefined : handleDragEnter}
+ onDragOver={readOnly ? undefined : handleDragOver}
+ onDragLeave={readOnly ? undefined : handleDragLeave}
+ onDrop={readOnly ? undefined : handleDrop}
>
{children.length > 0 && (