Introduce a new section for vehicle management, enhance user authentication with bcrypt, and implement CRUD operations for users. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 42d8028a-fa71-4ec2-938c-e43eedf7df01 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/42d8028a-fa71-4ec2-938c-e43eedf7df01/GNrPM6a
804 lines
30 KiB
TypeScript
804 lines
30 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 { insertVehicleSchema, type Vehicle, type Guard } from "@shared/schema";
|
|
import { z } from "zod";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "@/components/ui/alert-dialog";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/components/ui/form";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { Car, Plus, Pencil, Trash2, Loader2 } from "lucide-react";
|
|
|
|
const vehicleTypeLabels = {
|
|
car: "Auto",
|
|
van: "Furgone",
|
|
motorcycle: "Moto",
|
|
suv: "SUV",
|
|
};
|
|
|
|
const vehicleStatusLabels = {
|
|
available: "Disponibile",
|
|
in_use: "In uso",
|
|
maintenance: "In manutenzione",
|
|
out_of_service: "Fuori servizio",
|
|
};
|
|
|
|
const vehicleStatusColors = {
|
|
available: "bg-green-500/10 text-green-500 border-green-500/20",
|
|
in_use: "bg-blue-500/10 text-blue-500 border-blue-500/20",
|
|
maintenance: "bg-orange-500/10 text-orange-500 border-orange-500/20",
|
|
out_of_service: "bg-red-500/10 text-red-500 border-red-500/20",
|
|
};
|
|
|
|
type VehicleForm = z.infer<typeof insertVehicleSchema>;
|
|
|
|
export default function Vehicles() {
|
|
const { toast } = useToast();
|
|
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
const [selectedVehicle, setSelectedVehicle] = useState<Vehicle | null>(null);
|
|
|
|
const { data: vehicles, isLoading: isLoadingVehicles } = useQuery<Vehicle[]>({
|
|
queryKey: ["/api/vehicles"],
|
|
});
|
|
|
|
const { data: guards } = useQuery<Guard[]>({
|
|
queryKey: ["/api/guards"],
|
|
});
|
|
|
|
const createForm = useForm<VehicleForm>({
|
|
resolver: zodResolver(insertVehicleSchema.extend({
|
|
year: z.number().min(1900).max(new Date().getFullYear() + 1).optional().or(z.literal(null)),
|
|
mileage: z.number().min(0).optional().or(z.literal(null)),
|
|
})),
|
|
defaultValues: {
|
|
licensePlate: "",
|
|
brand: "",
|
|
model: "",
|
|
vehicleType: "car",
|
|
year: undefined,
|
|
assignedGuardId: null,
|
|
status: "available",
|
|
lastMaintenanceDate: null,
|
|
nextMaintenanceDate: null,
|
|
mileage: undefined,
|
|
notes: null,
|
|
},
|
|
});
|
|
|
|
const editForm = useForm<VehicleForm>({
|
|
resolver: zodResolver(insertVehicleSchema.extend({
|
|
year: z.number().min(1900).max(new Date().getFullYear() + 1).optional().or(z.literal(null)),
|
|
mileage: z.number().min(0).optional().or(z.literal(null)),
|
|
})),
|
|
defaultValues: {
|
|
licensePlate: "",
|
|
brand: "",
|
|
model: "",
|
|
vehicleType: "car",
|
|
year: undefined,
|
|
assignedGuardId: null,
|
|
status: "available",
|
|
lastMaintenanceDate: null,
|
|
nextMaintenanceDate: null,
|
|
mileage: undefined,
|
|
notes: null,
|
|
},
|
|
});
|
|
|
|
const createVehicleMutation = useMutation({
|
|
mutationFn: async (data: VehicleForm) => {
|
|
return apiRequest("POST", "/api/vehicles", data);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/vehicles"] });
|
|
toast({
|
|
title: "Veicolo creato",
|
|
description: "Il veicolo è stato aggiunto con successo.",
|
|
});
|
|
setCreateDialogOpen(false);
|
|
createForm.reset();
|
|
},
|
|
onError: (error: any) => {
|
|
toast({
|
|
title: "Errore",
|
|
description: error.message || "Impossibile creare il veicolo.",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const updateVehicleMutation = useMutation({
|
|
mutationFn: async ({ id, data }: { id: string; data: VehicleForm }) => {
|
|
return apiRequest("PATCH", `/api/vehicles/${id}`, data);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/vehicles"] });
|
|
toast({
|
|
title: "Veicolo aggiornato",
|
|
description: "Il veicolo è stato modificato con successo.",
|
|
});
|
|
setEditDialogOpen(false);
|
|
setSelectedVehicle(null);
|
|
editForm.reset();
|
|
},
|
|
onError: (error: any) => {
|
|
toast({
|
|
title: "Errore",
|
|
description: error.message || "Impossibile aggiornare il veicolo.",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const deleteVehicleMutation = useMutation({
|
|
mutationFn: async (id: string) => {
|
|
return apiRequest("DELETE", `/api/vehicles/${id}`);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["/api/vehicles"] });
|
|
toast({
|
|
title: "Veicolo eliminato",
|
|
description: "Il veicolo è stato eliminato con successo.",
|
|
});
|
|
setDeleteDialogOpen(false);
|
|
setSelectedVehicle(null);
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: "Errore",
|
|
description: "Impossibile eliminare il veicolo.",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const handleEdit = (vehicle: Vehicle) => {
|
|
setSelectedVehicle(vehicle);
|
|
editForm.reset({
|
|
licensePlate: vehicle.licensePlate,
|
|
brand: vehicle.brand,
|
|
model: vehicle.model,
|
|
vehicleType: vehicle.vehicleType,
|
|
year: vehicle.year ?? undefined,
|
|
assignedGuardId: vehicle.assignedGuardId,
|
|
status: vehicle.status,
|
|
lastMaintenanceDate: vehicle.lastMaintenanceDate,
|
|
nextMaintenanceDate: vehicle.nextMaintenanceDate,
|
|
mileage: vehicle.mileage ?? undefined,
|
|
notes: vehicle.notes,
|
|
});
|
|
setEditDialogOpen(true);
|
|
};
|
|
|
|
const handleDelete = (vehicle: Vehicle) => {
|
|
setSelectedVehicle(vehicle);
|
|
setDeleteDialogOpen(true);
|
|
};
|
|
|
|
if (isLoadingVehicles) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Parco Automezzi</h1>
|
|
<p className="text-muted-foreground">Gestione veicoli aziendali</p>
|
|
</div>
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
<div className="space-y-4">
|
|
{[1, 2, 3].map((i) => (
|
|
<div key={i} className="flex items-center gap-4">
|
|
<Skeleton className="h-12 w-12 rounded" />
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-48" />
|
|
<Skeleton className="h-3 w-32" />
|
|
</div>
|
|
<Skeleton className="h-9 w-32" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold" data-testid="text-page-title">
|
|
Parco Automezzi
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
Gestisci i veicoli aziendali e le assegnazioni
|
|
</p>
|
|
</div>
|
|
<Button onClick={() => setCreateDialogOpen(true)} data-testid="button-add-vehicle">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Aggiungi Veicolo
|
|
</Button>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Veicoli Registrati</CardTitle>
|
|
<CardDescription>
|
|
{vehicles?.length || 0} veicoli nel parco aziendale
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Targa</TableHead>
|
|
<TableHead>Veicolo</TableHead>
|
|
<TableHead>Tipo</TableHead>
|
|
<TableHead>Stato</TableHead>
|
|
<TableHead>Assegnato a</TableHead>
|
|
<TableHead className="text-right">Azioni</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{vehicles?.map((vehicle) => {
|
|
const assignedGuard = guards?.find(g => g.id === vehicle.assignedGuardId);
|
|
|
|
return (
|
|
<TableRow key={vehicle.id} data-testid={`row-vehicle-${vehicle.id}`}>
|
|
<TableCell className="font-medium" data-testid={`text-plate-${vehicle.id}`}>
|
|
{vehicle.licensePlate}
|
|
</TableCell>
|
|
<TableCell>
|
|
<div>
|
|
<p className="font-medium">{vehicle.brand} {vehicle.model}</p>
|
|
{vehicle.year && <p className="text-sm text-muted-foreground">Anno {vehicle.year}</p>}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>{vehicleTypeLabels[vehicle.vehicleType]}</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
variant="outline"
|
|
className={vehicleStatusColors[vehicle.status]}
|
|
data-testid={`badge-status-${vehicle.id}`}
|
|
>
|
|
{vehicleStatusLabels[vehicle.status]}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
{assignedGuard ? (
|
|
<span className="text-sm">{assignedGuard.badgeNumber}</span>
|
|
) : (
|
|
<span className="text-sm text-muted-foreground">Non assegnato</span>
|
|
)}
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<div className="flex justify-end gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleEdit(vehicle)}
|
|
data-testid={`button-edit-${vehicle.id}`}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleDelete(vehicle)}
|
|
data-testid={`button-delete-${vehicle.id}`}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
{vehicles?.length === 0 && (
|
|
<div className="text-center py-8 text-muted-foreground">
|
|
<Car className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
|
<p>Nessun veicolo registrato</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Create Vehicle Dialog */}
|
|
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto" data-testid="dialog-create-vehicle">
|
|
<DialogHeader>
|
|
<DialogTitle>Aggiungi Nuovo Veicolo</DialogTitle>
|
|
<DialogDescription>
|
|
Inserisci i dati del veicolo da aggiungere al parco aziendale.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...createForm}>
|
|
<form onSubmit={createForm.handleSubmit((data) => createVehicleMutation.mutate(data))} className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="licensePlate"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Targa *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="AB123CD" data-testid="input-create-plate" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="vehicleType"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo *</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value} data-testid="select-create-type">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="car">Auto</SelectItem>
|
|
<SelectItem value="van">Furgone</SelectItem>
|
|
<SelectItem value="motorcycle">Moto</SelectItem>
|
|
<SelectItem value="suv">SUV</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="brand"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Marca *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Fiat" data-testid="input-create-brand" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="model"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Modello *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="500" data-testid="input-create-model" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="year"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Anno</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="2024"
|
|
data-testid="input-create-year"
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
onChange={e => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={createForm.control}
|
|
name="status"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Stato *</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value} data-testid="select-create-status">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="available">Disponibile</SelectItem>
|
|
<SelectItem value="in_use">In uso</SelectItem>
|
|
<SelectItem value="maintenance">In manutenzione</SelectItem>
|
|
<SelectItem value="out_of_service">Fuori servizio</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="assignedGuardId"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Assegnato a</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value || ""} data-testid="select-create-guard">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Seleziona guardia" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="">Nessuno</SelectItem>
|
|
{guards?.map(guard => (
|
|
<SelectItem key={guard.id} value={guard.id}>
|
|
{guard.badgeNumber}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="mileage"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Chilometraggio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="50000"
|
|
data-testid="input-create-mileage"
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
onChange={e => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={createForm.control}
|
|
name="notes"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Note</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
placeholder="Note aggiuntive sul veicolo..."
|
|
data-testid="input-create-notes"
|
|
{...field}
|
|
value={field.value || ""}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" onClick={() => setCreateDialogOpen(false)} data-testid="button-create-cancel">
|
|
Annulla
|
|
</Button>
|
|
<Button type="submit" disabled={createVehicleMutation.isPending} data-testid="button-create-submit">
|
|
{createVehicleMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Creazione...
|
|
</>
|
|
) : (
|
|
"Crea Veicolo"
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Edit Vehicle Dialog - Same structure as create */}
|
|
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto" data-testid="dialog-edit-vehicle">
|
|
<DialogHeader>
|
|
<DialogTitle>Modifica Veicolo</DialogTitle>
|
|
<DialogDescription>
|
|
Modifica i dati del veicolo {selectedVehicle?.licensePlate}.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Form {...editForm}>
|
|
<form onSubmit={editForm.handleSubmit((data) => selectedVehicle && updateVehicleMutation.mutate({ id: selectedVehicle.id, data }))} className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="licensePlate"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Targa *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="AB123CD" data-testid="input-edit-plate" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="vehicleType"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Tipo *</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value} data-testid="select-edit-type">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="car">Auto</SelectItem>
|
|
<SelectItem value="van">Furgone</SelectItem>
|
|
<SelectItem value="motorcycle">Moto</SelectItem>
|
|
<SelectItem value="suv">SUV</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="brand"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Marca *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Fiat" data-testid="input-edit-brand" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="model"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Modello *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="500" data-testid="input-edit-model" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="year"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Anno</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="2024"
|
|
data-testid="input-edit-year"
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
onChange={e => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={editForm.control}
|
|
name="status"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Stato *</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value} data-testid="select-edit-status">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="available">Disponibile</SelectItem>
|
|
<SelectItem value="in_use">In uso</SelectItem>
|
|
<SelectItem value="maintenance">In manutenzione</SelectItem>
|
|
<SelectItem value="out_of_service">Fuori servizio</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="assignedGuardId"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Assegnato a</FormLabel>
|
|
<Select onValueChange={field.onChange} value={field.value || ""} data-testid="select-edit-guard">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Seleziona guardia" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="">Nessuno</SelectItem>
|
|
{guards?.map(guard => (
|
|
<SelectItem key={guard.id} value={guard.id}>
|
|
{guard.badgeNumber}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="mileage"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Chilometraggio</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
placeholder="50000"
|
|
data-testid="input-edit-mileage"
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
onChange={e => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={editForm.control}
|
|
name="notes"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Note</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
placeholder="Note aggiuntive sul veicolo..."
|
|
data-testid="input-edit-notes"
|
|
{...field}
|
|
value={field.value || ""}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" onClick={() => setEditDialogOpen(false)} data-testid="button-edit-cancel">
|
|
Annulla
|
|
</Button>
|
|
<Button type="submit" disabled={updateVehicleMutation.isPending} data-testid="button-edit-submit">
|
|
{updateVehicleMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Salvataggio...
|
|
</>
|
|
) : (
|
|
"Salva Modifiche"
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
<AlertDialogContent data-testid="dialog-delete-vehicle">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Conferma Eliminazione</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
Sei sicuro di voler eliminare il veicolo <strong>{selectedVehicle?.licensePlate}</strong>?
|
|
Questa azione non può essere annullata.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel data-testid="button-delete-cancel">Annulla</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={() => selectedVehicle && deleteVehicleMutation.mutate(selectedVehicle.id)}
|
|
className="bg-destructive hover:bg-destructive/90"
|
|
data-testid="button-delete-confirm"
|
|
>
|
|
{deleteVehicleMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Eliminazione...
|
|
</>
|
|
) : (
|
|
"Elimina"
|
|
)}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
);
|
|
}
|