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);
|
const cells = items.slice(0, 5);
|
||||||
|
|
||||||
return (
|
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">
|
<div className="flex flex-col sm:flex-row">
|
||||||
{cells.map((item, index) => {
|
{cells.map((item, index) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
|
|||||||
@@ -4,20 +4,17 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Container,
|
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Stack,
|
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { ClipboardList, Eye, PackageOpen, Truck } from 'lucide-react';
|
import { ClipboardList, Eye, PackageOpen, Truck } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import {
|
import {
|
||||||
InspectionReportModal,
|
InspectionReportModal,
|
||||||
VisualEmptyState,
|
VisualEmptyState,
|
||||||
WarehouseHero,
|
|
||||||
formatDate,
|
formatDate,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useArrivalQueue, useAutoUnloadArrived, useUnloadBooking } from '@/hooks/useWarehouses';
|
import { useArrivalQueue, useAutoUnloadArrived, useUnloadBooking } from '@/hooks/useWarehouses';
|
||||||
@@ -64,31 +61,27 @@ export default function ArrivalQueuePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Arrival queue' }]} />
|
<PageHeader
|
||||||
|
title="Arrival / Unloading Queue"
|
||||||
|
subtitle="Arrived bookings ready to unload, store and inspect."
|
||||||
|
action={
|
||||||
|
<Button
|
||||||
|
leftSection={<PackageOpen size={16} />}
|
||||||
|
loading={autoUnload.isPending}
|
||||||
|
onClick={handleAutoUnload}
|
||||||
|
>
|
||||||
|
Auto Unload Arrived Bookings
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack gap="lg" mt="sm">
|
<Card>
|
||||||
<WarehouseHero
|
<Text fw={600} mb="md">
|
||||||
variant="container"
|
{items.length} arrived booking(s)
|
||||||
secondaryVariant="warehouse"
|
</Text>
|
||||||
title="Arrival / Unloading Queue"
|
|
||||||
subtitle="Arrived bookings ready to unload, store and inspect."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Card withBorder radius="md" padding="lg">
|
{isLoading ? (
|
||||||
<Group justify="space-between" mb="md">
|
|
||||||
<Text fw={600}>{items.length} arrived booking(s)</Text>
|
|
||||||
<Button
|
|
||||||
color="orange"
|
|
||||||
leftSection={<PackageOpen size={16} />}
|
|
||||||
loading={autoUnload.isPending}
|
|
||||||
onClick={handleAutoUnload}
|
|
||||||
>
|
|
||||||
Auto Unload Arrived Bookings
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
{isLoading ? (
|
|
||||||
<Group justify="center" py="xl">
|
<Group justify="center" py="xl">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Group>
|
</Group>
|
||||||
@@ -187,14 +180,13 @@ export default function ArrivalQueuePage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</Table.ScrollContainer>
|
</Table.ScrollContainer>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<InspectionReportModal
|
<InspectionReportModal
|
||||||
opened={Boolean(inspectInventoryId)}
|
opened={Boolean(inspectInventoryId)}
|
||||||
onClose={() => setInspectInventoryId(null)}
|
onClose={() => setInspectInventoryId(null)}
|
||||||
inventoryId={inspectInventoryId}
|
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 { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { InventoryWorkbench, VisualEmptyState, WarehouseHero } from '@/components/warehouses';
|
import { InventoryWorkbench, VisualEmptyState } from '@/components/warehouses';
|
||||||
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||||
|
|
||||||
/** Items that are LOADED and awaiting dispatch (train departure). */
|
/** Items that are LOADED and awaiting dispatch (train departure). */
|
||||||
@@ -10,29 +10,23 @@ export default function DispatchQueuePage() {
|
|||||||
const items = data ?? [];
|
const items = data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Dispatch queue' }]} />
|
<PageHeader
|
||||||
|
title="Dispatch Queue"
|
||||||
|
subtitle="Loaded inventory awaiting train departure. Mark items dispatched once they leave."
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack gap="lg" mt="sm">
|
<Card>
|
||||||
<WarehouseHero
|
{!isLoading && items.length === 0 ? (
|
||||||
variant="train"
|
<VisualEmptyState
|
||||||
secondaryVariant="route"
|
variant="train"
|
||||||
title="Dispatch Queue"
|
title="Nothing to dispatch"
|
||||||
subtitle="Loaded inventory awaiting train departure. Mark items dispatched once they leave."
|
description="Loaded items appear here, ready to mark as dispatched."
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
<Card withBorder radius="md" padding="lg">
|
<InventoryWorkbench items={items} isLoading={isLoading} />
|
||||||
{!isLoading && items.length === 0 ? (
|
)}
|
||||||
<VisualEmptyState
|
</Card>
|
||||||
variant="train"
|
</PageContainer>
|
||||||
title="Nothing to dispatch"
|
|
||||||
description="Loaded items appear here, ready to mark as dispatched."
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InventoryWorkbench items={items} isLoading={isLoading} />
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useMemo, useState } from 'react';
|
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 { Search } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { VisualEmptyState, WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
import { VisualEmptyState, WarehouseInquiryTable, inventoryStatusOptions } from '@/components/warehouses';
|
||||||
import {
|
import {
|
||||||
useInventoryInquiry,
|
useInventoryInquiry,
|
||||||
@@ -43,113 +43,107 @@ export default function InventoryInquiryPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Inventory inquiry' }]} />
|
<PageHeader
|
||||||
|
title="Inventory Inquiry"
|
||||||
|
subtitle="Locate any cargo, container or goods inside the warehouse network."
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack gap="lg" mt="sm">
|
<Card>
|
||||||
<div>
|
<Stack gap="md">
|
||||||
<Title order={2}>Inventory Inquiry</Title>
|
<Group gap="sm" wrap="wrap">
|
||||||
<Text c="dimmed" size="sm">
|
<TextInput
|
||||||
Locate any cargo, container or goods inside the warehouse network.
|
label="Booking number"
|
||||||
</Text>
|
placeholder="e.g. BKG-00123"
|
||||||
</div>
|
value={draft.bookingNumber ?? ''}
|
||||||
|
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, bookingNumber: v || undefined })); }}
|
||||||
<Card withBorder radius="md" padding="lg">
|
w={200}
|
||||||
<Stack gap="md">
|
|
||||||
<Group gap="sm" wrap="wrap">
|
|
||||||
<TextInput
|
|
||||||
label="Booking number"
|
|
||||||
placeholder="e.g. BKG-00123"
|
|
||||||
value={draft.bookingNumber ?? ''}
|
|
||||||
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, bookingNumber: v || undefined })); }}
|
|
||||||
w={200}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Container number"
|
|
||||||
placeholder="e.g. MSKU1234567"
|
|
||||||
value={draft.containerNumber ?? ''}
|
|
||||||
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, containerNumber: v || undefined })); }}
|
|
||||||
w={200}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Goods name"
|
|
||||||
placeholder="e.g. Coffee"
|
|
||||||
value={draft.goodsName ?? ''}
|
|
||||||
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, goodsName: v || 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>
|
|
||||||
) : results.length === 0 ? (
|
|
||||||
<VisualEmptyState
|
|
||||||
variant="container"
|
|
||||||
title="No items found"
|
|
||||||
description="Adjust your filters and search to locate cargo, containers or goods across the warehouse network."
|
|
||||||
/>
|
/>
|
||||||
) : (
|
<TextInput
|
||||||
<WarehouseInquiryTable results={results} />
|
label="Container number"
|
||||||
)}
|
placeholder="e.g. MSKU1234567"
|
||||||
</Card>
|
value={draft.containerNumber ?? ''}
|
||||||
</Stack>
|
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, containerNumber: v || undefined })); }}
|
||||||
</Container>
|
w={200}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Goods name"
|
||||||
|
placeholder="e.g. Coffee"
|
||||||
|
value={draft.goodsName ?? ''}
|
||||||
|
onChange={(e) => { const v = e.currentTarget.value; setDraft((f) => ({ ...f, goodsName: v || 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>
|
||||||
|
{isFetching ? (
|
||||||
|
<Center py="xl">
|
||||||
|
<Loader />
|
||||||
|
</Center>
|
||||||
|
) : results.length === 0 ? (
|
||||||
|
<VisualEmptyState
|
||||||
|
variant="container"
|
||||||
|
title="No items found"
|
||||||
|
description="Adjust your filters and search to locate cargo, containers or goods across the warehouse network."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<WarehouseInquiryTable results={results} />
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</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 { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { FreightVisual, VisualEmptyState, WarehouseHero, formatDate, formatNumber } from '@/components/warehouses';
|
import { FreightVisual, VisualEmptyState, formatDate, formatNumber } from '@/components/warehouses';
|
||||||
import { useWarehouseLoadings } from '@/hooks/useWarehouses';
|
import { useWarehouseLoadings } from '@/hooks/useWarehouses';
|
||||||
|
|
||||||
/** Record of every inventory item loaded onto a wagon. */
|
/** Record of every inventory item loaded onto a wagon. */
|
||||||
@@ -10,77 +10,71 @@ export default function LoadedInventoryPage() {
|
|||||||
const loadings = data ?? [];
|
const loadings = data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Loaded inventory' }]} />
|
<PageHeader
|
||||||
|
title="Loaded Inventory"
|
||||||
|
subtitle="Items loaded onto wagons, with their loading records."
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack gap="lg" mt="sm">
|
<Card>
|
||||||
<WarehouseHero
|
{isLoading ? (
|
||||||
variant="container"
|
<Group justify="center" py="xl">
|
||||||
secondaryVariant="wagon"
|
<Loader />
|
||||||
title="Loaded Inventory"
|
</Group>
|
||||||
subtitle="Items loaded onto wagons, with their loading records."
|
) : loadings.length === 0 ? (
|
||||||
/>
|
<VisualEmptyState
|
||||||
|
variant="container"
|
||||||
<Card withBorder radius="md" padding="lg">
|
title="No loaded inventory yet"
|
||||||
{isLoading ? (
|
description="Once items are loaded onto a wagon, their records show here."
|
||||||
<Group justify="center" py="xl">
|
/>
|
||||||
<Loader />
|
) : (
|
||||||
</Group>
|
<Table.ScrollContainer minWidth={760}>
|
||||||
) : loadings.length === 0 ? (
|
<Table verticalSpacing="sm" highlightOnHover>
|
||||||
<VisualEmptyState
|
<Table.Thead>
|
||||||
variant="container"
|
<Table.Tr>
|
||||||
title="No loaded inventory yet"
|
<Table.Th>Wagon</Table.Th>
|
||||||
description="Once items are loaded onto a wagon, their records show here."
|
<Table.Th>Warehouse</Table.Th>
|
||||||
/>
|
<Table.Th>Zone</Table.Th>
|
||||||
) : (
|
<Table.Th>Loaded Weight (kg)</Table.Th>
|
||||||
<Table.ScrollContainer minWidth={760}>
|
<Table.Th>Loaded At</Table.Th>
|
||||||
<Table verticalSpacing="sm" highlightOnHover>
|
<Table.Th>Status</Table.Th>
|
||||||
<Table.Thead>
|
</Table.Tr>
|
||||||
<Table.Tr>
|
</Table.Thead>
|
||||||
<Table.Th>Wagon</Table.Th>
|
<Table.Tbody>
|
||||||
<Table.Th>Warehouse</Table.Th>
|
{loadings.map((l) => (
|
||||||
<Table.Th>Zone</Table.Th>
|
<Table.Tr key={l.id}>
|
||||||
<Table.Th>Loaded Weight (kg)</Table.Th>
|
<Table.Td>
|
||||||
<Table.Th>Loaded At</Table.Th>
|
<Group gap={6} wrap="nowrap">
|
||||||
<Table.Th>Status</Table.Th>
|
<FreightVisual variant="wagon" size={22} />
|
||||||
|
<Text fw={600} size="sm">
|
||||||
|
{l.wagonNumber ?? l.wagonId.slice(0, 8)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
{l.inventory?.warehouse
|
||||||
|
? `${l.inventory.warehouse.name} (${l.inventory.warehouse.code})`
|
||||||
|
: '—'}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{l.inventory?.zone ? l.inventory.zone.name : '—'}</Table.Td>
|
||||||
|
<Table.Td>{formatNumber(l.loadedWeight)}</Table.Td>
|
||||||
|
<Table.Td>{formatDate(l.loadedAt)}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={l.inventory?.status === 'DISPATCHED' ? 'green' : 'teal'}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{l.inventory?.status ?? 'LOADED'}
|
||||||
|
</Badge>
|
||||||
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
))}
|
||||||
<Table.Tbody>
|
</Table.Tbody>
|
||||||
{loadings.map((l) => (
|
</Table>
|
||||||
<Table.Tr key={l.id}>
|
</Table.ScrollContainer>
|
||||||
<Table.Td>
|
)}
|
||||||
<Group gap={6} wrap="nowrap">
|
</Card>
|
||||||
<FreightVisual variant="wagon" size={22} />
|
</PageContainer>
|
||||||
<Text fw={600} size="sm">
|
|
||||||
{l.wagonNumber ?? l.wagonId.slice(0, 8)}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>
|
|
||||||
{l.inventory?.warehouse
|
|
||||||
? `${l.inventory.warehouse.name} (${l.inventory.warehouse.code})`
|
|
||||||
: '—'}
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>{l.inventory?.zone ? l.inventory.zone.name : '—'}</Table.Td>
|
|
||||||
<Table.Td>{formatNumber(l.loadedWeight)}</Table.Td>
|
|
||||||
<Table.Td>{formatDate(l.loadedAt)}</Table.Td>
|
|
||||||
<Table.Td>
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
color={l.inventory?.status === 'DISPATCHED' ? 'green' : 'teal'}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{l.inventory?.status ?? 'LOADED'}
|
|
||||||
</Badge>
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
))}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
</Table.ScrollContainer>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { CreditCard, Eye, Truck } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import {
|
import {
|
||||||
InventoryWorkbench,
|
InventoryWorkbench,
|
||||||
VisualEmptyState,
|
VisualEmptyState,
|
||||||
WarehouseHero,
|
|
||||||
formatNumber,
|
formatNumber,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useAutoLoadReady, useWarehouseInventory } from '@/hooks/useWarehouses';
|
import { useAutoLoadReady, useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||||
@@ -53,30 +52,23 @@ export default function LoadingQueuePage() {
|
|||||||
const unpaidItems = useMemo(() => readyItems.filter((i) => !isPaid(i)), [readyItems]);
|
const unpaidItems = useMemo(() => readyItems.filter((i) => !isPaid(i)), [readyItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Loading queue' }]} />
|
<PageHeader
|
||||||
|
title="Loading Queue"
|
||||||
<Stack gap="lg" mt="sm">
|
subtitle="Manage bookings and inventory through the loading workflow."
|
||||||
<WarehouseHero
|
action={
|
||||||
variant="wagon"
|
|
||||||
secondaryVariant="cargo"
|
|
||||||
title="Loading Queue"
|
|
||||||
subtitle="Manage bookings and inventory through the loading workflow."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group justify="flex-end">
|
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
|
||||||
leftSection={<Truck size={16} />}
|
leftSection={<Truck size={16} />}
|
||||||
loading={autoLoad.isPending}
|
loading={autoLoad.isPending}
|
||||||
onClick={handleAutoLoad}
|
onClick={handleAutoLoad}
|
||||||
>
|
>
|
||||||
Auto Load Ready Items
|
Auto Load Ready Items
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card withBorder radius="md" padding="lg">
|
<Card>
|
||||||
<Tabs defaultValue="ready">
|
<Tabs defaultValue="ready">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
value="ready"
|
value="ready"
|
||||||
@@ -171,10 +163,9 @@ export default function LoadingQueuePage() {
|
|||||||
<InventoryWorkbench items={loadedItems} isLoading={loadedLoading} />
|
<InventoryWorkbench items={loadedItems} isLoading={loadedLoading} />
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
</PageContainer>
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
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 { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { PackagePlus, Search } from 'lucide-react';
|
import { PackagePlus, Search } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import {
|
import {
|
||||||
InventoryWorkbench,
|
InventoryWorkbench,
|
||||||
ReceiveInventoryModal,
|
ReceiveInventoryModal,
|
||||||
@@ -52,79 +52,73 @@ export default function WarehouseInventoryPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Warehouse inventory' }]} />
|
<PageHeader
|
||||||
|
title="Warehouse Inventory"
|
||||||
<Stack gap="lg" mt="sm">
|
subtitle="Track received items through the storage, reservation, loading and dispatch lifecycle."
|
||||||
<Group justify="space-between" align="flex-end">
|
action={
|
||||||
<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>
|
|
||||||
<Button leftSection={<PackagePlus size={16} />} onClick={() => setModalOpen(true)}>
|
<Button leftSection={<PackagePlus size={16} />} onClick={() => setModalOpen(true)}>
|
||||||
Receive Inventory
|
Receive Inventory
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card withBorder radius="md" padding="lg">
|
<Card>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Group gap="sm" wrap="wrap">
|
<Group gap="sm" wrap="wrap">
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Search notes"
|
placeholder="Search notes"
|
||||||
leftSection={<Search size={16} />}
|
leftSection={<Search size={16} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
w={220}
|
w={220}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
placeholder="All warehouses"
|
placeholder="All warehouses"
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
data={warehouseOptions}
|
data={warehouseOptions}
|
||||||
value={filter.warehouseId ?? null}
|
value={filter.warehouseId ?? null}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setFilter((f) => ({ ...f, warehouseId: value ?? undefined, yardId: undefined, zoneId: undefined }))
|
setFilter((f) => ({ ...f, warehouseId: value ?? undefined, yardId: undefined, zoneId: undefined }))
|
||||||
}
|
}
|
||||||
w={220}
|
w={220}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
placeholder="All yards"
|
placeholder="All yards"
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
disabled={!filter.warehouseId}
|
disabled={!filter.warehouseId}
|
||||||
data={yardOptions}
|
data={yardOptions}
|
||||||
value={filter.yardId ?? null}
|
value={filter.yardId ?? null}
|
||||||
onChange={(value) => setFilter((f) => ({ ...f, yardId: value ?? undefined, zoneId: undefined }))}
|
onChange={(value) => setFilter((f) => ({ ...f, yardId: value ?? undefined, zoneId: undefined }))}
|
||||||
w={200}
|
w={200}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
placeholder="All zones"
|
placeholder="All zones"
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
disabled={!filter.yardId}
|
disabled={!filter.yardId}
|
||||||
data={zoneOptions}
|
data={zoneOptions}
|
||||||
value={filter.zoneId ?? null}
|
value={filter.zoneId ?? null}
|
||||||
onChange={(value) => setFilter((f) => ({ ...f, zoneId: value ?? undefined }))}
|
onChange={(value) => setFilter((f) => ({ ...f, zoneId: value ?? undefined }))}
|
||||||
w={200}
|
w={200}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
placeholder="All statuses"
|
placeholder="All statuses"
|
||||||
clearable
|
clearable
|
||||||
data={inventoryStatusOptions}
|
data={inventoryStatusOptions}
|
||||||
value={filter.status ?? null}
|
value={filter.status ?? null}
|
||||||
onChange={(value) => setFilter((f) => ({ ...f, status: (value as InventoryStatus) ?? undefined }))}
|
onChange={(value) => setFilter((f) => ({ ...f, status: (value as InventoryStatus) ?? undefined }))}
|
||||||
w={200}
|
w={200}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<InventoryWorkbench items={inventoryQuery.data ?? []} isLoading={inventoryQuery.isLoading} />
|
<InventoryWorkbench items={inventoryQuery.data ?? []} isLoading={inventoryQuery.isLoading} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<ReceiveInventoryModal opened={modalOpen} onClose={() => setModalOpen(false)} />
|
<ReceiveInventoryModal opened={modalOpen} onClose={() => setModalOpen(false)} />
|
||||||
</Container>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Container,
|
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
@@ -18,8 +17,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Ban, CreditCard, Eye, Search } from 'lucide-react';
|
import { Ban, CreditCard, Eye, Search } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { WarehouseHero } from '@/components/warehouses';
|
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
import {
|
||||||
useCancelInvoice,
|
useCancelInvoice,
|
||||||
@@ -59,18 +57,14 @@ export default function WarehouseInvoicesPage() {
|
|||||||
}, [invoices, search]);
|
}, [invoices, search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Warehouse fee invoices' }]} />
|
<PageHeader
|
||||||
<Stack gap="lg" mt="sm">
|
title="Warehouse Fee Invoices"
|
||||||
<WarehouseHero
|
subtitle="Demurrage & storage invoices generated from warehouse fee rules."
|
||||||
variant="container"
|
/>
|
||||||
secondaryVariant="warehouse"
|
|
||||||
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">
|
<Group justify="space-between" mb="md" wrap="wrap">
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Search invoice no / booking / customer"
|
placeholder="Search invoice no / booking / customer"
|
||||||
leftSection={<Search size={16} />}
|
leftSection={<Search size={16} />}
|
||||||
@@ -123,11 +117,10 @@ export default function WarehouseInvoicesPage() {
|
|||||||
</Table>
|
</Table>
|
||||||
</Table.ScrollContainer>
|
</Table.ScrollContainer>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<InvoiceDetailModal id={detailId} onClose={() => setDetailId(null)} />
|
<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))}
|
onChange={(v) => setPayAmount(v === '' ? '' : Number(v))}
|
||||||
style={{ flex: 1 }}
|
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
|
Pay
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import {
|
import {
|
||||||
CreateWarehouseModal,
|
CreateWarehouseModal,
|
||||||
WarehouseCardView,
|
WarehouseCardView,
|
||||||
@@ -42,44 +42,38 @@ export default function WarehouseListPage() {
|
|||||||
const openDetail = (warehouse: Warehouse) => navigate(`/dashboard/warehouses/${warehouse.id}`);
|
const openDetail = (warehouse: Warehouse) => navigate(`/dashboard/warehouses/${warehouse.id}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Warehouses' }]} />
|
<PageHeader
|
||||||
|
title="Warehouses"
|
||||||
<Stack gap="lg" mt="sm">
|
subtitle="Manage warehouses, yards and zones."
|
||||||
<Group justify="space-between" align="flex-end">
|
action={
|
||||||
<div>
|
|
||||||
<Title order={2}>Warehouses</Title>
|
|
||||||
<Text c="dimmed" size="sm">
|
|
||||||
Manage warehouses, yards and zones.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<Button leftSection={<Plus size={16} />} onClick={openCreate}>
|
<Button leftSection={<Plus size={16} />} onClick={openCreate}>
|
||||||
Create Warehouse
|
Create Warehouse
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card withBorder radius="md" padding="lg">
|
<Card>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<WarehouseFilters filter={filter} onChange={setFilter} view={view} onViewChange={setView} />
|
<WarehouseFilters filter={filter} onChange={setFilter} view={view} onViewChange={setView} />
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Center>
|
</Center>
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<Text c="red" ta="center" py="xl">
|
<Text c="red" ta="center" py="xl">
|
||||||
Failed to load warehouses.
|
Failed to load warehouses.
|
||||||
</Text>
|
</Text>
|
||||||
) : view === 'table' ? (
|
) : view === 'table' ? (
|
||||||
<WarehouseTable warehouses={warehouses} onView={openDetail} onEdit={openEdit} />
|
<WarehouseTable warehouses={warehouses} onView={openDetail} onEdit={openEdit} />
|
||||||
) : (
|
) : (
|
||||||
<WarehouseCardView warehouses={warehouses} onView={openDetail} onEdit={openEdit} />
|
<WarehouseCardView warehouses={warehouses} onView={openDetail} onEdit={openEdit} />
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<CreateWarehouseModal opened={modalOpen} onClose={() => setModalOpen(false)} warehouse={editing} />
|
<CreateWarehouseModal opened={modalOpen} onClose={() => setModalOpen(false)} warehouse={editing} />
|
||||||
</Container>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Container,
|
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -18,8 +17,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Plus, Trash2 } from 'lucide-react';
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
import { WarehouseHero } from '@/components/warehouses';
|
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import {
|
import {
|
||||||
useAllocationRules,
|
useAllocationRules,
|
||||||
@@ -45,31 +43,26 @@ const clean = (s: string) => s.trim() || undefined;
|
|||||||
|
|
||||||
export default function WarehouseRulesPage() {
|
export default function WarehouseRulesPage() {
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<PageContainer>
|
||||||
<Breadcrumbs items={[{ label: 'Warehouse rules' }]} />
|
<PageHeader
|
||||||
<Stack gap="lg" mt="sm">
|
title="Allocation & Fee Rules"
|
||||||
<WarehouseHero
|
subtitle="Configure deterministic yard allocation and storage / demurrage free time and rates."
|
||||||
variant="warehouse"
|
/>
|
||||||
secondaryVariant="container"
|
<Card>
|
||||||
title="Allocation & Fee Rules"
|
<Tabs defaultValue="allocation">
|
||||||
subtitle="Configure deterministic yard allocation and storage / demurrage free time and rates."
|
<Tabs.List>
|
||||||
/>
|
<Tabs.Tab value="allocation">Allocation Rules</Tabs.Tab>
|
||||||
<Card withBorder radius="md" padding="lg">
|
<Tabs.Tab value="fees">Storage / Demurrage Fees</Tabs.Tab>
|
||||||
<Tabs defaultValue="allocation">
|
</Tabs.List>
|
||||||
<Tabs.List>
|
<Tabs.Panel value="allocation" pt="md">
|
||||||
<Tabs.Tab value="allocation">Allocation Rules</Tabs.Tab>
|
<AllocationRules />
|
||||||
<Tabs.Tab value="fees">Storage / Demurrage Fees</Tabs.Tab>
|
</Tabs.Panel>
|
||||||
</Tabs.List>
|
<Tabs.Panel value="fees" pt="md">
|
||||||
<Tabs.Panel value="allocation" pt="md">
|
<FeeRules />
|
||||||
<AllocationRules />
|
</Tabs.Panel>
|
||||||
</Tabs.Panel>
|
</Tabs>
|
||||||
<Tabs.Panel value="fees" pt="md">
|
</Card>
|
||||||
<FeeRules />
|
</PageContainer>
|
||||||
</Tabs.Panel>
|
|
||||||
</Tabs>
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +109,7 @@ function AllocationRules() {
|
|||||||
<>
|
<>
|
||||||
<Group justify="space-between" mb="sm">
|
<Group justify="space-between" mb="sm">
|
||||||
<Text c="dimmed" size="sm">{rules.length} rule(s) — matched by ascending priority</Text>
|
<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>
|
</Group>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Group justify="center" py="xl"><Loader /></Group>
|
<Group justify="center" py="xl"><Loader /></Group>
|
||||||
@@ -172,7 +165,7 @@ function AllocationRules() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group justify="flex-end" mt="sm">
|
<Group justify="flex-end" mt="sm">
|
||||||
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
|
<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>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -222,7 +215,7 @@ function FeeRules() {
|
|||||||
<>
|
<>
|
||||||
<Group justify="space-between" mb="sm">
|
<Group justify="space-between" mb="sm">
|
||||||
<Text c="dimmed" size="sm">{rules.length} rule(s) — most specific match applies</Text>
|
<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>
|
</Group>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Group justify="center" py="xl"><Loader /></Group>
|
<Group justify="center" py="xl"><Loader /></Group>
|
||||||
@@ -276,7 +269,7 @@ function FeeRules() {
|
|||||||
</Group>
|
</Group>
|
||||||
<Group justify="flex-end" mt="sm">
|
<Group justify="flex-end" mt="sm">
|
||||||
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
|
<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>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user