mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
style: warehouse revamp
This commit is contained in:
@@ -37,7 +37,7 @@ export function KpiStrip({ items, loading = false }: KpiStripProps) {
|
||||
const cells = items.slice(0, 5);
|
||||
|
||||
return (
|
||||
<Card withBorder shadow="sm" radius="lg" p={0} className="overflow-hidden">
|
||||
<Card withBorder shadow="sm" p={0} className="overflow-hidden">
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
{cells.map((item, index) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
@@ -4,20 +4,17 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { ClipboardList, Eye, PackageOpen, Truck } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
InspectionReportModal,
|
||||
VisualEmptyState,
|
||||
WarehouseHero,
|
||||
formatDate,
|
||||
} from '@/components/warehouses';
|
||||
import { useArrivalQueue, useAutoUnloadArrived, useUnloadBooking } from '@/hooks/useWarehouses';
|
||||
@@ -64,29 +61,25 @@ export default function ArrivalQueuePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Arrival queue' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="container"
|
||||
secondaryVariant="warehouse"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Arrival / Unloading Queue"
|
||||
subtitle="Arrived bookings ready to unload, store and inspect."
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Text fw={600}>{items.length} arrived booking(s)</Text>
|
||||
action={
|
||||
<Button
|
||||
color="orange"
|
||||
leftSection={<PackageOpen size={16} />}
|
||||
loading={autoUnload.isPending}
|
||||
onClick={handleAutoUnload}
|
||||
>
|
||||
Auto Unload Arrived Bookings
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Text fw={600} mb="md">
|
||||
{items.length} arrived booking(s)
|
||||
</Text>
|
||||
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="xl">
|
||||
@@ -188,13 +181,12 @@ export default function ArrivalQueuePage() {
|
||||
</Table.ScrollContainer>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<InspectionReportModal
|
||||
opened={Boolean(inspectInventoryId)}
|
||||
onClose={() => setInspectInventoryId(null)}
|
||||
inventoryId={inspectInventoryId}
|
||||
/>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Card, Container, Stack } from '@mantine/core';
|
||||
import { Card } from '@mantine/core';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { InventoryWorkbench, VisualEmptyState, WarehouseHero } from '@/components/warehouses';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { InventoryWorkbench, VisualEmptyState } from '@/components/warehouses';
|
||||
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||
|
||||
/** Items that are LOADED and awaiting dispatch (train departure). */
|
||||
@@ -10,18 +10,13 @@ export default function DispatchQueuePage() {
|
||||
const items = data ?? [];
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Dispatch queue' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="train"
|
||||
secondaryVariant="route"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Dispatch Queue"
|
||||
subtitle="Loaded inventory awaiting train departure. Mark items dispatched once they leave."
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
{!isLoading && items.length === 0 ? (
|
||||
<VisualEmptyState
|
||||
variant="train"
|
||||
@@ -32,7 +27,6 @@ export default function DispatchQueuePage() {
|
||||
<InventoryWorkbench items={items} isLoading={isLoading} />
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Button, Card, Center, Container, Group, Loader, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Button, Card, Center, Group, Loader, Select, Stack, TextInput } from '@mantine/core';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { VisualEmptyState, WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
||||
import {
|
||||
useInventoryInquiry,
|
||||
@@ -43,18 +43,13 @@ export default function InventoryInquiryPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Inventory inquiry' }]} />
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Inventory Inquiry"
|
||||
subtitle="Locate any cargo, container or goods inside the warehouse network."
|
||||
/>
|
||||
|
||||
<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">
|
||||
<Card>
|
||||
<Stack gap="md">
|
||||
<Group gap="sm" wrap="wrap">
|
||||
<TextInput
|
||||
@@ -134,7 +129,7 @@ export default function InventoryInquiryPage() {
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
{isFetching ? (
|
||||
<Center py="xl">
|
||||
<Loader />
|
||||
@@ -149,7 +144,6 @@ export default function InventoryInquiryPage() {
|
||||
<WarehouseInquiryTable results={results} />
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Badge, Card, Container, Group, Loader, Stack, Table, Text } from '@mantine/core';
|
||||
import { Badge, Card, Group, Loader, Table, Text } from '@mantine/core';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { FreightVisual, VisualEmptyState, WarehouseHero, formatDate, formatNumber } from '@/components/warehouses';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { FreightVisual, VisualEmptyState, formatDate, formatNumber } from '@/components/warehouses';
|
||||
import { useWarehouseLoadings } from '@/hooks/useWarehouses';
|
||||
|
||||
/** Record of every inventory item loaded onto a wagon. */
|
||||
@@ -10,18 +10,13 @@ export default function LoadedInventoryPage() {
|
||||
const loadings = data ?? [];
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Loaded inventory' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="container"
|
||||
secondaryVariant="wagon"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Loaded Inventory"
|
||||
subtitle="Items loaded onto wagons, with their loading records."
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="xl">
|
||||
<Loader />
|
||||
@@ -80,7 +75,6 @@ export default function LoadedInventoryPage() {
|
||||
</Table.ScrollContainer>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Badge, Button, Card, Container, Group, Stack, Table, Tabs, Text } from '@mantine/core';
|
||||
import { Badge, Button, Card, Group, Table, Tabs, Text } from '@mantine/core';
|
||||
import { CreditCard, Eye, Truck } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
InventoryWorkbench,
|
||||
VisualEmptyState,
|
||||
WarehouseHero,
|
||||
formatNumber,
|
||||
} from '@/components/warehouses';
|
||||
import { useAutoLoadReady, useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||
@@ -53,29 +52,22 @@ export default function LoadingQueuePage() {
|
||||
const unpaidItems = useMemo(() => readyItems.filter((i) => !isPaid(i)), [readyItems]);
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Loading queue' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="wagon"
|
||||
secondaryVariant="cargo"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Loading Queue"
|
||||
subtitle="Manage bookings and inventory through the loading workflow."
|
||||
/>
|
||||
|
||||
<Group justify="flex-end">
|
||||
action={
|
||||
<Button
|
||||
color="green"
|
||||
leftSection={<Truck size={16} />}
|
||||
loading={autoLoad.isPending}
|
||||
onClick={handleAutoLoad}
|
||||
>
|
||||
Auto Load Ready Items
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
<Tabs defaultValue="ready">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab
|
||||
@@ -173,8 +165,7 @@ export default function LoadingQueuePage() {
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Button, Card, Container, Group, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Button, Card, Group, Select, Stack, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { PackagePlus, Search } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
InventoryWorkbench,
|
||||
ReceiveInventoryModal,
|
||||
@@ -52,23 +52,18 @@ export default function WarehouseInventoryPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Warehouse inventory' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<Group justify="space-between" align="flex-end">
|
||||
<div>
|
||||
<Title order={2}>Warehouse Inventory</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Track received items through the storage, reservation, loading and dispatch lifecycle.
|
||||
</Text>
|
||||
</div>
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Warehouse Inventory"
|
||||
subtitle="Track received items through the storage, reservation, loading and dispatch lifecycle."
|
||||
action={
|
||||
<Button leftSection={<PackagePlus size={16} />} onClick={() => setModalOpen(true)}>
|
||||
Receive Inventory
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
<Stack gap="md">
|
||||
<Group gap="sm" wrap="wrap">
|
||||
<TextInput
|
||||
@@ -122,9 +117,8 @@ export default function WarehouseInventoryPage() {
|
||||
<InventoryWorkbench items={inventoryQuery.data ?? []} isLoading={inventoryQuery.isLoading} />
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<ReceiveInventoryModal opened={modalOpen} onClose={() => setModalOpen(false)} />
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
@@ -18,8 +17,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { Ban, CreditCard, Eye, Search } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { WarehouseHero } from '@/components/warehouses';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useCancelInvoice,
|
||||
@@ -59,17 +57,13 @@ export default function WarehouseInvoicesPage() {
|
||||
}, [invoices, search]);
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Warehouse fee invoices' }]} />
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="container"
|
||||
secondaryVariant="warehouse"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Warehouse Fee Invoices"
|
||||
subtitle="Demurrage & storage invoices generated from warehouse fee rules."
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
<Group justify="space-between" mb="md" wrap="wrap">
|
||||
<TextInput
|
||||
placeholder="Search invoice no / booking / customer"
|
||||
@@ -124,10 +118,9 @@ export default function WarehouseInvoicesPage() {
|
||||
</Table.ScrollContainer>
|
||||
)}
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<InvoiceDetailModal id={detailId} onClose={() => setDetailId(null)} />
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,7 +210,7 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () =>
|
||||
onChange={(v) => setPayAmount(v === '' ? '' : Number(v))}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button color="green" leftSection={<CreditCard size={16} />} loading={pay.isPending} onClick={handlePay}>
|
||||
<Button leftSection={<CreditCard size={16} />} loading={pay.isPending} onClick={handlePay}>
|
||||
Pay
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button, Card, Center, Container, Group, Loader, Stack, Text, Title } from '@mantine/core';
|
||||
import { Button, Card, Center, Loader, Stack, Text } from '@mantine/core';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import {
|
||||
CreateWarehouseModal,
|
||||
WarehouseCardView,
|
||||
@@ -42,23 +42,18 @@ export default function WarehouseListPage() {
|
||||
const openDetail = (warehouse: Warehouse) => navigate(`/dashboard/warehouses/${warehouse.id}`);
|
||||
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Warehouses' }]} />
|
||||
|
||||
<Stack gap="lg" mt="sm">
|
||||
<Group justify="space-between" align="flex-end">
|
||||
<div>
|
||||
<Title order={2}>Warehouses</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Manage warehouses, yards and zones.
|
||||
</Text>
|
||||
</div>
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Warehouses"
|
||||
subtitle="Manage warehouses, yards and zones."
|
||||
action={
|
||||
<Button leftSection={<Plus size={16} />} onClick={openCreate}>
|
||||
Create Warehouse
|
||||
</Button>
|
||||
</Group>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
<Stack gap="md">
|
||||
<WarehouseFilters filter={filter} onChange={setFilter} view={view} onViewChange={setView} />
|
||||
|
||||
@@ -77,9 +72,8 @@ export default function WarehouseListPage() {
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
|
||||
<CreateWarehouseModal opened={modalOpen} onClose={() => setModalOpen(false)} warehouse={editing} />
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
@@ -18,8 +17,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||
import { WarehouseHero } from '@/components/warehouses';
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
useAllocationRules,
|
||||
@@ -45,16 +43,12 @@ const clean = (s: string) => s.trim() || undefined;
|
||||
|
||||
export default function WarehouseRulesPage() {
|
||||
return (
|
||||
<Container size="xxl" py="lg">
|
||||
<Breadcrumbs items={[{ label: 'Warehouse rules' }]} />
|
||||
<Stack gap="lg" mt="sm">
|
||||
<WarehouseHero
|
||||
variant="warehouse"
|
||||
secondaryVariant="container"
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Allocation & Fee Rules"
|
||||
subtitle="Configure deterministic yard allocation and storage / demurrage free time and rates."
|
||||
/>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Card>
|
||||
<Tabs defaultValue="allocation">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="allocation">Allocation Rules</Tabs.Tab>
|
||||
@@ -68,8 +62,7 @@ export default function WarehouseRulesPage() {
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,7 +109,7 @@ function AllocationRules() {
|
||||
<>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text c="dimmed" size="sm">{rules.length} rule(s) — matched by ascending priority</Text>
|
||||
<Button color="orange" leftSection={<Plus size={16} />} onClick={() => setOpen(true)}>New allocation rule</Button>
|
||||
<Button leftSection={<Plus size={16} />} onClick={() => setOpen(true)}>New allocation rule</Button>
|
||||
</Group>
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="xl"><Loader /></Group>
|
||||
@@ -172,7 +165,7 @@ function AllocationRules() {
|
||||
</Group>
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
|
||||
<Button color="orange" loading={create.isPending} onClick={submit}>Create</Button>
|
||||
<Button loading={create.isPending} onClick={submit}>Create</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
@@ -222,7 +215,7 @@ function FeeRules() {
|
||||
<>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text c="dimmed" size="sm">{rules.length} rule(s) — most specific match applies</Text>
|
||||
<Button color="teal" leftSection={<Plus size={16} />} onClick={() => setOpen(true)}>New fee rule</Button>
|
||||
<Button leftSection={<Plus size={16} />} onClick={() => setOpen(true)}>New fee rule</Button>
|
||||
</Group>
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="xl"><Loader /></Group>
|
||||
@@ -276,7 +269,7 @@ function FeeRules() {
|
||||
</Group>
|
||||
<Group justify="flex-end" mt="sm">
|
||||
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
|
||||
<Button color="teal" loading={create.isPending} onClick={submit}>Create</Button>
|
||||
<Button loading={create.isPending} onClick={submit}>Create</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user