diff --git a/src/frontend/src/components/tables/PartStockCell.tsx b/src/frontend/src/components/tables/PartStockCell.tsx new file mode 100644 index 0000000000..d5e8dc6246 --- /dev/null +++ b/src/frontend/src/components/tables/PartStockCell.tsx @@ -0,0 +1,132 @@ +import { t } from '@lingui/core/macro'; +import { Group, Text } from '@mantine/core'; +import type { ReactNode } from 'react'; +import { formatDecimal } from '../../defaults/formatters'; +import { TableHoverCard } from './TableHoverCard'; + +export function renderPartStockCell(record: any): ReactNode { + if (record.virtual) { + return ( + + {t`Virtual part`} + + ); + } + + const extra: ReactNode[] = []; + + const stock = record?.total_in_stock ?? 0; + const allocated = + (record?.allocated_to_build_orders ?? 0) + + (record?.allocated_to_sales_orders ?? 0); + const available = Math.max(0, stock - allocated); + const min_stock = record?.minimum_stock ?? 0; + const max_stock = record?.maximum_stock ?? 0; + + let text = String(formatDecimal(stock)); + + let color: string | undefined = undefined; + + if (min_stock > stock) { + extra.push( + + {`${t`Minimum stock`}: ${formatDecimal(min_stock)}`} + + ); + + color = 'orange'; + } + + if (max_stock > 0 && stock > max_stock) { + extra.push( + + {`${t`Maximum stock`}: ${formatDecimal(max_stock)}`} + + ); + } + + if (record.ordering > 0) { + extra.push( + {`${t`On Order`}: ${formatDecimal(record.ordering)}`} + ); + } + + if (record.building) { + extra.push( + {`${t`Building`}: ${formatDecimal(record.building)}`} + ); + } + + if (record.allocated_to_build_orders > 0) { + extra.push( + + {`${t`Build Order Allocations`}: ${formatDecimal(record.allocated_to_build_orders)}`} + + ); + } + + if (record.allocated_to_sales_orders > 0) { + extra.push( + + {`${t`Sales Order Allocations`}: ${formatDecimal(record.allocated_to_sales_orders)}`} + + ); + } + + if (available != stock) { + extra.push( + + {t`Available`}: {formatDecimal(available)} + + ); + } + + if (record.external_stock > 0) { + extra.push( + + {t`External stock`}: {formatDecimal(record.external_stock)} + + ); + } + + if ((record.variant_stock ?? 0) > 0) { + extra.push( + + {t`Includes variant stock`}: {formatDecimal(record.variant_stock)} + + ); + extra.push( + + {t`Direct stock`}: {formatDecimal(record.in_stock ?? 0)} + + ); + } + + if (stock <= 0) { + color = 'red'; + text = t`No stock`; + } else if (available <= 0) { + color = 'orange'; + } else if (available < min_stock) { + color = 'yellow'; + } + + return ( + + + {text} + + {record.units && ( + + [{record.units}] + + )} + + } + title={t`Stock Information`} + extra={extra} + /> + ); +} diff --git a/src/frontend/src/tables/part/ParametricPartTable.tsx b/src/frontend/src/tables/part/ParametricPartTable.tsx index 0d30102e1b..32477cc389 100644 --- a/src/frontend/src/tables/part/ParametricPartTable.tsx +++ b/src/frontend/src/tables/part/ParametricPartTable.tsx @@ -7,6 +7,7 @@ import { DescriptionColumn, PartColumn } from '../../components/tables/ColumnRenderers'; +import { renderPartStockCell } from '../../components/tables/PartStockCell'; import ParametricDataTable from '../general/ParametricDataTable'; import { PartTableFilters } from './PartTableFilters'; @@ -33,7 +34,8 @@ export default function ParametricPartTable({ }, { accessor: 'total_in_stock', - sortable: true + sortable: true, + render: renderPartStockCell } ]; }, []); diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx index 3122a94dcb..5cf56f21e3 100644 --- a/src/frontend/src/tables/part/PartTable.tsx +++ b/src/frontend/src/tables/part/PartTable.tsx @@ -12,14 +12,13 @@ import type { ApiFormFieldSet } from '@lib/types/Forms'; import type { TableColumn } from '@lib/types/Tables'; import type { InvenTreeTableProps } from '@lib/types/Tables'; import { t } from '@lingui/core/macro'; -import { Group, Text } from '@mantine/core'; import { IconFileUpload, IconPackageImport, IconPlus, IconShoppingCart } from '@tabler/icons-react'; -import { type ReactNode, useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { ActionDropdown } from '../../components/items/ActionDropdown'; import { BooleanColumn, @@ -31,10 +30,10 @@ import { PartColumn } from '../../components/tables/ColumnRenderers'; import { InvenTreeTable } from '../../components/tables/InvenTreeTable'; -import { TableHoverCard } from '../../components/tables/TableHoverCard'; +import { renderPartStockCell } from '../../components/tables/PartStockCell'; import ImportPartWizard from '../../components/wizards/ImportPartWizard'; import OrderPartsWizard from '../../components/wizards/OrderPartsWizard'; -import { formatDecimal, formatPriceRange } from '../../defaults/formatters'; +import { formatPriceRange } from '../../defaults/formatters'; import { DuplicateField } from '../../forms/CommonFields'; import { dataImporterSessionFields } from '../../forms/ImporterForms'; import { usePartFields } from '../../forms/PartForms'; @@ -84,119 +83,7 @@ function partTableColumns(): TableColumn[] { accessor: 'total_in_stock', sortable: true, filter: ['has_stock', 'low_stock', 'high_stock'], - render: (record) => { - if (record.virtual) { - return ( - - {t`Virtual part`} - - ); - } - - const extra: ReactNode[] = []; - - const stock = record?.total_in_stock ?? 0; - const allocated = - (record?.allocated_to_build_orders ?? 0) + - (record?.allocated_to_sales_orders ?? 0); - const available = Math.max(0, stock - allocated); - const min_stock = record?.minimum_stock ?? 0; - const max_stock = record?.maximum_stock ?? 0; - - let text = String(formatDecimal(stock)); - - let color: string | undefined = undefined; - - if (min_stock > stock) { - extra.push( - - {`${t`Minimum stock`}: ${formatDecimal(min_stock)}`} - - ); - - color = 'orange'; - } - - if (max_stock > 0 && stock > max_stock) { - extra.push( - - {`${t`Maximum stock`}: ${formatDecimal(max_stock)}`} - - ); - } - - if (record.ordering > 0) { - extra.push( - {`${t`On Order`}: ${formatDecimal(record.ordering)}`} - ); - } - - if (record.building) { - extra.push( - {`${t`Building`}: ${formatDecimal(record.building)}`} - ); - } - - if (record.allocated_to_build_orders > 0) { - extra.push( - - {`${t`Build Order Allocations`}: ${formatDecimal(record.allocated_to_build_orders)}`} - - ); - } - - if (record.allocated_to_sales_orders > 0) { - extra.push( - - {`${t`Sales Order Allocations`}: ${formatDecimal(record.allocated_to_sales_orders)}`} - - ); - } - - if (available != stock) { - extra.push( - - {t`Available`}: {formatDecimal(available)} - - ); - } - - if (record.external_stock > 0) { - extra.push( - - {t`External stock`}: {formatDecimal(record.external_stock)} - - ); - } - - if (stock <= 0) { - color = 'red'; - text = t`No stock`; - } else if (available <= 0) { - color = 'orange'; - } else if (available < min_stock) { - color = 'yellow'; - } - - return ( - - - {text} - - {record.units && ( - - [{record.units}] - - )} - - } - title={t`Stock Information`} - extra={extra} - /> - ); - } + render: renderPartStockCell }, { accessor: 'price_range',