import { useState } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { queryClient, apiRequest } from "@/lib/queryClient"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { insertSiteSchema, insertServiceTypeSchema, type Site, type ServiceType } from "@shared/schema"; import { z } from "zod"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } 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 { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { useToast } from "@/hooks/use-toast"; import * as LucideIcons from "lucide-react"; import { MapPin, Plus, Pencil } from "lucide-react"; // Helper to get icon component from name const getIconComponent = (iconName: string) => { const Icon = (LucideIcons as any)[iconName]; return Icon || LucideIcons.Building2; }; // Helper to get color classes from color name const getColorClasses = (color: string) => { const colorMap: Record = { blue: "bg-blue-500/10 text-blue-500 border-blue-500/20", green: "bg-green-500/10 text-green-500 border-green-500/20", purple: "bg-purple-500/10 text-purple-500 border-purple-500/20", orange: "bg-orange-500/10 text-orange-500 border-orange-500/20", red: "bg-red-500/10 text-red-500 border-red-500/20", yellow: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }; return colorMap[color] || colorMap.blue; }; type SiteForm = z.infer; type ServiceTypeForm = z.infer; export default function Services() { const { toast } = useToast(); const [selectedServiceType, setSelectedServiceType] = useState(null); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); // Service Type management states const [manageTypesDialogOpen, setManageTypesDialogOpen] = useState(false); const [createTypeDialogOpen, setCreateTypeDialogOpen] = useState(false); const [editTypeDialogOpen, setEditTypeDialogOpen] = useState(false); const [selectedType, setSelectedType] = useState(null); const { data: sites = [], isLoading: isLoadingSites } = useQuery({ queryKey: ["/api/sites"], }); const { data: serviceTypes = [], isLoading: isLoadingServiceTypes } = useQuery({ queryKey: ["/api/service-types"], }); const isLoading = isLoadingSites || isLoadingServiceTypes; const createForm = useForm({ resolver: zodResolver(insertSiteSchema), defaultValues: { name: "", location: "roccapiemonte", address: "", clientId: null, shiftType: "fixed_post", minGuards: 1, requiresArmed: false, requiresDriverLicense: false, isActive: true, }, }); const editForm = useForm({ resolver: zodResolver(insertSiteSchema), defaultValues: { name: "", location: "roccapiemonte", address: "", clientId: null, shiftType: "fixed_post", minGuards: 1, requiresArmed: false, requiresDriverLicense: false, isActive: true, }, }); const createSiteMutation = useMutation({ mutationFn: async (data: SiteForm) => { return apiRequest("POST", "/api/sites", data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/sites"] }); toast({ title: "Sito creato", description: "Il sito รจ stato aggiunto con successo.", }); setCreateDialogOpen(false); createForm.reset(); }, onError: (error: any) => { toast({ title: "Errore", description: error.message || "Impossibile creare il sito.", variant: "destructive", }); }, }); const updateSiteMutation = useMutation({ mutationFn: async ({ id, data }: { id: string; data: SiteForm }) => { return apiRequest("PATCH", `/api/sites/${id}`, data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/sites"] }); toast({ title: "Sito aggiornato", description: "Il sito รจ stato modificato con successo.", }); setEditDialogOpen(false); setSelectedSite(null); }, onError: (error: any) => { toast({ title: "Errore", description: error.message || "Impossibile aggiornare il sito.", variant: "destructive", }); }, }); // Service Type Forms const createTypeForm = useForm({ resolver: zodResolver(insertServiceTypeSchema), defaultValues: { code: "", label: "", description: "", icon: "Building2", color: "blue", isActive: true, }, }); const editTypeForm = useForm({ resolver: zodResolver(insertServiceTypeSchema), defaultValues: { code: "", label: "", description: "", icon: "Building2", color: "blue", isActive: true, }, }); // Service Type Mutations const createTypeMutation = useMutation({ mutationFn: async (data: ServiceTypeForm) => { return apiRequest("POST", "/api/service-types", data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/service-types"] }); toast({ title: "Tipologia creata", description: "La tipologia di servizio รจ stata aggiunta con successo.", }); setCreateTypeDialogOpen(false); createTypeForm.reset(); }, onError: (error: any) => { toast({ title: "Errore", description: error.message || "Impossibile creare la tipologia.", variant: "destructive", }); }, }); const updateTypeMutation = useMutation({ mutationFn: async ({ id, data }: { id: string; data: ServiceTypeForm }) => { return apiRequest("PATCH", `/api/service-types/${id}`, data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/service-types"] }); toast({ title: "Tipologia aggiornata", description: "Le modifiche sono state salvate con successo.", }); setEditTypeDialogOpen(false); setSelectedType(null); }, onError: (error: any) => { toast({ title: "Errore", description: error.message || "Impossibile aggiornare la tipologia.", variant: "destructive", }); }, }); const handleEditType = (type: ServiceType) => { setSelectedType(type); editTypeForm.reset({ code: type.code, label: type.label, description: type.description, icon: type.icon, color: type.color, isActive: type.isActive, }); setEditTypeDialogOpen(true); }; const handleCreateSite = (serviceType: string) => { createForm.reset({ name: "", location: "roccapiemonte", address: "", clientId: null, shiftType: serviceType as any, minGuards: 1, requiresArmed: false, requiresDriverLicense: false, isActive: true, }); setCreateDialogOpen(true); }; const handleEditSite = (site: Site) => { setSelectedSite(site); editForm.reset({ name: site.name, location: site.location, address: site.address || "", clientId: site.clientId, shiftType: site.shiftType, minGuards: site.minGuards, requiresArmed: site.requiresArmed || false, requiresDriverLicense: site.requiresDriverLicense || false, isActive: site.isActive, }); setEditDialogOpen(true); }; // Calculate statistics per service type const stats = serviceTypes.reduce((acc, serviceType) => { const sitesForType = sites.filter(s => s.shiftType === serviceType.code); acc[serviceType.code] = { total: sitesForType.length, active: sitesForType.filter(s => s.isActive).length, requiresArmed: sitesForType.filter(s => s.requiresArmed).length, requiresDriver: sitesForType.filter(s => s.requiresDriverLicense).length, }; return acc; }, {} as Record); const filteredSites = selectedServiceType ? sites.filter(s => s.shiftType === selectedServiceType) : []; return (

Gestione Servizi

Panoramica tipologie di servizio e relative configurazioni

{isLoading ? (
Caricamento servizi...
) : ( <>
{serviceTypes.map((serviceType) => { const Icon = getIconComponent(serviceType.icon); const stat = stats[serviceType.code] || { total: 0, active: 0, requiresArmed: 0, requiresDriver: 0 }; return (
{serviceType.label} {serviceType.description}

Siti Totali

{stat.total}

Attivi

{stat.active}

Richiedono Armati

{stat.requiresArmed}

Richiedono Patente

{stat.requiresDriver}

{sites.filter(s => s.shiftType === serviceType.code && s.location === 'roccapiemonte').length} Roccapiemonte {sites.filter(s => s.shiftType === serviceType.code && s.location === 'milano').length} Milano {sites.filter(s => s.shiftType === serviceType.code && s.location === 'roma').length} Roma
{stat.total > 0 && ( )}
); })}
{selectedServiceType && filteredSites.length > 0 && ( Siti {serviceTypes.find(st => st.code === selectedServiceType)?.label || ""} Lista completa dei siti con questo tipo di servizio
{filteredSites.map((site) => (

{site.name}

{site.isActive ? "Attivo" : "Inattivo"}
๐Ÿ“ {site.location} ๐Ÿ‘ฅ Min. {site.minGuards} guardie {site.requiresArmed && ๐Ÿ”ซ Armato} {site.requiresDriverLicense && ๐Ÿš— Patente}
))}
)} )} {/* Create Site Dialog */} Aggiungi Nuovo Sito Crea un nuovo sito di servizio
createSiteMutation.mutate(data))} className="space-y-4">
( Nome Sito* )} /> ( Sede* )} />
( Indirizzo )} />
( Tipo Servizio* )} /> ( Guardie Minime* field.onChange(parseInt(e.target.value) || 1)} data-testid="input-min-guards" /> )} />
( Richiede Armato )} /> ( Richiede Patente )} />
( Sito Attivo )} />
{/* Edit Site Dialog */} Modifica Sito Aggiorna le informazioni del sito
selectedSite && updateSiteMutation.mutate({ id: selectedSite.id, data }) )} className="space-y-4" >
( Nome Sito* )} /> ( Sede* )} />
( Indirizzo )} />
( Tipo Servizio* )} /> ( Guardie Minime* field.onChange(parseInt(e.target.value) || 1)} data-testid="input-edit-min-guards" /> )} />
( Richiede Armato )} /> ( Richiede Patente )} />
( Sito Attivo )} />
{/* Manage Service Types Dialog */} Gestione Tipologie Servizio Configura le tipologie di servizio disponibili nel sistema
{serviceTypes.map((type) => { const Icon = getIconComponent(type.icon); return (

{type.label}

{type.code} {!type.isActive && ( Disattivato )}

{type.description}

); })}
{/* Create Service Type Dialog */} Nuova Tipologia Servizio Crea una nuova tipologia di servizio
createTypeMutation.mutate(data))} className="space-y-4" > ( Codice* )} /> ( Nome* )} /> ( Descrizione*