Files
edr-platform/apps/edr-freight-web/backoffice/src/components/cargoes/CargoFormDialog.tsx
2026-06-05 21:07:04 +03:00

239 lines
7.0 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import axios from 'axios';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Loader2 } from 'lucide-react';
interface Cargo {
id: string;
cargoReference: string;
description: string;
quantity: number;
weight: number;
status: 'PENDING' | 'LOADED' | 'IN_TRANSIT' | 'DELIVERED' | 'CANCELLED';
remarks?: string;
}
interface CargoFormDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
cargo?: Cargo | null;
onSuccess?: () => void;
}
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001';
export default function CargoFormDialog({
open,
onOpenChange,
cargo,
onSuccess,
}: CargoFormDialogProps) {
const queryClient = useQueryClient();
const [formData, setFormData] = useState<Partial<Cargo>>({
cargoReference: '',
description: '',
quantity: 0,
weight: 0,
status: 'PENDING',
remarks: '',
});
useEffect(() => {
if (cargo) {
setFormData(cargo);
} else {
setFormData({
cargoReference: '',
description: '',
quantity: 0,
weight: 0,
status: 'PENDING',
remarks: '',
});
}
}, [cargo, open]);
const createMutation = useMutation({
mutationFn: (data: Partial<Cargo>) =>
axios.post(`${API_BASE_URL}/api/cargoes`, data),
onSuccess: () => {
toast.success('Cargo created successfully');
onOpenChange(false);
queryClient.invalidateQueries({ queryKey: ['cargoes'] });
onSuccess?.();
},
onError: (error) => {
const message = axios.isAxiosError(error)
? error.response?.data?.message || 'Failed to create cargo'
: 'Failed to create cargo';
toast.error(message);
},
});
const updateMutation = useMutation({
mutationFn: (data: Partial<Cargo>) =>
axios.patch(`${API_BASE_URL}/api/cargoes/${cargo?.id}`, data),
onSuccess: () => {
toast.success('Cargo updated successfully');
onOpenChange(false);
queryClient.invalidateQueries({ queryKey: ['cargoes'] });
onSuccess?.();
},
onError: (error) => {
const message = axios.isAxiosError(error)
? error.response?.data?.message || 'Failed to update cargo'
: 'Failed to update cargo';
toast.error(message);
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!formData.cargoReference || !formData.description) {
toast.error('Please fill in all required fields');
return;
}
if (cargo?.id) {
updateMutation.mutate(formData);
} else {
createMutation.mutate(formData);
}
};
const isLoading = createMutation.isPending || updateMutation.isPending;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{cargo ? 'Edit Cargo' : 'Create New Cargo'}
</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="cargoReference">Cargo Reference *</Label>
<Input
id="cargoReference"
value={formData.cargoReference || ''}
onChange={(e) =>
setFormData({ ...formData, cargoReference: e.target.value })
}
placeholder="e.g., CRG001"
required
/>
</div>
<div>
<Label htmlFor="status">Status</Label>
<select
id="status"
value={formData.status || 'PENDING'}
onChange={(e) =>
setFormData({ ...formData, status: e.target.value as any })
}
className="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="PENDING">Pending</option>
<option value="LOADED">Loaded</option>
<option value="IN_TRANSIT">In Transit</option>
<option value="DELIVERED">Delivered</option>
<option value="CANCELLED">Cancelled</option>
</select>
</div>
</div>
<div>
<Label htmlFor="description">Description *</Label>
<Textarea
id="description"
value={formData.description || ''}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
placeholder="Describe the cargo contents..."
rows={3}
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="quantity">Quantity *</Label>
<Input
id="quantity"
type="number"
value={formData.quantity || ''}
onChange={(e) =>
setFormData({
...formData,
quantity: parseFloat(e.target.value) || 0,
})
}
placeholder="0"
required
/>
</div>
<div>
<Label htmlFor="weight">Weight (kg) *</Label>
<Input
id="weight"
type="number"
value={formData.weight || ''}
onChange={(e) =>
setFormData({
...formData,
weight: parseFloat(e.target.value) || 0,
})
}
placeholder="0"
required
/>
</div>
</div>
<div>
<Label htmlFor="remarks">Remarks</Label>
<Textarea
id="remarks"
value={formData.remarks || ''}
onChange={(e) =>
setFormData({ ...formData, remarks: e.target.value })
}
placeholder="Add any additional notes..."
rows={2}
/>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isLoading}
>
Cancel
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{cargo ? 'Update' : 'Create'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}