Introduces a new `classification` field to `serviceTypes` table and UI elements, allowing distinction between fixed and mobile services for planning purposes. Refactors date handling in route registration for improved accuracy and reliability. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e5565357-90e1-419f-b9a8-6ee8394636df Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e5565357-90e1-419f-b9a8-6ee8394636df/kHMnjKS
1401 lines
54 KiB
TypeScript
1401 lines
54 KiB
TypeScript
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<string, string> = {
|
|
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<typeof insertSiteSchema>;
|
|
type ServiceTypeForm = z.infer<typeof insertServiceTypeSchema>;
|
|
|
|
export default function Services() {
|
|
const { toast } = useToast();
|
|
const [selectedServiceType, setSelectedServiceType] = useState<string | null>(null);
|
|
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
const [selectedSite, setSelectedSite] = useState<Site | null>(null);
|
|
|
|
// Service Type management states
|
|
const [manageTypesDialogOpen, setManageTypesDialogOpen] = useState(false);
|
|
const [createTypeDialogOpen, setCreateTypeDialogOpen] = useState(false);
|
|
const [editTypeDialogOpen, setEditTypeDialogOpen] = useState(false);
|
|
const [selectedType, setSelectedType] = useState<ServiceType | null>(null);
|
|
|
|
const { data: sites = [], isLoading: isLoadingSites } = useQuery<Site[]>({
|
|
queryKey: ["/api/sites"],
|
|
});
|
|
|
|
const { data: serviceTypes = [], isLoading: isLoadingServiceTypes } = useQuery<ServiceType[]>({
|
|
queryKey: ["/api/service-types"],
|
|
});
|
|
|
|
const isLoading = isLoadingSites || isLoadingServiceTypes;
|
|
|
|
const createForm = useForm<SiteForm>({
|
|
resolver: zodResolver(insertSiteSchema),
|
|
defaultValues: {
|
|
name: "",
|
|
location: "roccapiemonte",
|
|
address: "",
|
|
clientId: null,
|
|
shiftType: "fixed_post",
|
|
minGuards: 1,
|
|
requiresArmed: false,
|
|
requiresDriverLicense: false,
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const editForm = useForm<SiteForm>({
|
|
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<ServiceTypeForm>({
|
|
resolver: zodResolver(insertServiceTypeSchema),
|
|
defaultValues: {
|
|
code: "",
|
|
label: "",
|
|
description: "",
|
|
icon: "Building2",
|
|
color: "blue",
|
|
classification: "fisso", // ✅ NUOVO: Discriminante Planning Fissi/Mobile
|
|
isActive: true,
|
|
},
|
|
});
|
|
|
|
const editTypeForm = useForm<ServiceTypeForm>({
|
|
resolver: zodResolver(insertServiceTypeSchema),
|
|
defaultValues: {
|
|
code: "",
|
|
label: "",
|
|
description: "",
|
|
icon: "Building2",
|
|
color: "blue",
|
|
classification: "fisso", // ✅ NUOVO: Discriminante Planning Fissi/Mobile
|
|
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,
|
|
classification: type.classification, // ✅ NUOVO: includi classification
|
|
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,
|
|
serviceStartTime: site.serviceStartTime || "",
|
|
serviceEndTime: site.serviceEndTime || "",
|
|
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<string, any>);
|
|
|
|
const filteredSites = selectedServiceType
|
|
? sites.filter(s => s.shiftType === selectedServiceType)
|
|
: [];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-semibold mb-2">Gestione Servizi</h1>
|
|
<p className="text-muted-foreground">
|
|
Panoramica tipologie di servizio e relative configurazioni
|
|
</p>
|
|
</div>
|
|
<Button
|
|
onClick={() => setManageTypesDialogOpen(true)}
|
|
data-testid="button-manage-service-types"
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Gestisci Tipologie
|
|
</Button>
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<div className="text-center py-8 text-muted-foreground">Caricamento servizi...</div>
|
|
) : (
|
|
<>
|
|
<div className="grid gap-6 md:grid-cols-2">
|
|
{serviceTypes.map((serviceType) => {
|
|
const Icon = getIconComponent(serviceType.icon);
|
|
const stat = stats[serviceType.code] || { total: 0, active: 0, requiresArmed: 0, requiresDriver: 0 };
|
|
|
|
return (
|
|
<Card key={serviceType.id} data-testid={`card-service-${serviceType.code}`}>
|
|
<CardHeader>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-2 rounded-lg ${getColorClasses(serviceType.color)}`}>
|
|
<Icon className="h-6 w-6" />
|
|
</div>
|
|
<div>
|
|
<CardTitle className="text-xl">{serviceType.label}</CardTitle>
|
|
<CardDescription className="mt-1">{serviceType.description}</CardDescription>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Siti Totali</p>
|
|
<p className="text-2xl font-semibold" data-testid={`text-total-${serviceType.code}`}>
|
|
{stat.total}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Attivi</p>
|
|
<p className="text-2xl font-semibold text-green-500" data-testid={`text-active-${serviceType.code}`}>
|
|
{stat.active}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Richiedono Armati</p>
|
|
<p className="text-lg font-semibold" data-testid={`text-armed-${serviceType.code}`}>
|
|
{stat.requiresArmed}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">Richiedono Patente</p>
|
|
<p className="text-lg font-semibold" data-testid={`text-driver-${serviceType.code}`}>
|
|
{stat.requiresDriver}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<div className="flex flex-wrap gap-2">
|
|
<Badge variant="outline" className="font-normal">
|
|
<MapPin className="h-3 w-3 mr-1" />
|
|
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'roccapiemonte').length} Roccapiemonte
|
|
</Badge>
|
|
<Badge variant="outline" className="font-normal">
|
|
<MapPin className="h-3 w-3 mr-1" />
|
|
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'milano').length} Milano
|
|
</Badge>
|
|
<Badge variant="outline" className="font-normal">
|
|
<MapPin className="h-3 w-3 mr-1" />
|
|
{sites.filter(s => s.shiftType === serviceType.code && s.location === 'roma').length} Roma
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{stat.total > 0 && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full mt-4"
|
|
onClick={() => setSelectedServiceType(selectedServiceType === serviceType.code ? null : serviceType.code)}
|
|
data-testid={`button-view-sites-${serviceType.code}`}
|
|
>
|
|
{selectedServiceType === serviceType.code ? "Nascondi" : "Visualizza"} Siti ({stat.total})
|
|
</Button>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{selectedServiceType && filteredSites.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
Siti {serviceTypes.find(st => st.code === selectedServiceType)?.label || ""}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Lista completa dei siti con questo tipo di servizio
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-3">
|
|
{filteredSites.map((site) => (
|
|
<div
|
|
key={site.id}
|
|
className="flex items-center justify-between p-4 rounded-lg border"
|
|
data-testid={`site-item-${site.id}`}
|
|
>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3">
|
|
<h4 className="font-semibold">{site.name}</h4>
|
|
<Badge variant={site.isActive ? "default" : "secondary"}>
|
|
{site.isActive ? "Attivo" : "Inattivo"}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex gap-4 mt-2 text-sm text-muted-foreground">
|
|
<span>📍 {site.location}</span>
|
|
<span>👥 Min. {site.minGuards} guardie</span>
|
|
{site.requiresArmed && <span>🔫 Armato</span>}
|
|
{site.requiresDriverLicense && <span>🚗 Patente</span>}
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => handleEditSite(site)}
|
|
data-testid={`button-edit-site-${site.id}`}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Create Site Dialog */}
|
|
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Aggiungi Nuovo Sito</DialogTitle>
|
|
<DialogDescription>
|
|
Crea un nuovo sito di servizio
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...createForm}>
|
|
<form onSubmit={createForm.handleSubmit((data) => createSiteMutation.mutate(data))} className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome Sito*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} data-testid="input-site-name" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="location"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Sede*</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-site-location">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
|
<SelectItem value="milano">Milano</SelectItem>
|
|
<SelectItem value="roma">Roma</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={createForm.control}
|
|
name="address"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Indirizzo</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} value={field.value || ""} data-testid="input-site-address" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="shiftType"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo Servizio*</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-shift-type">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
{serviceTypes.filter(st => st.isActive).map(st => (
|
|
<SelectItem key={st.id} value={st.code}>{st.label}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="minGuards"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Guardie Minime*</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value}
|
|
onChange={(e) => field.onChange(parseInt(e.target.value) || 1)}
|
|
data-testid="input-min-guards"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="serviceStartTime"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Orario Inizio Servizio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="time"
|
|
{...field}
|
|
value={field.value || ""}
|
|
data-testid="input-service-start-time"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="serviceEndTime"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Orario Fine Servizio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="time"
|
|
{...field}
|
|
value={field.value || ""}
|
|
data-testid="input-service-end-time"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="requiresArmed"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Richiede Armato</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? false}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-requires-armed"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="requiresDriverLicense"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Richiede Patente</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? false}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-requires-driver"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={createForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Sito Attivo</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? true}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-is-active"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setCreateDialogOpen(false)}
|
|
data-testid="button-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={createSiteMutation.isPending}
|
|
data-testid="button-save-site"
|
|
>
|
|
{createSiteMutation.isPending ? "Salvataggio..." : "Salva Sito"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Edit Site Dialog */}
|
|
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Modifica Sito</DialogTitle>
|
|
<DialogDescription>
|
|
Aggiorna le informazioni del sito
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...editForm}>
|
|
<form
|
|
onSubmit={editForm.handleSubmit((data) =>
|
|
selectedSite && updateSiteMutation.mutate({ id: selectedSite.id, data })
|
|
)}
|
|
className="space-y-4"
|
|
>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome Sito*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} data-testid="input-edit-site-name" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="location"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Sede*</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-edit-site-location">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
|
<SelectItem value="milano">Milano</SelectItem>
|
|
<SelectItem value="roma">Roma</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="address"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Indirizzo</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} value={field.value || ""} data-testid="input-edit-site-address" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="shiftType"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo Servizio*</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-edit-shift-type">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
{serviceTypes.filter(st => st.isActive).map(st => (
|
|
<SelectItem key={st.id} value={st.code}>{st.label}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="minGuards"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Guardie Minime*</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value}
|
|
onChange={(e) => field.onChange(parseInt(e.target.value) || 1)}
|
|
data-testid="input-edit-min-guards"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="serviceStartTime"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Orario Inizio Servizio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="time"
|
|
{...field}
|
|
value={field.value || ""}
|
|
data-testid="input-edit-service-start-time"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="serviceEndTime"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Orario Fine Servizio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="time"
|
|
{...field}
|
|
value={field.value || ""}
|
|
data-testid="input-edit-service-end-time"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="requiresArmed"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Richiede Armato</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? false}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-edit-requires-armed"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="requiresDriverLicense"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Richiede Patente</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? false}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-edit-requires-driver"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={editForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Sito Attivo</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? true}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-edit-is-active"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setEditDialogOpen(false);
|
|
setSelectedSite(null);
|
|
}}
|
|
data-testid="button-edit-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={updateSiteMutation.isPending}
|
|
data-testid="button-update-site"
|
|
>
|
|
{updateSiteMutation.isPending ? "Salvataggio..." : "Aggiorna Sito"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Manage Service Types Dialog */}
|
|
<Dialog open={manageTypesDialogOpen} onOpenChange={setManageTypesDialogOpen}>
|
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Gestione Tipologie Servizio</DialogTitle>
|
|
<DialogDescription>
|
|
Configura le tipologie di servizio disponibili nel sistema
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
<Button
|
|
onClick={() => setCreateTypeDialogOpen(true)}
|
|
className="w-full"
|
|
data-testid="button-add-service-type"
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Aggiungi Nuova Tipologia
|
|
</Button>
|
|
|
|
<div className="space-y-3">
|
|
{serviceTypes.map((type) => {
|
|
const Icon = getIconComponent(type.icon);
|
|
return (
|
|
<Card key={type.id} className={!type.isActive ? "opacity-50" : ""}>
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3 flex-1">
|
|
<div className={`p-2 rounded-lg ${getColorClasses(type.color)}`}>
|
|
<Icon className="h-5 w-5" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="font-semibold">{type.label}</h3>
|
|
<Badge variant="outline" className="text-xs">
|
|
{type.code}
|
|
</Badge>
|
|
{!type.isActive && (
|
|
<Badge variant="secondary" className="text-xs">
|
|
Disattivato
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{type.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
onClick={() => handleEditType(type)}
|
|
data-testid={`button-edit-service-type-${type.code}`}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Create Service Type Dialog */}
|
|
<Dialog open={createTypeDialogOpen} onOpenChange={setCreateTypeDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Nuova Tipologia Servizio</DialogTitle>
|
|
<DialogDescription>
|
|
Crea una nuova tipologia di servizio
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...createTypeForm}>
|
|
<form
|
|
onSubmit={createTypeForm.handleSubmit((data) => createTypeMutation.mutate(data))}
|
|
className="space-y-4"
|
|
>
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="code"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Codice*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} placeholder="es: fixed_post" data-testid="input-type-code" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="label"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} placeholder="es: Presidio Fisso" data-testid="input-type-label" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="description"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Descrizione*</FormLabel>
|
|
<FormControl>
|
|
<Textarea {...field} value={field.value || ""} placeholder="Descrizione del servizio" data-testid="input-type-description" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="icon"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Icona*</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-type-icon">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="Building2">Building2</SelectItem>
|
|
<SelectItem value="Shield">Shield</SelectItem>
|
|
<SelectItem value="Eye">Eye</SelectItem>
|
|
<SelectItem value="Zap">Zap</SelectItem>
|
|
<SelectItem value="Car">Car</SelectItem>
|
|
<SelectItem value="Users">Users</SelectItem>
|
|
<SelectItem value="MapPin">MapPin</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="color"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Colore*</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-type-color">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="blue">Blu</SelectItem>
|
|
<SelectItem value="green">Verde</SelectItem>
|
|
<SelectItem value="purple">Viola</SelectItem>
|
|
<SelectItem value="orange">Arancione</SelectItem>
|
|
<SelectItem value="red">Rosso</SelectItem>
|
|
<SelectItem value="yellow">Giallo</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
{/* ✅ NUOVO: Classification (Fisso/Mobile) */}
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="classification"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo Pianificazione*</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-type-classification">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="fisso">Fisso (Planning Fissi)</SelectItem>
|
|
<SelectItem value="mobile">Mobile (Planning Mobile)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={createTypeForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Tipologia Attiva</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? true}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-type-active"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setCreateTypeDialogOpen(false)}
|
|
data-testid="button-type-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={createTypeMutation.isPending}
|
|
data-testid="button-save-type"
|
|
>
|
|
{createTypeMutation.isPending ? "Salvataggio..." : "Crea Tipologia"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Edit Service Type Dialog */}
|
|
<Dialog open={editTypeDialogOpen} onOpenChange={setEditTypeDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Modifica Tipologia Servizio</DialogTitle>
|
|
<DialogDescription>
|
|
Modifica le informazioni della tipologia
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...editTypeForm}>
|
|
<form
|
|
onSubmit={editTypeForm.handleSubmit((data) =>
|
|
selectedType && updateTypeMutation.mutate({ id: selectedType.id, data })
|
|
)}
|
|
className="space-y-4"
|
|
>
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="code"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Codice*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} data-testid="input-edit-type-code" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="label"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Nome*</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} data-testid="input-edit-type-label" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="description"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Descrizione*</FormLabel>
|
|
<FormControl>
|
|
<Textarea {...field} value={field.value || ""} data-testid="input-edit-type-description" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="icon"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Icona*</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-edit-type-icon">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="Building2">Building2</SelectItem>
|
|
<SelectItem value="Shield">Shield</SelectItem>
|
|
<SelectItem value="Eye">Eye</SelectItem>
|
|
<SelectItem value="Zap">Zap</SelectItem>
|
|
<SelectItem value="Car">Car</SelectItem>
|
|
<SelectItem value="Users">Users</SelectItem>
|
|
<SelectItem value="MapPin">MapPin</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="color"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Colore*</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-edit-type-color">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="blue">Blu</SelectItem>
|
|
<SelectItem value="green">Verde</SelectItem>
|
|
<SelectItem value="purple">Viola</SelectItem>
|
|
<SelectItem value="orange">Arancione</SelectItem>
|
|
<SelectItem value="red">Rosso</SelectItem>
|
|
<SelectItem value="yellow">Giallo</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
{/* ✅ NUOVO: Classification (Fisso/Mobile) */}
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="classification"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo Pianificazione*</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger data-testid="select-edit-type-classification">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="fisso">Fisso (Planning Fissi)</SelectItem>
|
|
<SelectItem value="mobile">Mobile (Planning Mobile)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="space-y-4 p-4 border rounded-lg" style={{display: "none"}}>
|
|
<h4 className="font-semibold text-sm">Parametri Specifici</h4>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="fixedPostHours"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Ore Presidio Fisso</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value || ""}
|
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
placeholder="es: 8, 12"
|
|
data-testid="input-edit-fixed-post-hours"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="patrolPassages"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Passaggi Pattugliamento</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value || ""}
|
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
placeholder="es: 3, 5"
|
|
data-testid="input-edit-patrol-passages"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="inspectionFrequency"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Frequenza Ispezioni (min)</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value || ""}
|
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
placeholder="es: 60, 120"
|
|
data-testid="input-edit-inspection-frequency"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="responseTimeMinutes"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tempo Risposta (min)</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
{...field}
|
|
value={field.value || ""}
|
|
onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
placeholder="es: 15, 30"
|
|
data-testid="input-edit-response-time"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<FormField
|
|
control={editTypeForm.control}
|
|
name="isActive"
|
|
render={({ field }) => (
|
|
<FormItem className="flex items-center justify-between p-3 border rounded-lg">
|
|
<FormLabel>Tipologia Attiva</FormLabel>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value ?? true}
|
|
onCheckedChange={field.onChange}
|
|
data-testid="switch-edit-type-active"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setEditTypeDialogOpen(false);
|
|
setSelectedType(null);
|
|
}}
|
|
data-testid="button-edit-type-cancel"
|
|
>
|
|
Annulla
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={updateTypeMutation.isPending}
|
|
data-testid="button-update-type"
|
|
>
|
|
{updateTypeMutation.isPending ? "Salvataggio..." : "Aggiorna Tipologia"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|