diff --git a/src/frontend/src/components/details/LineItemOverviewTable.tsx b/src/frontend/src/components/details/LineItemOverviewTable.tsx new file mode 100644 index 000000000000..327451662f90 --- /dev/null +++ b/src/frontend/src/components/details/LineItemOverviewTable.tsx @@ -0,0 +1,125 @@ +import { t } from '@lingui/core/macro'; +import { Anchor, Group, Paper, Stack } from '@mantine/core'; +import { useQuery } from '@tanstack/react-query'; +import { DataTable } from 'mantine-datatable'; +import { useMemo } from 'react'; +import { Link } from 'react-router-dom'; + +import { ProgressBar } from '@lib/components/ProgressBar'; +import { StylishText } from '@lib/components/StylishText'; +import type { ApiEndpoints } from '@lib/enums/ApiEndpoints'; +import { apiUrl } from '@lib/functions/Api'; +import { resolveItem } from '@lib/functions/Conversion'; +import { formatDecimal } from '@lib/functions/Formatting'; +import { useApi } from '../../contexts/ApiContext'; +import { RenderPartColumn } from '../tables/ColumnRenderers'; + +export interface LineItemProgress { + value: number; + maximum: number; +} + +export interface LineItemOverviewTableProps { + // API endpoint which returns the line items for this order + endpoint: ApiEndpoints; + // Extra query parameters to filter the line item list (e.g. the order pk) + params?: Record; + // Accessor for the "part" detail object on each line item record + partAccessor?: string; + // Accessor for the target quantity on each line item record + quantityAccessor?: string; + // Extract progress information from a line item record. + // If not provided (or returns null for a record), the quantity is shown as plain text + progress?: (record: any) => LineItemProgress | null; + // Title for the progress column + progressLabel?: string; + // Name of the panel (within the same PanelGroup) which shows the full line item table + linkToPanel?: string; + // Maximum number of line items to display + limit?: number; + // Optional title, displayed above the table + title?: string; +} + +/** + * A minimal, read-only overview of an order's line items - not selectable, + * searchable, sortable or paginated. Intended for embedding in an order's + * "Details" panel, alongside the full interactive line item table on a separate panel. + */ +export function LineItemOverviewTable({ + endpoint, + params, + partAccessor = 'part_detail', + quantityAccessor = 'quantity', + progress, + progressLabel, + linkToPanel = 'line-items', + limit = 10, + title +}: Readonly) { + const api = useApi(); + + const { data, isFetching } = useQuery({ + queryKey: ['line-item-overview', endpoint, params, limit], + queryFn: () => + api + .get(apiUrl(endpoint), { + params: { ...params, limit, offset: 0 } + }) + .then((response) => response.data), + placeholderData: (previous) => previous + }); + + const records: any[] = data?.results ?? []; + const count: number = data?.count ?? records.length; + + const columns = useMemo(() => { + return [ + { + accessor: 'part', + title: t`Part`, + render: (record: any) => ( + + ) + }, + { + accessor: 'progress', + title: progressLabel ?? t`Progress`, + render: (record: any) => { + const p = progress?.(record); + + if (p) { + return ( + + ); + } + + return formatDecimal(resolveItem(record, quantityAccessor)); + } + } + ]; + }, [partAccessor, quantityAccessor, progress, progressLabel]); + + return ( + + + {title && {title}} + + {count > records.length && ( + + + {t`View all ${count} line items`} + + + )} + + + ); +} diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx index 60156bf3dd1e..9d5de3d620c1 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx @@ -14,6 +14,7 @@ import { } from '../../components/details/Details'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { LineItemOverviewTable } from '../../components/details/LineItemOverviewTable'; import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; import { formatCurrency } from '../../defaults/formatters'; import { useGlobalSettingsState } from '../../states/SettingsStates'; @@ -253,6 +254,16 @@ export function PurchaseOrderDetailsPanel({ + ({ + value: record.received, + maximum: record.quantity + })} + /> ); } diff --git a/src/frontend/src/pages/sales/ReturnOrderDetailsPanel.tsx b/src/frontend/src/pages/sales/ReturnOrderDetailsPanel.tsx index aa4117700e7b..ba8adf2b889c 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetailsPanel.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetailsPanel.tsx @@ -14,6 +14,7 @@ import { } from '../../components/details/Details'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { LineItemOverviewTable } from '../../components/details/LineItemOverviewTable'; import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; import { RenderAddress } from '../../components/render/Company'; import { formatCurrency } from '../../defaults/formatters'; @@ -265,6 +266,16 @@ export function ReturnOrderDetailsPanel({ + ({ + value: record.received_date ? 1 : 0, + maximum: 1 + })} + /> ); } diff --git a/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx b/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx index cddd8dc626a5..787c4cfb45f9 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx @@ -14,6 +14,7 @@ import { } from '../../components/details/Details'; import { DetailsImage } from '../../components/details/DetailsImage'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { LineItemOverviewTable } from '../../components/details/LineItemOverviewTable'; import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; import { RenderAddress } from '../../components/render/Company'; import { formatCurrency } from '../../defaults/formatters'; @@ -265,6 +266,16 @@ export function SalesOrderDetailsPanel({ + ({ + value: record.shipped, + maximum: record.quantity + })} + /> ); } diff --git a/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx index a3388b7bc9a1..38751da405eb 100644 --- a/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx +++ b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx @@ -1,6 +1,7 @@ import { t } from '@lingui/core/macro'; import { Grid, Skeleton, Stack } from '@mantine/core'; +import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { TagsList } from '@lib/index'; @@ -9,6 +10,7 @@ import { DetailsTable } from '../../components/details/Details'; import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { LineItemOverviewTable } from '../../components/details/LineItemOverviewTable'; import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; export function TransferOrderDetailsPanel({ @@ -176,6 +178,16 @@ export function TransferOrderDetailsPanel({ + ({ + value: record.transferred, + maximum: record.quantity + })} + /> ); } diff --git a/src/frontend/src/tables/stock/TransferOrderTable.tsx b/src/frontend/src/tables/stock/TransferOrderTable.tsx index 455c60b29350..af5fcaad749b 100644 --- a/src/frontend/src/tables/stock/TransferOrderTable.tsx +++ b/src/frontend/src/tables/stock/TransferOrderTable.tsx @@ -11,6 +11,7 @@ import { CreatedByColumn, CreationDateColumn, DescriptionColumn, + LineItemsProgressColumn, LocationColumn, ProjectCodeColumn, ReferenceColumn, @@ -57,7 +58,7 @@ export function TransferOrderTable({ sortable: true, switchable: true }), - // LineItemsProgressColumn({}), + LineItemsProgressColumn({}), StatusColumn({ model: ModelType.transferorder }), ProjectCodeColumn({ defaultVisible: false