Compare commits

...

2 Commits

Author SHA1 Message Date
Marco Lanzara
918c5f0226 🚀 Release v1.0.12
- Tipo: patch
- Database backup: database-backups/vigilanzaturni_v1.0.12_20251017_094703.sql.gz
- Data: 2025-10-17 09:47:18
2025-10-17 09:47:18 +00:00
marco370
e6cac35763 Add functionality to manage service types for scheduling
Introduce state management, form handling with Zod validation, and mutation logic for creating and updating service types via API endpoints.

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/EEOXc3D
2025-10-17 09:46:18 +00:00
4 changed files with 476 additions and 2 deletions

View File

@ -58,6 +58,7 @@ const getColorClasses = (color: string) => {
};
type SiteForm = z.infer<typeof insertSiteSchema>;
type ServiceTypeForm = z.infer<typeof insertServiceTypeSchema>;
export default function Services() {
const { toast } = useToast();
@ -65,6 +66,12 @@ export default function Services() {
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"],
@ -150,6 +157,89 @@ export default function Services() {
},
});
// Service Type Forms
const createTypeForm = useForm<ServiceTypeForm>({
resolver: zodResolver(insertServiceTypeSchema),
defaultValues: {
code: "",
label: "",
description: "",
icon: "Building2",
color: "blue",
isActive: true,
},
});
const editTypeForm = useForm<ServiceTypeForm>({
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: "",
@ -206,6 +296,13 @@ export default function Services() {
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 ? (
@ -712,6 +809,377 @@ export default function Services() {
</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>
<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>
<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>
);
}

View File

@ -1,7 +1,13 @@
{
"version": "1.0.11",
"lastUpdate": "2025-10-17T09:32:00.721Z",
"version": "1.0.12",
"lastUpdate": "2025-10-17T09:47:18.311Z",
"changelog": [
{
"version": "1.0.12",
"date": "2025-10-17",
"type": "patch",
"description": "Deployment automatico v1.0.12"
},
{
"version": "1.0.11",
"date": "2025-10-17",