mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
trains, wagons,containers and cargoes schema and API
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { useCargoesByContainer, useDeliverCargo, useUnloadCargo } from '@/hooks/useCargoes';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { LoadCargoDialog } from './LoadCargoDialog';
|
||||
|
||||
export function CargoesTable({ containerId }: { containerId: string }) {
|
||||
const { data: cargoes, refetch } = useCargoesByContainer(containerId);
|
||||
const deliver = useDeliverCargo();
|
||||
const unload = useUnloadCargo();
|
||||
|
||||
if (!cargoes?.length) return <div className="text-muted-foreground">No cargoes for this container.</div>;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Reference</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Quantity</TableHead>
|
||||
<TableHead>Weight (kg)</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{cargoes.map(cargo => (
|
||||
<TableRow key={cargo.id}>
|
||||
<TableCell>{cargo.cargoReference}</TableCell>
|
||||
<TableCell>{cargo.description || '-'}</TableCell>
|
||||
<TableCell>{cargo.quantity}</TableCell>
|
||||
<TableCell>{cargo.weight}</TableCell>
|
||||
<TableCell><Badge variant="outline">{cargo.status}</Badge></TableCell>
|
||||
<TableCell className="space-x-2">
|
||||
{cargo.status === 'PENDING' && <LoadCargoDialog cargoId={cargo.id} onSuccess={() => refetch()} />}
|
||||
{cargo.status === 'LOADED' && <Button size="sm" onClick={() => deliver.mutateAsync(cargo.id).then(() => refetch())}>Deliver</Button>}
|
||||
{cargo.status === 'LOADED' && <Button size="sm" variant="outline" onClick={() => unload.mutateAsync(cargo.id).then(() => refetch())}>Unload</Button>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useLoadCargo } from '@/hooks/useCargoes';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
export function LoadCargoDialog({ cargoId, onSuccess }: { cargoId: string; onSuccess?: () => void }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [quantity, setQuantity] = useState(0);
|
||||
const [weight, setWeight] = useState(0);
|
||||
const [volume, setVolume] = useState<number>();
|
||||
const load = useLoadCargo();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleLoad = async () => {
|
||||
await load.mutateAsync({ id: cargoId, quantity, weight, volume });
|
||||
toast({ title: 'Loaded', description: 'Cargo loaded into container.' });
|
||||
setOpen(false);
|
||||
onSuccess?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild><Button size="sm">Load Cargo</Button></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>Load Cargo</DialogTitle></DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div><Label>Quantity*</Label><Input type="number" required value={quantity} onChange={e => setQuantity(parseFloat(e.target.value))} /></div>
|
||||
<div><Label>Weight (kg)*</Label><Input type="number" required value={weight} onChange={e => setWeight(parseFloat(e.target.value))} /></div>
|
||||
<div><Label>Volume (m³)</Label><Input type="number" value={volume ?? ''} onChange={e => setVolume(parseFloat(e.target.value) || undefined)} /></div>
|
||||
<Button onClick={handleLoad} disabled={load.isPending}>Confirm Load</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useContainers, useAssignContainerToWagon } from '@/hooks/useContainers';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
export function AssignContainerDialog({ wagonId }: { wagonId: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [containerId, setContainerId] = useState('');
|
||||
const [position, setPosition] = useState<number>();
|
||||
const { data: containers } = useContainers();
|
||||
const assign = useAssignContainerToWagon();
|
||||
const { toast } = useToast();
|
||||
|
||||
const available = containers?.filter(c => c.status === 'AVAILABLE' && !c.wagonId);
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!containerId) return;
|
||||
await assign.mutateAsync({ containerId, wagonId, position });
|
||||
toast({ title: 'Assigned', description: 'Container placed on wagon.' });
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild><Button size="sm"><Plus className="mr-2 h-4 w-4" />Assign Container</Button></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>Assign Container to Wagon</DialogTitle></DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div><Label>Container</Label><Select value={containerId} onValueChange={setContainerId}><SelectTrigger><SelectValue placeholder="Select container" /></SelectTrigger><SelectContent>{available?.map(c => <SelectItem key={c.id} value={c.id}>{c.containerNumber}</SelectItem>)}</SelectContent></Select></div>
|
||||
<div><Label>Position (optional)</Label><Input type="number" value={position ?? ''} onChange={e => setPosition(parseInt(e.target.value) || undefined)} /></div>
|
||||
<Button onClick={handleAssign} disabled={assign.isPending}>Assign</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useContainersByWagon, useUnassignContainer } from '@/hooks/useContainers';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
export function ContainersTable({ wagonId }: { wagonId: string }) {
|
||||
const { data: containers, refetch } = useContainersByWagon(wagonId);
|
||||
const unassign = useUnassignContainer();
|
||||
|
||||
if (!containers?.length) return <div className="text-muted-foreground">No containers assigned.</div>;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Number</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Position</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{containers.map(container => (
|
||||
<TableRow key={container.id}>
|
||||
<TableCell>{container.containerNumber}</TableCell>
|
||||
<TableCell>{container.containerTypeId}</TableCell>
|
||||
<TableCell>{container.position}</TableCell>
|
||||
<TableCell>{container.status}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="icon" onClick={() => unassign.mutateAsync(container.id).then(() => refetch())}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Train } from '@/services/trainService';
|
||||
|
||||
export function TrainDetailCard({ train }: { train: Train }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle>{train.trainNumber || train.code} - {train.trainName || 'Unnamed'}</CardTitle></CardHeader>
|
||||
<CardContent className="grid md:grid-cols-2 gap-4">
|
||||
<div><span className="font-medium">Status:</span> {train.status}</div>
|
||||
<div><span className="font-medium">Capacity:</span> {train.capacityTons} tons</div>
|
||||
<div><span className="font-medium">Origin:</span> {train.originStationId || '-'}</div>
|
||||
<div><span className="font-medium">Destination:</span> {train.destinationStationId || '-'}</div>
|
||||
<div><span className="font-medium">Departure:</span> {train.departureTime ? new Date(train.departureTime).toLocaleString() : '-'}</div>
|
||||
<div><span className="font-medium">Arrival:</span> {train.arrivalTime ? new Date(train.arrivalTime).toLocaleString() : '-'}</div>
|
||||
{train.remarks && <div className="col-span-2"><span className="font-medium">Remarks:</span> {train.remarks}</div>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useCreateTrain, useUpdateTrain } from '@/hooks/useTrains';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface TrainFormDialogProps {
|
||||
trigger?: React.ReactNode;
|
||||
train?: any;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function TrainFormDialog({ trigger, train, onSuccess }: TrainFormDialogProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [form, setForm] = useState({ code: '', capacityTons: 0, trainNumber: '', trainName: '' });
|
||||
const createTrain = useCreateTrain();
|
||||
const updateTrain = useUpdateTrain();
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (train) setForm({
|
||||
code: train.code,
|
||||
capacityTons: train.capacityTons,
|
||||
trainNumber: train.trainNumber || '',
|
||||
trainName: train.trainName || '',
|
||||
});
|
||||
}, [train]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
if (train) await updateTrain.mutateAsync({ id: train.id, data: form });
|
||||
else await createTrain.mutateAsync(form);
|
||||
toast({ title: train ? 'Train updated' : 'Train created', description: `${form.code} saved.` });
|
||||
setOpen(false);
|
||||
onSuccess?.();
|
||||
} catch {
|
||||
toast({ title: 'Error', description: `Failed to ${train ? 'update' : 'create'} train.`, variant: 'destructive' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{trigger || <Button>New Train</Button>}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>{train ? 'Edit Train' : 'Create Train'}</DialogTitle></DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div><Label>Code*</Label><Input required value={form.code} onChange={e => setForm({...form, code: e.target.value})} /></div>
|
||||
<div><Label>Capacity (tons)*</Label><Input type="number" required value={form.capacityTons} onChange={e => setForm({...form, capacityTons: parseFloat(e.target.value)})} /></div>
|
||||
<div><Label>Train Number</Label><Input value={form.trainNumber} onChange={e => setForm({...form, trainNumber: e.target.value})} /></div>
|
||||
<div><Label>Train Name</Label><Input value={form.trainName} onChange={e => setForm({...form, trainName: e.target.value})} /></div>
|
||||
<Button type="submit" disabled={createTrain.isPending || updateTrain.isPending}>Save</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useTrains, useDeleteTrain } from '@/hooks/useTrains';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Eye, Trash2 } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export function TrainsTable() {
|
||||
const { data: trains, isLoading } = useTrains();
|
||||
const deleteTrain = useDeleteTrain();
|
||||
|
||||
if (isLoading) return <div>Loading trains...</div>;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Number</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Capacity (tons)</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{trains?.map(train => (
|
||||
<TableRow key={train.id}>
|
||||
<TableCell>{train.trainNumber || train.code}</TableCell>
|
||||
<TableCell>{train.trainName || '-'}</TableCell>
|
||||
<TableCell><Badge variant="outline">{train.status}</Badge></TableCell>
|
||||
<TableCell>{train.capacityTons}</TableCell>
|
||||
<TableCell className="flex space-x-2">
|
||||
<Link to={`/trains/${train.id}`}>
|
||||
<Button variant="ghost" size="icon"><Eye className="h-4 w-4" /></Button>
|
||||
</Link>
|
||||
<Button variant="ghost" size="icon" onClick={() => deleteTrain.mutate(train.id)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useState } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useWagons, useAssignWagonToTrain } from '@/hooks/useWagons';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
export function AssignWagonDialog({ trainId }: { trainId: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [wagonId, setWagonId] = useState('');
|
||||
const [sequence, setSequence] = useState<number>();
|
||||
const { data: wagons } = useWagons();
|
||||
const assign = useAssignWagonToTrain();
|
||||
const { toast } = useToast();
|
||||
|
||||
const available = wagons?.filter(w => w.status === 'AVAILABLE' || !w.trainId);
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!wagonId) return;
|
||||
await assign.mutateAsync({ wagonId, trainId, sequenceNumber: sequence });
|
||||
toast({ title: 'Assigned', description: 'Wagon attached to train.' });
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild><Button size="sm"><Plus className="mr-2 h-4 w-4" />Assign Wagon</Button></DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>Assign Wagon to Train</DialogTitle></DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>Wagon</Label>
|
||||
<Select value={wagonId} onValueChange={setWagonId}>
|
||||
<SelectTrigger><SelectValue placeholder="Select wagon" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{available?.map(w => <SelectItem key={w.id} value={w.id}>{w.wagonNumber}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Sequence (optional)</Label>
|
||||
<Input type="number" value={sequence ?? ''} onChange={e => setSequence(parseInt(e.target.value) || undefined)} />
|
||||
</div>
|
||||
<Button onClick={handleAssign} disabled={assign.isPending}>Assign</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// src/components/wagons/WagonFormDialog.tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useCreateWagon, useUpdateWagon } from '@/hooks/useWagons';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface WagonFormDialogProps {
|
||||
trigger?: React.ReactNode;
|
||||
wagon?: any;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export function WagonFormDialog({ trigger, wagon, onSuccess }: WagonFormDialogProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [form, setForm] = useState({
|
||||
wagonNumber: '',
|
||||
wagonTypeId: '',
|
||||
tareWeight: 0,
|
||||
maxPayloadWeight: 0,
|
||||
status: 'AVAILABLE',
|
||||
notes: ''
|
||||
});
|
||||
const createWagon = useCreateWagon();
|
||||
const updateWagon = useUpdateWagon();
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (wagon) setForm({
|
||||
wagonNumber: wagon.wagonNumber,
|
||||
wagonTypeId: wagon.wagonTypeId,
|
||||
tareWeight: wagon.tareWeight,
|
||||
maxPayloadWeight: wagon.maxPayloadWeight,
|
||||
status: wagon.status,
|
||||
notes: wagon.notes || ''
|
||||
});
|
||||
}, [wagon]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
if (wagon) await updateWagon.mutateAsync({ id: wagon.id, data: form });
|
||||
else await createWagon.mutateAsync(form);
|
||||
toast({ title: wagon ? 'Wagon updated' : 'Wagon created', description: `${form.wagonNumber} saved.` });
|
||||
setOpen(false);
|
||||
onSuccess?.();
|
||||
} catch {
|
||||
toast({ title: 'Error', description: `Failed to ${wagon ? 'update' : 'create'} wagon.`, variant: 'destructive' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{trigger || <Button>New Wagon</Button>}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>{wagon ? 'Edit Wagon' : 'Create Wagon'}</DialogTitle></DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div><Label>Wagon Number*</Label><Input required value={form.wagonNumber} onChange={e => setForm({...form, wagonNumber: e.target.value})} /></div>
|
||||
<div><Label>Wagon Type ID*</Label><Input required value={form.wagonTypeId} onChange={e => setForm({...form, wagonTypeId: e.target.value})} /></div>
|
||||
<div><Label>Tare Weight (kg)*</Label><Input type="number" required value={form.tareWeight} onChange={e => setForm({...form, tareWeight: parseFloat(e.target.value)})} /></div>
|
||||
<div><Label>Max Payload (kg)*</Label><Input type="number" required value={form.maxPayloadWeight} onChange={e => setForm({...form, maxPayloadWeight: parseFloat(e.target.value)})} /></div>
|
||||
<div><Label>Status</Label><Input value={form.status} onChange={e => setForm({...form, status: e.target.value})} /></div>
|
||||
<div><Label>Notes</Label><Input value={form.notes} onChange={e => setForm({...form, notes: e.target.value})} /></div>
|
||||
<Button type="submit" disabled={createWagon.isPending || updateWagon.isPending}>Save</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useWagonsByTrain, useUnassignWagon, useReorderWagons } from '@/hooks/useWagons';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Trash2, GripVertical } from 'lucide-react';
|
||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||
|
||||
export function WagonsTable({ trainId }: { trainId: string }) {
|
||||
const { data: wagons, refetch } = useWagonsByTrain(trainId);
|
||||
const unassign = useUnassignWagon();
|
||||
const reorder = useReorderWagons();
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
if (!result.destination) return;
|
||||
const items = Array.from(wagons || []);
|
||||
const [removed] = items.splice(result.source.index, 1);
|
||||
items.splice(result.destination.index, 0, removed);
|
||||
reorder.mutate({ trainId, wagonIds: items.map(w => w.id) });
|
||||
};
|
||||
|
||||
if (!wagons?.length) return <div className="text-muted-foreground">No wagons assigned.</div>;
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="wagons">
|
||||
{(provided) => (
|
||||
<Table {...provided.droppableProps} ref={provided.innerRef}>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-10"></TableHead>
|
||||
<TableHead>Number</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Sequence</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{wagons.map((wagon, idx) => (
|
||||
<Draggable key={wagon.id} draggableId={wagon.id} index={idx}>
|
||||
{(provided) => (
|
||||
<TableRow ref={provided.innerRef} {...provided.draggableProps}>
|
||||
<TableCell {...provided.dragHandleProps}><GripVertical className="h-4 w-4 cursor-grab" /></TableCell>
|
||||
<TableCell>{wagon.wagonNumber}</TableCell>
|
||||
<TableCell>{wagon.wagonTypeId}</TableCell>
|
||||
<TableCell>{wagon.sequenceNumber}</TableCell>
|
||||
<TableCell>{wagon.status}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="icon" onClick={() => unassign.mutateAsync(wagon.id).then(() => refetch())}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
39
apps/edr-freight-web/backoffice/src/hooks/useCargoes.ts
Normal file
39
apps/edr-freight-web/backoffice/src/hooks/useCargoes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { cargoService } from '@/services/cargoService';
|
||||
|
||||
export const cargoKeys = {
|
||||
all: ['cargoes'] as const,
|
||||
byContainer: (containerId: string) => [...cargoKeys.all, 'container', containerId] as const,
|
||||
};
|
||||
|
||||
export function useCargoes() {
|
||||
return useQuery({ queryKey: cargoKeys.all, queryFn: () => cargoService.getAll().then(res => res.data) });
|
||||
}
|
||||
|
||||
export function useCargoesByContainer(containerId: string) {
|
||||
return useQuery({ queryKey: cargoKeys.byContainer(containerId), queryFn: () => cargoService.getByContainer(containerId).then(res => res.data), enabled: !!containerId });
|
||||
}
|
||||
|
||||
export function useLoadCargo() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, quantity, weight, volume }: any) => cargoService.load(id, quantity, weight, volume),
|
||||
onSuccess: (_, { id }) => qc.invalidateQueries({ queryKey: cargoKeys.all })
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeliverCargo() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => cargoService.deliver(id),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: cargoKeys.all })
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnloadCargo() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => cargoService.unload(id),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: cargoKeys.all })
|
||||
});
|
||||
}
|
||||
31
apps/edr-freight-web/backoffice/src/hooks/useContainers.ts
Normal file
31
apps/edr-freight-web/backoffice/src/hooks/useContainers.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { containerService } from '@/services/containerService';
|
||||
|
||||
export const containerKeys = {
|
||||
all: ['containers'] as const,
|
||||
byWagon: (wagonId: string) => [...containerKeys.all, 'wagon', wagonId] as const,
|
||||
};
|
||||
|
||||
export function useContainers() {
|
||||
return useQuery({ queryKey: containerKeys.all, queryFn: () => containerService.getAll().then(res => res.data) });
|
||||
}
|
||||
|
||||
export function useContainersByWagon(wagonId: string) {
|
||||
return useQuery({ queryKey: containerKeys.byWagon(wagonId), queryFn: () => containerService.getByWagon(wagonId).then(res => res.data), enabled: !!wagonId });
|
||||
}
|
||||
|
||||
export function useAssignContainerToWagon() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ containerId, wagonId, position }: any) => containerService.assignToWagon(containerId, wagonId, position),
|
||||
onSuccess: (_, { wagonId }) => qc.invalidateQueries({ queryKey: containerKeys.byWagon(wagonId) })
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnassignContainer() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: containerService.unassign,
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: containerKeys.all })
|
||||
});
|
||||
}
|
||||
35
apps/edr-freight-web/backoffice/src/hooks/useTrains.ts
Normal file
35
apps/edr-freight-web/backoffice/src/hooks/useTrains.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { trainService } from '@/services/trainService';
|
||||
|
||||
export const trainKeys = {
|
||||
all: ['trains'] as const,
|
||||
lists: () => [...trainKeys.all, 'list'] as const,
|
||||
details: () => [...trainKeys.all, 'detail'] as const,
|
||||
detail: (id: string) => [...trainKeys.details(), id] as const,
|
||||
};
|
||||
|
||||
export function useTrains() {
|
||||
return useQuery({ queryKey: trainKeys.lists(), queryFn: () => trainService.getAll().then(res => res.data) });
|
||||
}
|
||||
|
||||
export function useTrain(id: string) {
|
||||
return useQuery({ queryKey: trainKeys.detail(id), queryFn: () => trainService.getById(id).then(res => res.data), enabled: !!id });
|
||||
}
|
||||
|
||||
export function useCreateTrain() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: trainService.create, onSuccess: () => qc.invalidateQueries({ queryKey: trainKeys.lists() }) });
|
||||
}
|
||||
|
||||
export function useUpdateTrain() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: ({ id, data }: any) => trainService.update(id, data), onSuccess: (_, { id }) => {
|
||||
qc.invalidateQueries({ queryKey: trainKeys.lists() });
|
||||
qc.invalidateQueries({ queryKey: trainKeys.detail(id) });
|
||||
} });
|
||||
}
|
||||
|
||||
export function useDeleteTrain() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: trainService.delete, onSuccess: () => qc.invalidateQueries({ queryKey: trainKeys.lists() }) });
|
||||
}
|
||||
41
apps/edr-freight-web/backoffice/src/hooks/useWagons.ts
Normal file
41
apps/edr-freight-web/backoffice/src/hooks/useWagons.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { wagonService } from '@/services/wagon.service';
|
||||
|
||||
export const wagonKeys = {
|
||||
all: ['wagons'] as const,
|
||||
byTrain: (trainId: string) => [...wagonKeys.all, 'train', trainId] as const,
|
||||
details: () => [...wagonKeys.all, 'detail'] as const,
|
||||
};
|
||||
|
||||
export function useWagons() {
|
||||
return useQuery({ queryKey: wagonKeys.all, queryFn: () => wagonService.getAll().then(res => res.data) });
|
||||
}
|
||||
|
||||
export function useWagonsByTrain(trainId: string) {
|
||||
return useQuery({ queryKey: wagonKeys.byTrain(trainId), queryFn: () => wagonService.getByTrain(trainId).then(res => res.data), enabled: !!trainId });
|
||||
}
|
||||
|
||||
export function useAssignWagonToTrain() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: ({ wagonId, trainId, sequenceNumber }: any) => wagonService.assignToTrain(wagonId, trainId, sequenceNumber), onSuccess: (_, { trainId }) => qc.invalidateQueries({ queryKey: wagonKeys.byTrain(trainId) }) });
|
||||
}
|
||||
|
||||
export function useUnassignWagon() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: wagonService.unassign, onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
|
||||
}
|
||||
|
||||
export function useReorderWagons() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: ({ trainId, wagonIds }: any) => wagonService.reorder(trainId, wagonIds), onSuccess: (_, { trainId }) => qc.invalidateQueries({ queryKey: wagonKeys.byTrain(trainId) }) });
|
||||
}
|
||||
|
||||
export function useCreateWagon() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: wagonService.create, onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
|
||||
}
|
||||
|
||||
export function useUpdateWagon() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({ mutationFn: ({ id, data }: any) => wagonService.update(id, data), onSuccess: () => qc.invalidateQueries({ queryKey: wagonKeys.all }) });
|
||||
}
|
||||
@@ -7,15 +7,9 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { LoadingScreen } from '@/ui/LoadingScreen';
|
||||
import { useRateMatrixAuth } from '@/auth/hooks/useAuth';
|
||||
import { queryKeys } from '../../../constants/QUERY_KEYS';
|
||||
import { API_URLS } from '@/constants/URL_CONSTANTS';
|
||||
//import { MATRIX_STATUS } from '@/constants/rateMatrixConstants';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const API_URLS = {
|
||||
RATE_MATRIX: {
|
||||
LIST: '/api/rate-matrices',
|
||||
AUTHORIZE: (id: string) => `/api/rate-matrices/${id}/authorize`,
|
||||
},
|
||||
};
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
export default function RateMatrixApprovalPage() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// pages/admin/rateMatrix/RateMatrixRegistration.tsx
|
||||
import React from 'react';
|
||||
import { RateMatrixForm } from '@/components/baselineRatematrix/RateMatrixForm';
|
||||
import { useRateMatrixAuth } from '../../../auth/hooks/useAuth';
|
||||
import { useRateMatrixAuth } from '@/auth/useAuth';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
// Local lightweight fallback for LoadingScreen to avoid import errors
|
||||
const LoadingScreen: React.FC<{ message?: string }> = ({ message = 'Loading...' }) => (
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useContainers } from '@/hooks/useContainers';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
export default function ContainersPage() {
|
||||
const { data: containers, isLoading } = useContainers();
|
||||
if (isLoading) return <div>Loading containers...</div>;
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle>All Containers</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader><TableRow><TableHead>Number</TableHead><TableHead>Type</TableHead><TableHead>Wagon</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
|
||||
<TableBody>
|
||||
{containers?.map(c => (
|
||||
<TableRow key={c.id}>
|
||||
<TableCell>{c.containerNumber}</TableCell>
|
||||
<TableCell>{c.containerTypeId}</TableCell>
|
||||
<TableCell>{c.wagonId || 'Unassigned'}</TableCell>
|
||||
<TableCell><Badge variant="outline">{c.status}</Badge></TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTrain } from '@/hooks/useTrains';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { AssignWagonDialog } from '@/components/AssignWagonDialog';
|
||||
import { WagonsTable } from '@/components/WagonsTable';
|
||||
|
||||
export default function TrainDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { data: train, isLoading } = useTrain(id!);
|
||||
|
||||
if (isLoading) return <Skeleton className="h-96 w-full" />;
|
||||
if (!train) return <div>Train not found</div>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader><CardTitle>{train.trainNumber || train.code} - {train.trainName || 'Unnamed'}</CardTitle></CardHeader>
|
||||
<CardContent className="grid md:grid-cols-2 gap-4">
|
||||
<div><span className="font-medium">Status:</span> {train.status}</div>
|
||||
<div><span className="font-medium">Capacity:</span> {train.capacityTons} tons</div>
|
||||
<div><span className="font-medium">Origin Station:</span> {train.originStationId || '-'}</div>
|
||||
<div><span className="font-medium">Destination:</span> {train.destinationStationId || '-'}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold">Wagons</h2>
|
||||
<AssignWagonDialog trainId={train.id} />
|
||||
</div>
|
||||
<WagonsTable trainId={train.id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,78 @@
|
||||
import FeaturePlaceholder from "@/components/FeaturePlaceholder";
|
||||
import { useState } from 'react';
|
||||
import { useTrains, useDeleteTrain, useCreateTrain } from '@/hooks/useTrains';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { Plus, Eye, Trash2 } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const CreateTrainForm = ({ onSuccess }: { onSuccess: () => void }) => {
|
||||
const [form, setForm] = useState({ code: '', capacityTons: 0, trainNumber: '', trainName: '' });
|
||||
const createTrain = useCreateTrain();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await createTrain.mutateAsync(form);
|
||||
toast({ title: 'Train created', description: `${form.code} added.` });
|
||||
onSuccess();
|
||||
} catch {
|
||||
toast({ title: 'Error', description: 'Failed to create train.', variant: 'destructive' });
|
||||
}
|
||||
};
|
||||
|
||||
const TrainsPage = () => {
|
||||
return (
|
||||
<FeaturePlaceholder
|
||||
title="Trains"
|
||||
description="Coordinate train assignments, scheduling visibility, and operational readiness."
|
||||
/>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div><Label>Code*</Label><Input required value={form.code} onChange={e => setForm({...form, code: e.target.value})} /></div>
|
||||
<div><Label>Capacity (tons)*</Label><Input type="number" required value={form.capacityTons} onChange={e => setForm({...form, capacityTons: parseFloat(e.target.value)})} /></div>
|
||||
<div><Label>Train Number</Label><Input value={form.trainNumber} onChange={e => setForm({...form, trainNumber: e.target.value})} /></div>
|
||||
<div><Label>Train Name</Label><Input value={form.trainName} onChange={e => setForm({...form, trainName: e.target.value})} /></div>
|
||||
<Button type="submit" disabled={createTrain.isPending}>Save</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrainsPage;
|
||||
export default function TrainsPage() {
|
||||
const { data: trains, isLoading } = useTrains();
|
||||
const deleteTrain = useDeleteTrain();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (isLoading) return <div className="p-8">Loading trains...</div>;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Trains</CardTitle>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild><Button size="sm"><Plus className="mr-2 h-4 w-4" />New Train</Button></DialogTrigger>
|
||||
<DialogContent><DialogHeader><DialogTitle>Create Train</DialogTitle></DialogHeader><CreateTrainForm onSuccess={() => setOpen(false)} /></DialogContent>
|
||||
</Dialog>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader><TableRow><TableHead>Number</TableHead><TableHead>Name</TableHead><TableHead>Status</TableHead><TableHead>Capacity</TableHead><TableHead>Actions</TableHead></TableRow></TableHeader>
|
||||
<TableBody>
|
||||
{trains?.map(train => (
|
||||
<TableRow key={train.id}>
|
||||
<TableCell>{train.trainNumber || train.code}</TableCell>
|
||||
<TableCell>{train.trainName || '-'}</TableCell>
|
||||
<TableCell><Badge variant="outline">{train.status}</Badge></TableCell>
|
||||
<TableCell>{train.capacityTons} t</TableCell>
|
||||
<TableCell className="flex space-x-2">
|
||||
<Link to={`/trains/${train.id}`}><Button variant="ghost" size="icon"><Eye className="h-4 w-4" /></Button></Link>
|
||||
<Button variant="ghost" size="icon" onClick={() => deleteTrain.mutate(train.id)}><Trash2 className="h-4 w-4" /></Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useWagons } from '@/hooks/useWagons';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
export default function WagonsPage() {
|
||||
const { data: wagons, isLoading } = useWagons();
|
||||
if (isLoading) return <div>Loading wagons...</div>;
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle>All Wagons</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader><TableRow><TableHead>Number</TableHead><TableHead>Type</TableHead><TableHead>Train</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
|
||||
<TableBody>
|
||||
{wagons?.map(w => (
|
||||
<TableRow key={w.id}>
|
||||
<TableCell>{w.wagonNumber}</TableCell>
|
||||
<TableCell>{w.wagonTypeId}</TableCell>
|
||||
<TableCell>{w.trainId || 'Unassigned'}</TableCell>
|
||||
<TableCell><Badge variant="outline">{w.status}</Badge></TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { apiClient } from '@/lib/axios';
|
||||
|
||||
export interface Cargo {
|
||||
id: string;
|
||||
cargoReference: string;
|
||||
shipmentId: string;
|
||||
containerId: string;
|
||||
cargoTypeId?: string;
|
||||
description?: string;
|
||||
quantity: number;
|
||||
weight: number;
|
||||
volume?: number;
|
||||
status: 'PENDING' | 'LOADED' | 'IN_TRANSIT' | 'DELIVERED' | 'UNLOADED';
|
||||
loadedAt?: string;
|
||||
unloadedAt?: string;
|
||||
}
|
||||
|
||||
export const cargoService = {
|
||||
getAll: () => apiClient.get<Cargo[]>('/cargoes'),
|
||||
getByContainer: (containerId: string) => apiClient.get<Cargo[]>(`/cargoes?containerId=${containerId}`),
|
||||
create: (data: any) => apiClient.post('/cargoes', data),
|
||||
load: (cargoId: string, quantity: number, weight: number, volume?: number) =>
|
||||
apiClient.post(`/cargoes/${cargoId}/load`, { quantity, weight, volume }),
|
||||
deliver: (cargoId: string) => apiClient.post(`/cargoes/${cargoId}/deliver`),
|
||||
unload: (cargoId: string) => apiClient.post(`/cargoes/${cargoId}/unload`),
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { apiClient } from '@/lib/axios';
|
||||
|
||||
export interface Container {
|
||||
id: string;
|
||||
containerNumber: string;
|
||||
containerTypeId: string;
|
||||
wagonId: string | null;
|
||||
position: number | null;
|
||||
tareWeight: number;
|
||||
maxGrossWeight: number;
|
||||
sealNumber?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const containerService = {
|
||||
getAll: () => apiClient.get<Container[]>('/containers'),
|
||||
getByWagon: (wagonId: string) => apiClient.get<Container[]>(`/containers?wagonId=${wagonId}`),
|
||||
assignToWagon: (containerId: string, wagonId: string, position?: number) =>
|
||||
apiClient.post(`/containers/${containerId}/assign-wagon`, { wagonId, position }),
|
||||
unassign: (containerId: string) => apiClient.post(`/containers/${containerId}/unassign-wagon`),
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { apiClient } from '@/lib/axios';
|
||||
|
||||
export interface Train {
|
||||
id: string;
|
||||
code: string;
|
||||
capacityTons: number;
|
||||
trainNumber?: string;
|
||||
trainName?: string;
|
||||
routeId?: string;
|
||||
originStationId?: string;
|
||||
destinationStationId?: string;
|
||||
departureTime?: string;
|
||||
arrivalTime?: string;
|
||||
locomotiveNumber?: string;
|
||||
status: string;
|
||||
remarks?: string;
|
||||
}
|
||||
|
||||
export const trainService = {
|
||||
getAll: () => apiClient.get<Train[]>('/trains'),
|
||||
getById: (id: string) => apiClient.get<Train>(`/trains/${id}`),
|
||||
create: (data: Partial<Train>) => apiClient.post('/trains', data),
|
||||
update: (id: string, data: Partial<Train>) => apiClient.patch(`/trains/${id}`, data),
|
||||
delete: (id: string) => apiClient.delete(`/trains/${id}`),
|
||||
getDetails: (id: string) => apiClient.get(`/trains/${id}/details`),
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { apiClient } from '@/lib/axios';
|
||||
|
||||
export interface Wagon {
|
||||
id: string;
|
||||
wagonNumber: string;
|
||||
wagonTypeId: string;
|
||||
trainId: string | null;
|
||||
sequenceNumber: number | null;
|
||||
tareWeight: number;
|
||||
maxPayloadWeight: number;
|
||||
status: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export const wagonService = {
|
||||
getAll: () => apiClient.get<Wagon[]>('/wagons'),
|
||||
getByTrain: (trainId: string) => apiClient.get<Wagon[]>(`/wagons?trainId=${trainId}`),
|
||||
assignToTrain: (wagonId: string, trainId: string, sequenceNumber?: number) =>
|
||||
apiClient.post(`/wagons/${wagonId}/assign-train`, { trainId, sequenceNumber }),
|
||||
unassign: (wagonId: string) => apiClient.post(`/wagons/${wagonId}/unassign-train`),
|
||||
reorder: (trainId: string, wagonIds: string[]) =>
|
||||
apiClient.post(`/trains/${trainId}/reorder-wagons`, { wagonIds }),
|
||||
create: (data: Partial<Wagon>) => apiClient.post('/wagons', data),
|
||||
update: (id: string, data: Partial<Wagon>) => apiClient.patch(`/wagons/${id}`, data),
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user