diff --git a/src/frontend/src/components/render/StatusRenderer.tsx b/src/frontend/src/components/render/StatusRenderer.tsx index 21bcc549ac..debae8f082 100644 --- a/src/frontend/src/components/render/StatusRenderer.tsx +++ b/src/frontend/src/components/render/StatusRenderer.tsx @@ -68,7 +68,7 @@ export const StatusRenderer = ({ type, options }: { - status: string; + status: string | number; type: ModelType | string; options?: renderStatusLabelOptionsInterface; }) => { diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx index 6bcbb2cd71..712a9fa850 100644 --- a/src/frontend/src/functions/icons.tsx +++ b/src/frontend/src/functions/icons.tsx @@ -12,10 +12,12 @@ import { IconCopy, IconCornerUpRightDouble, IconCurrencyDollar, + IconDotsCircleHorizontal, IconExternalLink, IconFileUpload, IconGitBranch, IconGridDots, + IconHash, IconLayersLinked, IconLink, IconList, @@ -23,14 +25,15 @@ import { IconMapPin, IconMapPinHeart, IconNotes, + IconNumbers, IconPackage, IconPackageImport, IconPackages, IconPaperclip, IconPhoto, + IconProgressCheck, IconQuestionMark, IconRulerMeasure, - IconShape, IconShoppingCart, IconShoppingCartHeart, IconStack2, @@ -72,6 +75,8 @@ const icons: { [key: string]: (props: TablerIconsProps) => React.JSX.Element } = revision: IconGitBranch, units: IconRulerMeasure, keywords: IconTag, + status: IconInfoCircle, + info: IconInfoCircle, details: IconInfoCircle, parameters: IconList, stock: IconPackages, @@ -120,7 +125,10 @@ const icons: { [key: string]: (props: TablerIconsProps) => React.JSX.Element } = user: IconUser, group: IconUsersGroup, check: IconCheck, - copy: IconCopy + copy: IconCopy, + quantity: IconNumbers, + progress: IconProgressCheck, + reference: IconHash }; /** diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index 5f32b55fc5..6ae38b7d56 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -1,5 +1,12 @@ import { t } from '@lingui/macro'; -import { Group, LoadingOverlay, Skeleton, Stack, Table } from '@mantine/core'; +import { + Grid, + Group, + LoadingOverlay, + Skeleton, + Stack, + Table +} from '@mantine/core'; import { IconClipboardCheck, IconClipboardList, @@ -17,6 +24,8 @@ import { import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; +import { DetailsImage } from '../../components/details/DetailsImage'; +import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { ActionDropdown, DuplicateItemAction, @@ -33,10 +42,12 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; import { buildOrderFields } from '../../forms/BuildForms'; +import { partCategoryFields } from '../../forms/PartForms'; import { useEditApiFormModal } from '../../hooks/UseForm'; import { useInstance } from '../../hooks/UseInstance'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; +import { DetailsField, DetailsTable } from '../../tables/Details'; import BuildLineTable from '../../tables/build/BuildLineTable'; import { BuildOrderTable } from '../../tables/build/BuildOrderTable'; import { AttachmentTable } from '../../tables/general/AttachmentTable'; @@ -63,36 +74,102 @@ export default function BuildDetail() { refetchOnMount: true }); - const buildDetailsPanel = useMemo(() => { + const detailsPanel = useMemo(() => { + if (instanceQuery.isFetching) { + return ; + } + + let tl: DetailsField[] = [ + { + type: 'link', + name: 'part', + label: t`Part`, + model: ModelType.part + }, + { + type: 'status', + name: 'status', + label: t`Status`, + model: ModelType.build + }, + { + type: 'text', + name: 'reference', + label: t`Reference` + }, + { + type: 'text', + name: 'title', + label: t`Description`, + icon: 'description' + } + ]; + + let tr: DetailsField[] = [ + { + type: 'text', + name: 'quantity', + label: t`Build Quantity` + }, + { + type: 'progressbar', + name: 'completed', + icon: 'progress', + total: build.quantity, + progress: build.completed, + label: t`Completed Outputs` + }, + { + type: 'link', + name: 'sales_order', + label: t`Sales Order`, + icon: 'sales_orders', + model: ModelType.salesorder, + model_field: 'reference', + hidden: !build.sales_order + }, + { + type: 'text', + name: 'issued_by', + label: t`Issued By`, + badge: 'user', + icon: 'user' + } + ]; + + let bl: DetailsField[] = [ + { + type: 'text', + name: 'issued_by', + label: t`Issued By` + }, + { + type: 'text', + name: 'responsible', + label: t`Responsible` + } + ]; + return ( - - - - - - - - - - - - - - - - -
{t`Base Part`}{build.part_detail?.name}
{t`Quantity`}{build.quantity}
{t`Build Status`} - {build?.status && ( - - )} -
-
-
+ + + + + + + + + + + + ); - }, [build]); + }, [build, instanceQuery]); const buildPanels: PanelType[] = useMemo(() => { return [ @@ -100,7 +177,7 @@ export default function BuildDetail() { name: 'details', label: t`Build Details`, icon: , - content: buildDetailsPanel + content: detailsPanel }, { name: 'allocate-stock', @@ -259,7 +336,7 @@ export default function BuildDetail() { title={build.reference} subtitle={build.title} detail={buildDetail} - imageUrl={build.part_detail?.thumbnail} + imageUrl={build.part_detail?.image ?? build.part_detail?.thumbnail} breadcrumbs={[ { name: t`Build Orders`, url: '/build' }, { name: build.reference, url: `/build/${build.pk}` } diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx index 5eda863d73..4303568d9b 100644 --- a/src/frontend/src/pages/stock/StockDetail.tsx +++ b/src/frontend/src/pages/stock/StockDetail.tsx @@ -103,7 +103,8 @@ export default function StockDetail() { { type: 'text', name: 'tests', - label: `Completed Tests` + label: `Completed Tests`, + icon: 'progress' }, { type: 'text', @@ -136,7 +137,7 @@ export default function StockDetail() { { type: 'text', name: 'available_stock', - label: t`In Stock` + label: t`Available` } // TODO: allocated_to_sales_orders // TODO: allocated_to_build_orders @@ -220,7 +221,7 @@ export default function StockDetail() { return [ { name: 'details', - label: t`Details`, + label: t`Stock Details`, icon: , content: detailsPanel }, diff --git a/src/frontend/src/tables/Details.tsx b/src/frontend/src/tables/Details.tsx index 60811e1389..58932c979f 100644 --- a/src/frontend/src/tables/Details.tsx +++ b/src/frontend/src/tables/Details.tsx @@ -17,6 +17,7 @@ import { Suspense, useMemo } from 'react'; import { api } from '../App'; import { ProgressBar } from '../components/items/ProgressBar'; import { getModelInfo } from '../components/render/ModelType'; +import { StatusRenderer } from '../components/render/StatusRenderer'; import { ApiEndpoints } from '../enums/ApiEndpoints'; import { ModelType } from '../enums/ModelType'; import { InvenTreeIcon } from '../functions/icons'; @@ -44,7 +45,7 @@ export type DetailsField = badge?: BadgeType; copy?: boolean; value_formatter?: () => ValueFormatterReturn; - } & (StringDetailField | LinkDetailField | ProgressBarfield); + } & (StringDetailField | LinkDetailField | ProgressBarfield | StatusField); type BadgeType = 'owner' | 'user' | 'group'; type ValueFormatterReturn = string | number | null; @@ -60,6 +61,8 @@ type LinkDetailField = { type InternalLinkField = { model: ModelType; + model_field?: string; + model_formatter?: (value: any) => string; }; type ExternalLinkField = { @@ -72,6 +75,11 @@ type ProgressBarfield = { total: number; }; +type StatusField = { + type: 'status'; + model: ModelType; +}; + type FieldValueType = string | number | undefined; type FieldProps = { @@ -327,6 +335,16 @@ function TableAnchorValue(props: FieldProps) { return getDetailUrl(props.field_data.model, props.field_value); }, [props.field_data.model, props.field_value]); + // Construct the "return value" for the fetched data + // Basic fallback value + let value = data?.name ?? 'No name defined'; + + if (props.field_data.model_formatter) { + value = props.field_data.model_formatter(data) ?? value; + } else if (props.field_data.model_field) { + value = data?.[props.field_data.model_field] ?? value; + } + return ( }> - {data.name ?? 'No name defined'} + {value} ); @@ -350,6 +368,12 @@ function ProgressBarValue(props: FieldProps) { ); } +function StatusValue(props: FieldProps) { + return ( + + ); +} + function CopyField({ value }: { value: string }) { return ( @@ -384,6 +408,8 @@ export function DetailsTableField({ return TableAnchorValue; case 'progressbar': return ProgressBarValue; + case 'status': + return StatusValue; default: return TableStringValue; }