import { useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { ShiftWithDetails, InsertShift, Site, GuardWithCertifications } from "@shared/schema"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { insertShiftFormSchema } from "@shared/schema"; import { Plus, Calendar, MapPin, Users, Clock, UserPlus, X, Shield, Car, Heart, Flame, Pencil } from "lucide-react"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import { StatusBadge } from "@/components/status-badge"; import { Skeleton } from "@/components/ui/skeleton"; import { format } from "date-fns"; import { it } from "date-fns/locale"; import { Badge } from "@/components/ui/badge"; export default function Shifts() { const { toast } = useToast(); const [isDialogOpen, setIsDialogOpen] = useState(false); const [selectedShift, setSelectedShift] = useState(null); const [isAssignDialogOpen, setIsAssignDialogOpen] = useState(false); const [editingShift, setEditingShift] = useState(null); const { data: shifts, isLoading: shiftsLoading } = useQuery({ queryKey: ["/api/shifts"], }); const { data: sites } = useQuery({ queryKey: ["/api/sites"], }); const { data: guards } = useQuery({ queryKey: ["/api/guards"], }); const form = useForm({ resolver: zodResolver(insertShiftFormSchema), defaultValues: { siteId: "", startTime: "", endTime: "", status: "planned" as const, }, }); const editForm = useForm({ resolver: zodResolver(insertShiftFormSchema), defaultValues: { siteId: "", startTime: "", endTime: "", status: "planned" as const, }, }); const createMutation = useMutation({ mutationFn: async (data: InsertShift) => { return await apiRequest("POST", "/api/shifts", data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/shifts"] }); toast({ title: "Turno creato", description: "Il turno è stato pianificato con successo", }); setIsDialogOpen(false); form.reset(); }, onError: (error) => { toast({ title: "Errore", description: error.message, variant: "destructive", }); }, }); const onSubmit = (data: InsertShift) => { createMutation.mutate(data); }; const assignGuardMutation = useMutation({ mutationFn: async ({ shiftId, guardId }: { shiftId: string; guardId: string }) => { return await apiRequest("POST", "/api/shift-assignments", { shiftId, guardId }); }, onSuccess: async () => { // Invalidate and wait for refetch to complete await queryClient.refetchQueries({ queryKey: ["/api/shifts"] }); // Now get the fresh data const updatedShifts = queryClient.getQueryData(["/api/shifts"]); if (selectedShift && updatedShifts) { const updatedShift = updatedShifts.find(s => s.id === selectedShift.id); if (updatedShift) { setSelectedShift(updatedShift); } } toast({ title: "Guardia assegnata", description: "La guardia è stata assegnata al turno con successo", }); }, onError: (error) => { toast({ title: "Errore", description: error.message, variant: "destructive", }); }, }); const removeAssignmentMutation = useMutation({ mutationFn: async (assignmentId: string) => { return await apiRequest("DELETE", `/api/shift-assignments/${assignmentId}`); }, onSuccess: async () => { // Invalidate and wait for refetch to complete await queryClient.refetchQueries({ queryKey: ["/api/shifts"] }); // Now get the fresh data const updatedShifts = queryClient.getQueryData(["/api/shifts"]); if (selectedShift && updatedShifts) { const updatedShift = updatedShifts.find(s => s.id === selectedShift.id); if (updatedShift) { setSelectedShift(updatedShift); } } toast({ title: "Assegnazione rimossa", description: "La guardia è stata rimossa dal turno", }); }, onError: (error) => { toast({ title: "Errore", description: error.message, variant: "destructive", }); }, }); const updateMutation = useMutation({ mutationFn: async ({ id, data }: { id: string; data: InsertShift }) => { return await apiRequest("PATCH", `/api/shifts/${id}`, data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/shifts"] }); toast({ title: "Turno aggiornato", description: "Il turno è stato aggiornato con successo", }); setEditingShift(null); editForm.reset(); }, onError: (error) => { toast({ title: "Errore", description: error.message, variant: "destructive", }); }, }); const handleAssignGuard = (guardId: string) => { if (selectedShift) { assignGuardMutation.mutate({ shiftId: selectedShift.id, guardId }); } }; const handleRemoveAssignment = (assignmentId: string) => { removeAssignmentMutation.mutate(assignmentId); }; const onEditSubmit = (data: InsertShift) => { if (editingShift) { updateMutation.mutate({ id: editingShift.id, data }); } }; const openEditDialog = (shift: ShiftWithDetails) => { const formatForInput = (date: Date | string) => { const d = new Date(date); return d.toISOString().slice(0, 16); }; setEditingShift(shift); editForm.reset({ siteId: shift.siteId, startTime: formatForInput(shift.startTime), endTime: formatForInput(shift.endTime), status: shift.status, }); }; const isGuardAssigned = (guardId: string) => { return selectedShift?.assignments.some(a => a.guardId === guardId) || false; }; const canGuardBeAssigned = (guard: GuardWithCertifications) => { if (!selectedShift) return false; const site = sites?.find(s => s.id === selectedShift.siteId); if (!site) return true; if (site.requiresArmed && !guard.isArmed) return false; if (site.requiresDriverLicense && !guard.hasDriverLicense) return false; return true; }; const getStatusLabel = (status: string) => { const labels: Record = { planned: "Pianificato", active: "Attivo", completed: "Completato", cancelled: "Annullato", }; return labels[status] || status; }; const getStatusVariant = (status: string): "active" | "inactive" | "pending" | "completed" => { const variants: Record = { planned: "pending", active: "active", completed: "completed", cancelled: "inactive", }; return variants[status] || "inactive"; }; return (

Pianificazione Turni

Calendario 24/7 con assegnazione guardie

Nuovo Turno Pianifica un nuovo turno di servizio
( Sito )} />
( Inizio field.onChange(e.target.value)} data-testid="input-start-time" /> )} /> ( Fine field.onChange(e.target.value)} data-testid="input-end-time" /> )} />
{shiftsLoading ? (
) : shifts && shifts.length > 0 ? (
{shifts.map((shift) => (
{shift.site.name} {format(new Date(shift.startTime), "dd/MM/yyyy HH:mm", { locale: it })} -{" "} {format(new Date(shift.endTime), "HH:mm", { locale: it })}
{getStatusLabel(shift.status)}
{shift.assignments.length > 0 ? `${shift.assignments.length} guardie assegnate` : "Nessuna guardia assegnata"}
{shift.status === "planned" && ( )}
{shift.assignments.length > 0 && (
{shift.assignments.map((assignment) => (
{assignment.guard.user?.firstName} {assignment.guard.user?.lastName} {shift.status === "planned" && ( )}
))}
)}
))}
) : (

Nessun turno pianificato

Inizia pianificando il primo turno di servizio

)} {/* Assignment Dialog */} Assegna Guardie al Turno {selectedShift && ( <> {selectedShift.site.name} -{" "} {format(new Date(selectedShift.startTime), "dd/MM/yyyy HH:mm", { locale: it })} )} {selectedShift && (
{/* Site Requirements */}

Requisiti Sito

{selectedShift.site.requiresArmed && ( Armato richiesto )} {selectedShift.site.requiresDriverLicense && ( Patente richiesta )} Min. {selectedShift.site.minGuards} guardie
{/* Guards List */}

Guardie Disponibili

{guards && guards.length > 0 ? (
{guards.map((guard) => { const assigned = isGuardAssigned(guard.id); const canAssign = canGuardBeAssigned(guard); return (

{guard.user?.firstName} {guard.user?.lastName}

#{guard.badgeNumber}
{guard.isArmed && ( Armato )} {guard.hasDriverLicense && ( Patente )} {guard.hasFirstAid && ( Primo Soccorso )} {guard.hasFireSafety && ( Antincendio )}
); })}
) : (

Nessuna guardia disponibile

)}
)}
{/* Edit Shift Dialog */} !open && setEditingShift(null)}> Modifica Turno Modifica i dati del turno presso {editingShift?.site.name}
( Sito )} />
( Inizio field.onChange(e.target.value)} data-testid="input-edit-start-time" /> )} /> ( Fine field.onChange(e.target.value)} data-testid="input-edit-end-time" /> )} />
( Stato Turno )} />
); }