From 44437730407b286c658e6b802a3e9bea7b8151e5 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Sat, 11 Oct 2025 15:16:47 +0000 Subject: [PATCH] Add ability for administrators to manage user roles and permissions Introduce a new user management interface for viewing and updating user roles, including API endpoints and backend storage logic for role management. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 99f0fce6-9386-489a-9632-1d81223cab44 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/99f0fce6-9386-489a-9632-1d81223cab44/TRdpk3a --- .replit | 2 +- client/src/App.tsx | 2 + client/src/components/app-sidebar.tsx | 7 + client/src/pages/users.tsx | 239 ++++++++++++++++++++++++++ server/routes.ts | 29 ++++ server/storage.ts | 15 ++ 6 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 client/src/pages/users.tsx diff --git a/.replit b/.replit index 6ac90e9..ebeaf96 100644 --- a/.replit +++ b/.replit @@ -19,7 +19,7 @@ localPort = 33035 externalPort = 3001 [[ports]] -localPort = 33349 +localPort = 38973 externalPort = 3002 [[ports]] diff --git a/client/src/App.tsx b/client/src/App.tsx index 2c81685..cfbefa7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -15,6 +15,7 @@ import Sites from "@/pages/sites"; import Shifts from "@/pages/shifts"; import Reports from "@/pages/reports"; import Notifications from "@/pages/notifications"; +import Users from "@/pages/users"; function Router() { const { isAuthenticated, isLoading } = useAuth(); @@ -31,6 +32,7 @@ function Router() { + > )} diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/app-sidebar.tsx index 3f8cdbe..4736c54 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/app-sidebar.tsx @@ -7,6 +7,7 @@ import { Bell, Settings, LogOut, + UserCog, } from "lucide-react"; import { Link, useLocation } from "wouter"; import { @@ -63,6 +64,12 @@ const menuItems = [ icon: Bell, roles: ["admin", "coordinator", "guard"], }, + { + title: "Utenti", + url: "/users", + icon: UserCog, + roles: ["admin"], + }, ]; export function AppSidebar() { diff --git a/client/src/pages/users.tsx b/client/src/pages/users.tsx new file mode 100644 index 0000000..95b965f --- /dev/null +++ b/client/src/pages/users.tsx @@ -0,0 +1,239 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { queryClient, apiRequest } from "@/lib/queryClient"; +import { useAuth } from "@/hooks/useAuth"; +import { type User } from "@shared/schema"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Badge } from "@/components/ui/badge"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useToast } from "@/hooks/use-toast"; +import { Shield, UserCog, Users as UsersIcon, UserCheck } from "lucide-react"; + +const roleIcons = { + admin: Shield, + coordinator: UserCog, + guard: UsersIcon, + client: UserCheck, +}; + +const roleLabels = { + admin: "Admin", + coordinator: "Coordinatore", + guard: "Guardia", + client: "Cliente", +}; + +const roleColors = { + admin: "bg-red-500/10 text-red-500 border-red-500/20", + coordinator: "bg-blue-500/10 text-blue-500 border-blue-500/20", + guard: "bg-green-500/10 text-green-500 border-green-500/20", + client: "bg-orange-500/10 text-orange-500 border-orange-500/20", +}; + +export default function Users() { + const { user: currentUser } = useAuth(); + const { toast } = useToast(); + + const { data: users, isLoading } = useQuery({ + queryKey: ["/api/users"], + }); + + const updateRoleMutation = useMutation({ + mutationFn: async ({ userId, role }: { userId: string; role: string }) => { + return apiRequest("PATCH", `/api/users/${userId}`, { role }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/users"] }); + queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] }); + toast({ + title: "Ruolo aggiornato", + description: "Il ruolo dell'utente รจ stato modificato con successo.", + }); + }, + onError: () => { + toast({ + title: "Errore", + description: "Impossibile aggiornare il ruolo dell'utente.", + variant: "destructive", + }); + }, + }); + + if (isLoading) { + return ( + + + Gestione Utenti + + Gestisci utenti e permessi del sistema + + + + + + {[1, 2, 3, 4, 5].map((i) => ( + + + + + + + + + ))} + + + + + ); + } + + return ( + + + + Gestione Utenti + + + Gestisci utenti e permessi del sistema VigilanzaTurni + + + + + + Utenti Registrati + + {users?.length || 0} utenti totali nel sistema + + + + + + + Utente + Email + Ruolo Attuale + Modifica Ruolo + + + + {users?.map((user) => { + const RoleIcon = roleIcons[user.role]; + const isCurrentUser = user.id === currentUser?.id; + + return ( + + + + + + + {user.firstName?.[0]} + {user.lastName?.[0]} + + + + + {user.firstName} {user.lastName} + {isCurrentUser && ( + + (Tu) + + )} + + + ID: {user.id} + + + + + + {user.email} + + + + + {roleLabels[user.role]} + + + + + updateRoleMutation.mutate({ userId: user.id, role }) + } + disabled={isCurrentUser || updateRoleMutation.isPending} + data-testid={`select-role-${user.id}`} + > + + + + + + + + Admin + + + + + + Coordinatore + + + + + + Guardia + + + + + + Cliente + + + + + + + ); + })} + + + + {users?.length === 0 && ( + + Nessun utente registrato nel sistema + + )} + + + + ); +} diff --git a/server/routes.ts b/server/routes.ts index c9f8b9e..6bb1a6f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -23,6 +23,35 @@ export async function registerRoutes(app: Express): Promise { } }); + // ============= USER MANAGEMENT ROUTES ============= + app.get("/api/users", isAuthenticated, async (req, res) => { + try { + const allUsers = await storage.getAllUsers(); + res.json(allUsers); + } catch (error) { + console.error("Error fetching users:", error); + res.status(500).json({ message: "Failed to fetch users" }); + } + }); + + app.patch("/api/users/:id", isAuthenticated, async (req, res) => { + try { + const { role } = req.body; + if (!role || !["admin", "coordinator", "guard", "client"].includes(role)) { + return res.status(400).json({ message: "Invalid role" }); + } + + const updated = await storage.updateUserRole(req.params.id, role); + if (!updated) { + return res.status(404).json({ message: "User not found" }); + } + res.json(updated); + } catch (error) { + console.error("Error updating user role:", error); + res.status(500).json({ message: "Failed to update user role" }); + } + }); + // ============= GUARD ROUTES ============= app.get("/api/guards", isAuthenticated, async (req, res) => { try { diff --git a/server/storage.ts b/server/storage.ts index 0b10bae..1a15a9d 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -29,6 +29,8 @@ export interface IStorage { // User operations (Replit Auth required) getUser(id: string): Promise; upsertUser(user: UpsertUser): Promise; + getAllUsers(): Promise; + updateUserRole(id: string, role: "admin" | "coordinator" | "guard" | "client"): Promise; // Guard operations getAllGuards(): Promise; @@ -86,6 +88,19 @@ export class DatabaseStorage implements IStorage { return user; } + async getAllUsers(): Promise { + return await db.select().from(users).orderBy(desc(users.createdAt)); + } + + async updateUserRole(id: string, role: "admin" | "coordinator" | "guard" | "client"): Promise { + const [updated] = await db + .update(users) + .set({ role, updatedAt: new Date() }) + .where(eq(users.id, id)) + .returning(); + return updated; + } + // Guard operations async getAllGuards(): Promise { return await db.select().from(guards);
+ Gestisci utenti e permessi del sistema +
+ Gestisci utenti e permessi del sistema VigilanzaTurni +
+ {user.firstName} {user.lastName} + {isCurrentUser && ( + + (Tu) + + )} +
+ ID: {user.id} +