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
This commit is contained in:
marco370 2025-10-17 09:46:18 +00:00
parent c099eb6472
commit e6cac35763

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();
@ -66,6 +67,12 @@ export default function Services() {
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>
);
}