VigilanzaTurni/client/src/components/app-sidebar.tsx
marco370 e0504f0a13 Add planning consultation views and reorganize sidebar navigation
Introduce new planning consultation pages for fixed and mobile agents, refactor sidebar navigation into logical groups, and enhance shift assignment logic by preventing double-booking of guards.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/kDVJJUd
2025-10-23 16:34:28 +00:00

303 lines
7.5 KiB
TypeScript

import {
Calendar,
Shield,
MapPin,
Users,
BarChart3,
Bell,
Settings,
LogOut,
UserCog,
ClipboardList,
Car,
Briefcase,
Navigation,
} from "lucide-react";
import { Link, useLocation } from "wouter";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarHeader,
SidebarFooter,
} from "@/components/ui/sidebar";
import { useAuth } from "@/hooks/useAuth";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ThemeToggle } from "@/components/theme-toggle";
const dashboardItems = [
{
title: "Dashboard",
url: "/",
icon: Shield,
roles: ["admin", "coordinator", "guard", "client"],
},
];
const creationItems = [
{
title: "Turni Fissi",
url: "/shifts",
icon: Calendar,
roles: ["admin", "coordinator"],
},
{
title: "Pattuglie Mobile",
url: "/planning-mobile",
icon: Navigation,
roles: ["admin", "coordinator"],
},
];
const consultationItems = [
{
title: "Planning Agente Fisso",
url: "/planning-view-fixed-agent",
icon: Users,
roles: ["admin", "coordinator"],
},
{
title: "Planning Agente Mobile",
url: "/planning-view-mobile-agent",
icon: Navigation,
roles: ["admin", "coordinator"],
},
{
title: "Planning Sito",
url: "/site-planning-view",
icon: MapPin,
roles: ["admin", "coordinator"],
},
];
const personalItems = [
{
title: "I Miei Turni Fissi",
url: "/my-shifts-fixed",
icon: Calendar,
roles: ["guard"],
},
{
title: "Le Mie Pattuglie",
url: "/my-shifts-mobile",
icon: Navigation,
roles: ["guard"],
},
];
const registryItems = [
{
title: "Guardie",
url: "/guards",
icon: Users,
roles: ["admin", "coordinator"],
},
{
title: "Siti",
url: "/sites",
icon: MapPin,
roles: ["admin", "coordinator", "client"],
},
{
title: "Clienti",
url: "/customers",
icon: Briefcase,
roles: ["admin", "coordinator"],
},
{
title: "Servizi",
url: "/services",
icon: Briefcase,
roles: ["admin", "coordinator"],
},
{
title: "Parco Automezzi",
url: "/vehicles",
icon: Car,
roles: ["admin", "coordinator"],
},
];
const reportingItems = [
{
title: "Report",
url: "/reports",
icon: BarChart3,
roles: ["admin", "coordinator", "client"],
},
];
const systemItems = [
{
title: "Notifiche",
url: "/notifications",
icon: Bell,
roles: ["admin", "coordinator", "guard"],
},
{
title: "Utenti",
url: "/users",
icon: UserCog,
roles: ["admin"],
},
{
title: "Parametri",
url: "/parameters",
icon: Settings,
roles: ["admin", "coordinator"],
},
];
export function AppSidebar() {
const { user } = useAuth();
const [location] = useLocation();
const filterItems = (items: typeof dashboardItems) =>
items.filter((item) => user && item.roles.includes(user.role));
const renderMenuItems = (items: typeof dashboardItems) => (
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
isActive={location === item.url}
data-testid={`link-${item.title.toLowerCase().replace(/\s+/g, "-")}`}
>
<Link href={item.url}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
);
return (
<Sidebar>
<SidebarHeader className="p-4 border-b">
<div className="flex items-center gap-3">
<Shield className="h-8 w-8 text-primary" />
<div>
<h1 className="text-lg font-semibold">VigilanzaTurni</h1>
<p className="text-xs text-muted-foreground">Sistema Gestione</p>
</div>
</div>
</SidebarHeader>
<SidebarContent>
{/* Dashboard */}
{filterItems(dashboardItems).length > 0 && (
<SidebarGroup>
<SidebarGroupContent>
{renderMenuItems(filterItems(dashboardItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Planning Operativo - Creazione */}
{filterItems(creationItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Planning - Creazione</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(creationItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Planning Operativo - Consultazione */}
{filterItems(consultationItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Planning - Consultazione</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(consultationItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Viste Personali (Guard) */}
{filterItems(personalItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>I Miei Turni</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(personalItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Anagrafica */}
{filterItems(registryItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Anagrafica</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(registryItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Report */}
{filterItems(reportingItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Reporting</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(reportingItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
{/* Sistema */}
{filterItems(systemItems).length > 0 && (
<SidebarGroup>
<SidebarGroupLabel>Sistema</SidebarGroupLabel>
<SidebarGroupContent>
{renderMenuItems(filterItems(systemItems))}
</SidebarGroupContent>
</SidebarGroup>
)}
</SidebarContent>
<SidebarFooter className="p-4 border-t space-y-4">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 min-w-0">
<Avatar className="h-8 w-8">
<AvatarImage src={user?.profileImageUrl || undefined} />
<AvatarFallback>
{user?.firstName?.[0]}{user?.lastName?.[0]}
</AvatarFallback>
</Avatar>
<div className="min-w-0">
<p className="text-sm font-medium truncate">
{user?.firstName} {user?.lastName}
</p>
<p className="text-xs text-muted-foreground capitalize">
{user?.role}
</p>
</div>
</div>
<ThemeToggle />
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => window.location.href = '/api/logout'}
data-testid="button-logout"
>
<LogOut className="h-4 w-4 mr-2" />
Esci
</Button>
</div>
</SidebarFooter>
</Sidebar>
);
}