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
303 lines
7.5 KiB
TypeScript
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>
|
|
);
|
|
}
|