mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 18:20:57 +00:00
239 lines
7.0 KiB
TypeScript
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>
|
|
);
|
|
}
|