mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(warehouses): make dashboard date range + warehouse filter real
Backs the previously-dead header date-picker and Filters button with an actual scoped query. getDashboard() takes optional dateFrom/dateTo/ warehouseId: received (renamed from receivedToday) is the only activity counter and respects the date range, defaulting to today when omitted; the 10 status-backlog counters respect warehouseId only; totalWarehouses/emptyContainers/importTrains/exportTrains stay global since nothing ties them to a specific warehouse in the schema. Adds a warehouse Select + date-range picker to the dashboard header, with a caption spelling out what is and isn't scoped.
This commit is contained in:
@@ -5,26 +5,34 @@ import { Warehouse } from './entities/warehouse.entity';
|
|||||||
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
||||||
import { SchedulingReadFacade } from './scheduling-read.facade';
|
import { SchedulingReadFacade } from './scheduling-read.facade';
|
||||||
|
|
||||||
|
export interface WarehouseDashboardFilter {
|
||||||
|
/** Inclusive day, `YYYY-MM-DD`. Both omitted → defaults to "today" (the original behaviour). */
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
/** Scopes every warehouse_inventory-derived counter. Ignored by the always-global ones (see below). */
|
||||||
|
warehouseId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WarehouseDashboard {
|
export interface WarehouseDashboard {
|
||||||
|
// ── Always current — dateFrom/dateTo have no effect on these ──────────────
|
||||||
totalWarehouses: number;
|
totalWarehouses: number;
|
||||||
totalInventory: number;
|
totalInventory: number;
|
||||||
receivedToday: number;
|
|
||||||
// Inspection gate
|
|
||||||
awaitingInspection: number;
|
awaitingInspection: number;
|
||||||
inspected: number;
|
inspected: number;
|
||||||
// Export branch
|
|
||||||
stored: number;
|
stored: number;
|
||||||
reserved: number;
|
reserved: number;
|
||||||
readyForLoading: number;
|
readyForLoading: number;
|
||||||
loaded: number;
|
loaded: number;
|
||||||
dispatched: number;
|
dispatched: number;
|
||||||
// Import branch
|
|
||||||
readyForPickup: number;
|
readyForPickup: number;
|
||||||
delivered: number;
|
delivered: number;
|
||||||
// Fleet / train snapshot
|
/** Never warehouse-scoped — the container fleet isn't tied to a specific warehouse. */
|
||||||
emptyContainers: number;
|
emptyContainers: number;
|
||||||
|
/** Never warehouse-scoped — trains aren't tied to a specific warehouse. */
|
||||||
importTrains: number;
|
importTrains: number;
|
||||||
exportTrains: number;
|
exportTrains: number;
|
||||||
|
// ── The one activity counter — respects dateFrom/dateTo (default: today) ──
|
||||||
|
received: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -72,24 +80,40 @@ export class WarehouseDashboardService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async safeReceivedToday(startOfToday: Date): Promise<number> {
|
/** [inclusive start, exclusive end) for the "received" counter. Defaults to today. */
|
||||||
|
private resolveRange(filter: WarehouseDashboardFilter): { start: Date; end: Date } {
|
||||||
|
if (!filter.dateFrom && !filter.dateTo) {
|
||||||
|
const start = new Date();
|
||||||
|
start.setHours(0, 0, 0, 0);
|
||||||
|
return { start, end: new Date() };
|
||||||
|
}
|
||||||
|
const start = filter.dateFrom ? new Date(`${filter.dateFrom}T00:00:00`) : new Date(0);
|
||||||
|
// Exclusive end = start of the day AFTER dateTo, so the whole end day is included.
|
||||||
|
const end = filter.dateTo
|
||||||
|
? new Date(new Date(`${filter.dateTo}T00:00:00`).getTime() + 24 * 60 * 60 * 1000)
|
||||||
|
: new Date();
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async safeReceived(range: { start: Date; end: Date }, warehouseId?: string): Promise<number> {
|
||||||
try {
|
try {
|
||||||
return await this.dataSource
|
const qb = this.dataSource
|
||||||
.getRepository(WarehouseInventory)
|
.getRepository(WarehouseInventory)
|
||||||
.createQueryBuilder('inv')
|
.createQueryBuilder('inv')
|
||||||
.where('inv.arrived_at >= :start', { start: startOfToday })
|
.where('inv.arrived_at >= :start AND inv.arrived_at < :end', range);
|
||||||
.getCount();
|
if (warehouseId) qb.andWhere('inv.warehouse_id = :warehouseId', { warehouseId });
|
||||||
|
return await qb.getCount();
|
||||||
} catch {
|
} catch {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDashboard(): Promise<WarehouseDashboard> {
|
async getDashboard(filter: WarehouseDashboardFilter = {}): Promise<WarehouseDashboard> {
|
||||||
const warehouseRepo = this.dataSource.getRepository(Warehouse);
|
const warehouseRepo = this.dataSource.getRepository(Warehouse);
|
||||||
const inventoryRepo = this.dataSource.getRepository(WarehouseInventory);
|
const inventoryRepo = this.dataSource.getRepository(WarehouseInventory);
|
||||||
|
const warehouseId = filter.warehouseId || undefined;
|
||||||
const startOfToday = new Date();
|
const scope = warehouseId ? { warehouseId } : {};
|
||||||
startOfToday.setHours(0, 0, 0, 0);
|
const range = this.resolveRange(filter);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
totalWarehouses,
|
totalWarehouses,
|
||||||
@@ -103,23 +127,23 @@ export class WarehouseDashboardService {
|
|||||||
dispatched,
|
dispatched,
|
||||||
readyForPickup,
|
readyForPickup,
|
||||||
delivered,
|
delivered,
|
||||||
receivedToday,
|
received,
|
||||||
emptyContainers,
|
emptyContainers,
|
||||||
importTrains,
|
importTrains,
|
||||||
exportTrains,
|
exportTrains,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.safeCount(warehouseRepo),
|
this.safeCount(warehouseRepo),
|
||||||
this.safeCount(inventoryRepo),
|
this.safeCount(inventoryRepo, { where: { ...scope } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'RECEIVED', inspectionStatus: IsNull() } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'RECEIVED', inspectionStatus: IsNull() } }),
|
||||||
this.safeCount(inventoryRepo, { where: { inspectionStatus: 'PASSED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, inspectionStatus: 'PASSED' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'STORED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'STORED' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'RESERVED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'RESERVED' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'READY_FOR_LOADING' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'READY_FOR_LOADING' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'LOADED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'LOADED' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'DISPATCHED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'DISPATCHED' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'READY_FOR_PICKUP' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'READY_FOR_PICKUP' } }),
|
||||||
this.safeCount(inventoryRepo, { where: { status: 'DELIVERED' } }),
|
this.safeCount(inventoryRepo, { where: { ...scope, status: 'DELIVERED' } }),
|
||||||
this.safeReceivedToday(startOfToday),
|
this.safeReceived(range, warehouseId),
|
||||||
// ponytail: the container fleet has no literal EMPTY status (AVAILABLE | LOADED |
|
// ponytail: the container fleet has no literal EMPTY status (AVAILABLE | LOADED |
|
||||||
// IN_TRANSIT | MAINTENANCE | DAMAGED) — AVAILABLE (not on a wagon, not in transit,
|
// IN_TRANSIT | MAINTENANCE | DAMAGED) — AVAILABLE (not on a wagon, not in transit,
|
||||||
// not flagged) is the closest proxy for "empty and free to use". Revisit if the
|
// not flagged) is the closest proxy for "empty and free to use". Revisit if the
|
||||||
@@ -135,7 +159,7 @@ export class WarehouseDashboardService {
|
|||||||
return {
|
return {
|
||||||
totalWarehouses,
|
totalWarehouses,
|
||||||
totalInventory,
|
totalInventory,
|
||||||
receivedToday,
|
received,
|
||||||
awaitingInspection,
|
awaitingInspection,
|
||||||
inspected,
|
inspected,
|
||||||
stored,
|
stored,
|
||||||
|
|||||||
@@ -43,9 +43,20 @@ export class WarehousesController {
|
|||||||
|
|
||||||
@Get('dashboard')
|
@Get('dashboard')
|
||||||
@BookingStaff(FREIGHT_PERMS.warehouseDashboard.view)
|
@BookingStaff(FREIGHT_PERMS.warehouseDashboard.view)
|
||||||
@ApiOperation({ summary: 'Warehouse dashboard metrics' })
|
@ApiOperation({
|
||||||
dashboard() {
|
summary: 'Warehouse dashboard metrics',
|
||||||
return this.dashboardService.getDashboard();
|
description:
|
||||||
|
'dateFrom/dateTo scope only the activity counters (currently just "received"); ' +
|
||||||
|
'status-backlog and fleet counters are always current. Omit both for "received today" ' +
|
||||||
|
'(the original default). warehouseId scopes every warehouse_inventory-derived counter; ' +
|
||||||
|
'totalWarehouses/emptyContainers/importTrains/exportTrains are never warehouse-scoped.',
|
||||||
|
})
|
||||||
|
dashboard(
|
||||||
|
@Query('dateFrom') dateFrom?: string,
|
||||||
|
@Query('dateTo') dateTo?: string,
|
||||||
|
@Query('warehouseId') warehouseId?: string,
|
||||||
|
) {
|
||||||
|
return this.dashboardService.getDashboard({ dateFrom, dateTo, warehouseId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type {
|
|||||||
SaveWarehousePayload,
|
SaveWarehousePayload,
|
||||||
SaveYardPayload,
|
SaveYardPayload,
|
||||||
SaveZonePayload,
|
SaveZonePayload,
|
||||||
|
WarehouseDashboardFilter,
|
||||||
WarehouseFilter,
|
WarehouseFilter,
|
||||||
} from '@/types/warehouse';
|
} from '@/types/warehouse';
|
||||||
|
|
||||||
@@ -461,10 +462,10 @@ export function useInventoryActivity(id?: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWarehouseDashboard() {
|
export function useWarehouseDashboard(filter?: WarehouseDashboardFilter) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['warehouses', 'dashboard'],
|
queryKey: ['warehouses', 'dashboard', filter ?? {}],
|
||||||
queryFn: () => warehouseService.dashboard().then((r) => r.data),
|
queryFn: () => warehouseService.dashboard(filter).then((r) => r.data),
|
||||||
refetchInterval: DASHBOARD_REFETCH_MS,
|
refetchInterval: DASHBOARD_REFETCH_MS,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Badge, Card, Center, Divider, Group, Loader, SimpleGrid, Stack, Text, ThemeIcon } from '@mantine/core';
|
import { Badge, Card, Center, Divider, Group, Loader, Select, SimpleGrid, Stack, Text, ThemeIcon } from '@mantine/core';
|
||||||
|
import { DatePickerInput } from '@mantine/dates';
|
||||||
import {
|
import {
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
PackageCheck,
|
PackageCheck,
|
||||||
@@ -15,6 +17,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import { PageContainer, PageHeader } from '@/components/page';
|
import { PageContainer, PageHeader } from '@/components/page';
|
||||||
|
import { getDateRangePresets } from '@/components/common/dateRangePresets';
|
||||||
import {
|
import {
|
||||||
AccrualDashboard,
|
AccrualDashboard,
|
||||||
CycleTimeCard,
|
CycleTimeCard,
|
||||||
@@ -24,7 +27,7 @@ import {
|
|||||||
WarehouseOpsKpiStrip,
|
WarehouseOpsKpiStrip,
|
||||||
ZoneOccupancyHeatmap,
|
ZoneOccupancyHeatmap,
|
||||||
} from '@/components/warehouses';
|
} from '@/components/warehouses';
|
||||||
import { useWarehouseDashboard } from '@/hooks/useWarehouses';
|
import { useWarehouseDashboard, useWarehouses } from '@/hooks/useWarehouses';
|
||||||
import type { WarehouseDashboard } from '@/types/warehouse';
|
import type { WarehouseDashboard } from '@/types/warehouse';
|
||||||
|
|
||||||
function SectionTitle({ children }: { children: React.ReactNode }) {
|
function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||||
@@ -50,7 +53,7 @@ const GREEN = '#084b21';
|
|||||||
const METRICS: Metric[] = [
|
const METRICS: Metric[] = [
|
||||||
{ key: 'totalWarehouses', label: 'Total Warehouses', icon: <WarehouseIcon size={22} />, to: '/dashboard/warehouses', theme: ORANGE },
|
{ key: 'totalWarehouses', label: 'Total Warehouses', icon: <WarehouseIcon size={22} />, to: '/dashboard/warehouses', theme: ORANGE },
|
||||||
{ key: 'totalInventory', label: 'Total Inventory', icon: <Boxes size={22} />, to: '/dashboard/warehouse-inventory', theme: GREEN },
|
{ key: 'totalInventory', label: 'Total Inventory', icon: <Boxes size={22} />, to: '/dashboard/warehouse-inventory', theme: GREEN },
|
||||||
{ key: 'receivedToday', label: 'Received Today', icon: <PackagePlus size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: ORANGE },
|
{ key: 'received', label: 'Received Today', icon: <PackagePlus size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: ORANGE },
|
||||||
{ key: 'awaitingInspection', label: 'Awaiting Inspection', icon: <ClipboardList size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: GREEN },
|
{ key: 'awaitingInspection', label: 'Awaiting Inspection', icon: <ClipboardList size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: GREEN },
|
||||||
{ key: 'emptyContainers', label: 'Empty Containers', icon: <PackageOpen size={22} />, to: '/dashboard/containers', theme: ORANGE },
|
{ key: 'emptyContainers', label: 'Empty Containers', icon: <PackageOpen size={22} />, to: '/dashboard/containers', theme: ORANGE },
|
||||||
{ key: 'importTrains', label: 'Import Trains', icon: <Train size={22} />, to: '/dashboard/import-warehouse', theme: GREEN },
|
{ key: 'importTrains', label: 'Import Trains', icon: <Train size={22} />, to: '/dashboard/import-warehouse', theme: GREEN },
|
||||||
@@ -64,7 +67,23 @@ const METRICS: Metric[] = [
|
|||||||
|
|
||||||
export default function WarehouseDashboardPage() {
|
export default function WarehouseDashboardPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isError, isLoading } = useWarehouseDashboard();
|
// Both null → the API defaults `received` to "today", matching the page's original behaviour.
|
||||||
|
const [dateRange, setDateRange] = useState<[string | null, string | null]>([null, null]);
|
||||||
|
const [warehouseId, setWarehouseId] = useState<string | null>(null);
|
||||||
|
const [dateFrom, dateTo] = dateRange;
|
||||||
|
const hasCustomRange = Boolean(dateFrom || dateTo);
|
||||||
|
|
||||||
|
const warehousesQuery = useWarehouses();
|
||||||
|
const warehouseOptions = useMemo(
|
||||||
|
() => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })),
|
||||||
|
[warehousesQuery.data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, isError, isLoading } = useWarehouseDashboard({
|
||||||
|
dateFrom: dateFrom ?? undefined,
|
||||||
|
dateTo: dateTo ?? undefined,
|
||||||
|
warehouseId: warehouseId ?? undefined,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
@@ -72,27 +91,58 @@ export default function WarehouseDashboardPage() {
|
|||||||
title="Warehouse Dashboard"
|
title="Warehouse Dashboard"
|
||||||
subtitle="Live overview of warehouse capacity and inventory lifecycle."
|
subtitle="Live overview of warehouse capacity and inventory lifecycle."
|
||||||
action={
|
action={
|
||||||
<Badge
|
<Group gap="sm" wrap="wrap" justify="flex-end">
|
||||||
color="edr-green"
|
<Select
|
||||||
variant="light"
|
placeholder="All warehouses"
|
||||||
size="lg"
|
clearable
|
||||||
leftSection={
|
searchable
|
||||||
<span
|
data={warehouseOptions}
|
||||||
style={{
|
value={warehouseId}
|
||||||
display: 'inline-block',
|
onChange={setWarehouseId}
|
||||||
width: 8,
|
w={220}
|
||||||
height: 8,
|
/>
|
||||||
borderRadius: '50%',
|
<DatePickerInput
|
||||||
background: 'var(--mantine-color-edr-green-6)',
|
type="range"
|
||||||
}}
|
placeholder="Received: today"
|
||||||
/>
|
value={dateRange}
|
||||||
}
|
onChange={setDateRange}
|
||||||
>
|
presets={getDateRangePresets()}
|
||||||
Live · updates every 60s
|
clearable
|
||||||
</Badge>
|
w={230}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
color="edr-green"
|
||||||
|
variant="light"
|
||||||
|
size="lg"
|
||||||
|
leftSection={
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'var(--mantine-color-edr-green-6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Live · updates every 60s
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{(warehouseId || hasCustomRange) && (
|
||||||
|
<Text size="xs" c="dimmed" mt={-8}>
|
||||||
|
Scoped to{' '}
|
||||||
|
{warehouseId ? warehouseOptions.find((o) => o.value === warehouseId)?.label ?? 'selected warehouse' : 'all warehouses'}
|
||||||
|
{hasCustomRange
|
||||||
|
? ` · Received counts ${dateFrom ?? '…'} to ${dateTo ?? '…'}`
|
||||||
|
: ' · Received counts: today'}
|
||||||
|
. Status-backlog and fleet counters are always current regardless of the date range.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Center py="xl">
|
<Center py="xl">
|
||||||
<Loader />
|
<Loader />
|
||||||
@@ -123,7 +173,7 @@ export default function WarehouseDashboardPage() {
|
|||||||
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
||||||
<div>
|
<div>
|
||||||
<Text size="xs" c="edr-muted" tt="uppercase" fw={700} style={{ letterSpacing: 0.4 }}>
|
<Text size="xs" c="edr-muted" tt="uppercase" fw={700} style={{ letterSpacing: 0.4 }}>
|
||||||
{metric.label}
|
{metric.key === 'received' && hasCustomRange ? 'Received' : metric.label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fw={800} fz={32} mt={8} c="edr-text" lh={1.1}>
|
<Text fw={800} fz={32} mt={8} c="edr-text" lh={1.1}>
|
||||||
{data ? data[metric.key] : 0}
|
{data ? data[metric.key] : 0}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import type {
|
|||||||
Warehouse,
|
Warehouse,
|
||||||
WarehouseActivityLog,
|
WarehouseActivityLog,
|
||||||
WarehouseDashboard,
|
WarehouseDashboard,
|
||||||
|
WarehouseDashboardFilter,
|
||||||
WarehouseFacility,
|
WarehouseFacility,
|
||||||
WarehouseFilter,
|
WarehouseFilter,
|
||||||
WarehouseInventoryItem,
|
WarehouseInventoryItem,
|
||||||
@@ -245,7 +246,10 @@ export const warehouseService = {
|
|||||||
apiClient.get<Warehouse[]>(URL_CONSTANTS.WAREHOUSES.BASE, {
|
apiClient.get<Warehouse[]>(URL_CONSTANTS.WAREHOUSES.BASE, {
|
||||||
params: cleanParams(filter ?? {}),
|
params: cleanParams(filter ?? {}),
|
||||||
}),
|
}),
|
||||||
dashboard: () => apiClient.get<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD),
|
dashboard: (filter?: WarehouseDashboardFilter) =>
|
||||||
|
apiClient.get<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD, {
|
||||||
|
params: cleanParams(filter ?? {}),
|
||||||
|
}),
|
||||||
getDashboardSummary: (_filter?: InventoryFilter) =>
|
getDashboardSummary: (_filter?: InventoryFilter) =>
|
||||||
apiClient.get<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD),
|
apiClient.get<WarehouseDashboard>(URL_CONSTANTS.WAREHOUSES.DASHBOARD),
|
||||||
getById: (id: string) => apiClient.get<Warehouse>(URL_CONSTANTS.WAREHOUSES.BY_ID(id)),
|
getById: (id: string) => apiClient.get<Warehouse>(URL_CONSTANTS.WAREHOUSES.BY_ID(id)),
|
||||||
|
|||||||
@@ -286,10 +286,18 @@ export interface WarehouseActivityLog {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Both dates omitted → `received` defaults to "today" (the original behaviour). */
|
||||||
|
export interface WarehouseDashboardFilter {
|
||||||
|
dateFrom?: string;
|
||||||
|
dateTo?: string;
|
||||||
|
warehouseId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WarehouseDashboard {
|
export interface WarehouseDashboard {
|
||||||
totalWarehouses: number;
|
totalWarehouses: number;
|
||||||
totalInventory: number;
|
totalInventory: number;
|
||||||
receivedToday: number;
|
/** Items received in the requested range — "today" when no range is set. */
|
||||||
|
received: number;
|
||||||
awaitingInspection: number;
|
awaitingInspection: number;
|
||||||
inspected: number;
|
inspected: number;
|
||||||
stored: number;
|
stored: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user