Files
edr-platform/apps/edr-freight-web/backoffice/src/components/warehouses/MoveInventoryModal.tsx

140 lines
4.3 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import { Button, Group, Modal, Select, Stack, Textarea } from '@mantine/core';
import { useMutation, useQuery } from '@tanstack/react-query';
import { api } from '@/services/api';
import { useToast } from '@/hooks/use-toast';
import type { WarehouseInventoryItem } from '@/types/warehouse';
import { extractErrorMessage } from './options';
interface MoveInventoryModalProps {
opened: boolean;
onClose: () => void;
item: WarehouseInventoryItem | null;
}
export function MoveInventoryModal({ opened, onClose, item }: MoveInventoryModalProps) {
const { toast } = useToast();
const moveMutation = useMutation(api.warehouses.move.mutationOptions());
const [warehouseId, setWarehouseId] = useState('');
const [yardId, setYardId] = useState('');
const [zoneId, setZoneId] = useState('');
const [remarks, setRemarks] = useState('');
useEffect(() => {
if (opened) {
setWarehouseId('');
setYardId('');
setZoneId('');
setRemarks('');
}
}, [opened]);
const warehousesQuery = useQuery(
api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }),
);
const yardsQuery = useQuery(
api.warehouses.listYards.queryOptions({
input: { warehouseId },
enabled: Boolean(warehouseId),
}),
);
const zonesQuery = useQuery(
api.warehouses.listZones.queryOptions({
input: { yardId },
enabled: Boolean(yardId),
}),
);
const warehouseOptions = useMemo(
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
[warehousesQuery.data],
);
const yardOptions = useMemo(
() => (yardsQuery.data ?? []).filter((y) => y.status === 'ACTIVE').map((y) => ({ value: y.id, label: `${y.name} (${y.code})` })),
[yardsQuery.data],
);
const zoneOptions = useMemo(
() => (zonesQuery.data ?? []).filter((z) => z.status === 'ACTIVE').map((z) => ({ value: z.id, label: `${z.name} (${z.code})` })),
[zonesQuery.data],
);
const handleSubmit = async () => {
if (!item) return;
if (!warehouseId || !yardId || !zoneId) {
toast({ variant: 'destructive', title: 'Select destination warehouse, yard and zone' });
return;
}
try {
await moveMutation.mutateAsync({
id: item.id,
payload: { warehouseId, yardId, zoneId, remarks: remarks.trim() || undefined },
});
toast({ title: 'Inventory moved' });
onClose();
} catch (error) {
toast({ variant: 'destructive', title: 'Move failed', description: extractErrorMessage(error) });
}
};
return (
<Modal opened={opened} onClose={onClose} title="Move inventory" centered size="lg">
<Stack gap="md">
<Select
label="Destination warehouse"
placeholder="Select warehouse"
required
searchable
data={warehouseOptions}
value={warehouseId || null}
onChange={(v) => {
setWarehouseId(v ?? '');
setYardId('');
setZoneId('');
}}
/>
<Select
label="Destination yard"
placeholder={!warehouseId ? 'Select a warehouse first' : 'Select yard'}
required
searchable
disabled={!warehouseId}
data={yardOptions}
value={yardId || null}
onChange={(v) => {
setYardId(v ?? '');
setZoneId('');
}}
/>
<Select
label="Destination zone"
placeholder={!yardId ? 'Select a yard first' : 'Select zone'}
required
searchable
disabled={!yardId}
data={zoneOptions}
value={zoneId || null}
onChange={(v) => setZoneId(v ?? '')}
/>
<Textarea
label="Remarks"
placeholder="Reason for the move"
autosize
minRows={2}
value={remarks}
onChange={(e) => setRemarks(e.currentTarget.value)}
/>
<Group justify="flex-end" mt="sm">
<Button variant="default" onClick={onClose} disabled={moveMutation.isPending}>
Cancel
</Button>
<Button onClick={handleSubmit} loading={moveMutation.isPending}>
Move inventory
</Button>
</Group>
</Stack>
</Modal>
);
}