mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
Backend (edr-freight-api): - Warehouse, WarehouseYard, WarehouseZone, WarehouseInventory entities - CRUD for warehouses/yards/zones with scoped code uniqueness - Inventory receive with location-hierarchy + capacity validation and transactional capacity-counter updates - inspect / ready-for-loading status transitions - inventory inquiry (booking/customer/container/cargo-type joins) - migration creating freight.warehouse* tables Frontend (freight backoffice): - types, service, react-query hooks, URL constants - reusable components: badges, filters, table/card views, inventory and inquiry tables, create/receive modals - pages: Warehouse list, detail (overview/yards/zones/inventory tabs), inventory, inventory inquiry - Warehouse Information card + Receive At Warehouse on booking detail - routes + sidebar nav; app-wide ErrorBoundary Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
import { useMemo, useState } from 'react';
|
|
import { Button, Card, Center, Container, Group, Loader, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
|
import { Search } from 'lucide-react';
|
|
|
|
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
|
import { WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
|
import {
|
|
useInventoryInquiry,
|
|
useWarehouseYards,
|
|
useWarehouseZones,
|
|
useWarehouses,
|
|
} from '@/hooks/useWarehouses';
|
|
import type { InventoryInquiryFilter, InventoryStatus } from '@/types/warehouse';
|
|
|
|
export default function InventoryInquiryPage() {
|
|
const [draft, setDraft] = useState<InventoryInquiryFilter>({});
|
|
const [applied, setApplied] = useState<InventoryInquiryFilter>({});
|
|
|
|
const warehousesQuery = useWarehouses();
|
|
const yardsQuery = useWarehouseYards(draft.warehouseId);
|
|
const zonesQuery = useWarehouseZones(draft.yardId);
|
|
|
|
const { data, isFetching } = useInventoryInquiry(applied);
|
|
const results = data ?? [];
|
|
|
|
const warehouseOptions = useMemo(
|
|
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
|
[warehousesQuery.data],
|
|
);
|
|
const yardOptions = useMemo(
|
|
() => (yardsQuery.data ?? []).map((y) => ({ value: y.id, label: `${y.name} (${y.code})` })),
|
|
[yardsQuery.data],
|
|
);
|
|
const zoneOptions = useMemo(
|
|
() => (zonesQuery.data ?? []).map((z) => ({ value: z.id, label: `${z.name} (${z.code})` })),
|
|
[zonesQuery.data],
|
|
);
|
|
|
|
const runSearch = () => setApplied(draft);
|
|
const reset = () => {
|
|
setDraft({});
|
|
setApplied({});
|
|
};
|
|
|
|
return (
|
|
<Container size="xxl" py="lg">
|
|
<Breadcrumbs items={[{ label: 'Inventory inquiry' }]} />
|
|
|
|
<Stack gap="lg" mt="sm">
|
|
<div>
|
|
<Title order={2}>Inventory Inquiry</Title>
|
|
<Text c="dimmed" size="sm">
|
|
Locate any cargo, container or goods inside the warehouse network.
|
|
</Text>
|
|
</div>
|
|
|
|
<Card withBorder radius="md" padding="lg">
|
|
<Stack gap="md">
|
|
<Group gap="sm" wrap="wrap">
|
|
<TextInput
|
|
label="Booking number"
|
|
placeholder="e.g. BKG-00123"
|
|
value={draft.bookingNumber ?? ''}
|
|
onChange={(e) => setDraft((f) => ({ ...f, bookingNumber: e.currentTarget.value || undefined }))}
|
|
w={200}
|
|
/>
|
|
<TextInput
|
|
label="Container number"
|
|
placeholder="e.g. MSKU1234567"
|
|
value={draft.containerNumber ?? ''}
|
|
onChange={(e) => setDraft((f) => ({ ...f, containerNumber: e.currentTarget.value || undefined }))}
|
|
w={200}
|
|
/>
|
|
<TextInput
|
|
label="Goods name"
|
|
placeholder="e.g. Coffee"
|
|
value={draft.goodsName ?? ''}
|
|
onChange={(e) => setDraft((f) => ({ ...f, goodsName: e.currentTarget.value || undefined }))}
|
|
w={180}
|
|
/>
|
|
<Select
|
|
label="Warehouse"
|
|
placeholder="Any"
|
|
clearable
|
|
searchable
|
|
data={warehouseOptions}
|
|
value={draft.warehouseId ?? null}
|
|
onChange={(value) =>
|
|
setDraft((f) => ({ ...f, warehouseId: value ?? undefined, yardId: undefined, zoneId: undefined }))
|
|
}
|
|
w={200}
|
|
/>
|
|
<Select
|
|
label="Yard"
|
|
placeholder="Any"
|
|
clearable
|
|
searchable
|
|
disabled={!draft.warehouseId}
|
|
data={yardOptions}
|
|
value={draft.yardId ?? null}
|
|
onChange={(value) => setDraft((f) => ({ ...f, yardId: value ?? undefined, zoneId: undefined }))}
|
|
w={180}
|
|
/>
|
|
<Select
|
|
label="Zone"
|
|
placeholder="Any"
|
|
clearable
|
|
searchable
|
|
disabled={!draft.yardId}
|
|
data={zoneOptions}
|
|
value={draft.zoneId ?? null}
|
|
onChange={(value) => setDraft((f) => ({ ...f, zoneId: value ?? undefined }))}
|
|
w={180}
|
|
/>
|
|
<Select
|
|
label="Status"
|
|
placeholder="Any"
|
|
clearable
|
|
data={inventoryStatusOptions}
|
|
value={draft.status ?? null}
|
|
onChange={(value) => setDraft((f) => ({ ...f, status: (value as InventoryStatus) ?? undefined }))}
|
|
w={180}
|
|
/>
|
|
</Group>
|
|
|
|
<Group>
|
|
<Button leftSection={<Search size={16} />} onClick={runSearch}>
|
|
Search
|
|
</Button>
|
|
<Button variant="default" onClick={reset}>
|
|
Reset
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Card>
|
|
|
|
<Card withBorder radius="md" padding="lg">
|
|
{isFetching ? (
|
|
<Center py="xl">
|
|
<Loader />
|
|
</Center>
|
|
) : (
|
|
<WarehouseInquiryTable results={results} />
|
|
)}
|
|
</Card>
|
|
</Stack>
|
|
</Container>
|
|
);
|
|
}
|