import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { format, startOfWeek, addWeeks } from "date-fns"; import { it } from "date-fns/locale"; import { useLocation } from "wouter"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ChevronLeft, ChevronRight, Calendar, MapPin, Users, AlertTriangle, Car, Edit, CheckCircle2 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; interface GuardWithHours { guardId: string; guardName: string; badgeNumber: string; hours: number; } interface Vehicle { vehicleId: string; licensePlate: string; brand: string; model: string; } interface SiteData { siteId: string; siteName: string; serviceType: string; minGuards: number; guards: GuardWithHours[]; vehicles: Vehicle[]; totalShiftHours: number; guardsAssigned: number; missingGuards: number; shiftsCount: number; } interface DayData { date: string; dayOfWeek: string; sites: SiteData[]; } interface GeneralPlanningResponse { weekStart: string; weekEnd: string; location: string; days: DayData[]; summary: { totalGuardsNeeded: number; totalGuardsAssigned: number; totalGuardsMissing: number; }; } export default function GeneralPlanning() { const [, navigate] = useLocation(); const [selectedLocation, setSelectedLocation] = useState("roccapiemonte"); const [weekStart, setWeekStart] = useState(() => startOfWeek(new Date(), { weekStartsOn: 1 })); const [selectedCell, setSelectedCell] = useState<{ siteId: string; siteName: string; date: string; data: SiteData } | null>(null); // Query per dati planning settimanale const { data: planningData, isLoading } = useQuery({ queryKey: ["/api/general-planning", format(weekStart, "yyyy-MM-dd"), selectedLocation], queryFn: async () => { const response = await fetch( `/api/general-planning?weekStart=${format(weekStart, "yyyy-MM-dd")}&location=${selectedLocation}` ); if (!response.ok) throw new Error("Failed to fetch general planning"); return response.json(); }, }); // Navigazione settimana const goToPreviousWeek = () => setWeekStart(addWeeks(weekStart, -1)); const goToNextWeek = () => setWeekStart(addWeeks(weekStart, 1)); const goToCurrentWeek = () => setWeekStart(startOfWeek(new Date(), { weekStartsOn: 1 })); // Formatta nome sede const formatLocation = (loc: string) => { const locations: Record = { roccapiemonte: "Roccapiemonte", milano: "Milano", roma: "Roma", }; return locations[loc] || loc; }; // Raggruppa siti unici da tutti i giorni const allSites = planningData?.days.flatMap(day => day.sites) || []; const uniqueSites = Array.from( new Map(allSites.map(site => [site.siteId, site])).values() ); // Handler per aprire dialog cella const handleCellClick = (siteId: string, siteName: string, date: string, data: SiteData) => { setSelectedCell({ siteId, siteName, date, data }); }; // Naviga a pianificazione operativa con parametri const navigateToOperationalPlanning = () => { if (selectedCell) { // Encode parameters nella URL navigate(`/operational-planning?date=${selectedCell.date}&location=${selectedLocation}`); } }; return (
{/* Header */}

Planning Generale

Vista settimanale turni con calcolo automatico guardie mancanti

{/* Filtri e navigazione */}
{/* Selezione Sede */}
{/* Navigazione settimana */}
{/* Info settimana */} {planningData && (
{format(new Date(planningData.weekStart), "dd MMM", { locale: it })} -{" "} {format(new Date(planningData.weekEnd), "dd MMM yyyy", { locale: it })}
)}
{/* Summary Guardie Settimana */} {!isLoading && planningData?.summary && ( Riepilogo Guardie Settimana

Guardie Necessarie

{planningData.summary.totalGuardsNeeded}

Guardie Pianificate

{planningData.summary.totalGuardsAssigned}

0 ? 'bg-destructive/10 border-destructive/20' : 'bg-green-500/10 border-green-500/20'}`}>

Guardie Mancanti

0 ? 'text-destructive' : 'text-green-600 dark:text-green-500'}`}> {planningData.summary.totalGuardsMissing}

)} {/* Tabella Planning */} Planning Settimanale - {formatLocation(selectedLocation)} {isLoading ? (
) : planningData ? (
{planningData.days.map((day) => ( ))} {uniqueSites.map((site) => ( {planningData.days.map((day) => { const daySiteData = day.sites.find((s) => s.siteId === site.siteId); return ( ); })} ))}
Sito
{format(new Date(day.date), "EEEE", { locale: it })} {format(new Date(day.date), "dd/MM", { locale: it })}
{site.siteName} {site.serviceType}
daySiteData && handleCellClick(site.siteId, site.siteName, day.date, daySiteData)} > {daySiteData ? (
{/* Riepilogo guardie necessarie/assegnate/mancanti - SEMPRE VISIBILE */}
{daySiteData.missingGuards > 0 ? ( Mancano {daySiteData.missingGuards} {daySiteData.missingGuards === 1 ? "guardia" : "guardie"} ) : ( Copertura Completa )}
{daySiteData.guardsAssigned + daySiteData.missingGuards} necessarie ยท {daySiteData.guardsAssigned} assegnate
{/* Guardie assegnate */} {daySiteData.guards.length > 0 && (
Guardie:
{daySiteData.guards.map((guard, idx) => (
{guard.badgeNumber} {guard.hours}h
))}
)} {/* Veicoli */} {daySiteData.vehicles.length > 0 && (
Veicoli:
{daySiteData.vehicles.map((vehicle, idx) => (
{vehicle.licensePlate}
))}
)} {/* Info copertura - mostra solo se ci sono turni */} {daySiteData.shiftsCount > 0 && (
Turni: {daySiteData.shiftsCount}
Tot. ore: {daySiteData.totalShiftHours}h
)}
) : (
-
)}
{uniqueSites.length === 0 && (

Nessun sito attivo per la sede selezionata

)}
) : (

Errore nel caricamento dei dati

)}
{/* Dialog dettagli cella */} setSelectedCell(null)}> {selectedCell?.siteName} {selectedCell && format(new Date(selectedCell.date), "EEEE dd MMMM yyyy", { locale: it })} {selectedCell && (
{/* Info turni */}

Turni Pianificati

{selectedCell.data.shiftsCount}

Ore Totali

{selectedCell.data.totalShiftHours}h

{/* Guardie assegnate */} {selectedCell.data.guards.length > 0 && (
Guardie Assegnate ({selectedCell.data.guards.length})
{selectedCell.data.guards.map((guard, idx) => (

{guard.guardName}

{guard.badgeNumber}

{guard.hours}h
))}
)} {/* Veicoli */} {selectedCell.data.vehicles.length > 0 && (
Veicoli Assegnati ({selectedCell.data.vehicles.length})
{selectedCell.data.vehicles.map((vehicle, idx) => (

{vehicle.licensePlate}

{vehicle.brand} {vehicle.model}

))}
)} {/* Guardie mancanti */} {selectedCell.data.missingGuards > 0 && (
Attenzione: Guardie Mancanti

Servono ancora {selectedCell.data.missingGuards}{" "} {selectedCell.data.missingGuards === 1 ? "guardia" : "guardie"} per coprire completamente il servizio (calcolato su {selectedCell.data.totalShiftHours}h con max 9h per guardia e {selectedCell.data.minGuards} {selectedCell.data.minGuards === 1 ? "guardia minima" : "guardie minime"} contemporanee)

)} {/* No turni */} {selectedCell.data.shiftsCount === 0 && (

Nessun turno pianificato per questa data

)}
)}
); }