From e105a93d4045b9fe10a9ef52a48b1285810665fe Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 30 Jun 2026 13:50:39 +0000 Subject: [PATCH] Preview panel for PurchaseOrder model --- .../previews/models/PurchaseOrderPreview.tsx | 25 ++ .../pages/purchasing/PurchaseOrderDetail.tsx | 238 +--------------- .../purchasing/PurchaseOrderDetailsPanel.tsx | 258 ++++++++++++++++++ 3 files changed, 297 insertions(+), 224 deletions(-) create mode 100644 src/frontend/src/components/previews/models/PurchaseOrderPreview.tsx create mode 100644 src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx diff --git a/src/frontend/src/components/previews/models/PurchaseOrderPreview.tsx b/src/frontend/src/components/previews/models/PurchaseOrderPreview.tsx new file mode 100644 index 0000000000..c5033022be --- /dev/null +++ b/src/frontend/src/components/previews/models/PurchaseOrderPreview.tsx @@ -0,0 +1,25 @@ +import { t } from '@lingui/core/macro'; +import { PurchaseOrderDetailsPanel } from '../../../pages/purchasing/PurchaseOrderDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function PurchaseOrderPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const supplier = instance?.supplier_detail?.name ?? instance?.supplier_name; + const ref = instance?.reference ?? `#${modelId}`; + + let title = `${t`Purchase Order`} ${ref}`; + + if (supplier) { + title += ` (${supplier})`; + } + + return { + title: title, + preview: + }; +} diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index 43f5e84301..ce2549bdf6 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { Accordion, Grid, Skeleton, Stack } from '@mantine/core'; +import { Accordion, Stack } from '@mantine/core'; import { IconInfoCircle, IconList, IconPackages } from '@tabler/icons-react'; import { type ReactNode, useMemo } from 'react'; import { useParams } from 'react-router-dom'; @@ -9,17 +9,10 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; -import { TagsList } from '@lib/index'; import type { PanelType } from '@lib/types/Panel'; import AdminButton from '../../components/buttons/AdminButton'; import PrimaryActionButton from '../../components/buttons/PrimaryActionButton'; import { PrintingActions } from '../../components/buttons/PrintingActions'; -import { - type DetailsField, - DetailsTable -} from '../../components/details/Details'; -import { DetailsImage } from '../../components/details/DetailsImage'; -import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { BarcodeActionDropdown, CancelItemAction, @@ -35,7 +28,6 @@ import NotesPanel from '../../components/panels/NotesPanel'; import { PanelGroup } from '../../components/panels/PanelGroup'; import ParametersPanel from '../../components/panels/ParametersPanel'; import { StatusRenderer } from '../../components/render/StatusRenderer'; -import { formatCurrency } from '../../defaults/formatters'; import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms'; import { useCreateApiFormModal, @@ -48,6 +40,7 @@ import { useUserState } from '../../states/UserState'; import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable'; import { PurchaseOrderLineItemTable } from '../../tables/purchasing/PurchaseOrderLineItemTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; +import { PurchaseOrderDetailsPanel } from './PurchaseOrderDetailsPanel'; /** * Detail page for a single PurchaseOrder @@ -72,13 +65,13 @@ export default function PurchaseOrderDetail() { refetchOnMount: true }); - const orderCurrency = useMemo(() => { - return ( + const orderCurrency = useMemo( + () => order.order_currency || order.supplier_detail?.currency || - globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY') - ); - }, [order, globalSettings]); + globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY'), + [order, globalSettings] + ); const purchaseOrderFields = usePurchaseOrderFields({}); @@ -133,222 +126,19 @@ export default function PurchaseOrderDetail() { modelType: ModelType.purchaseorder }); - const detailsPanel = useMemo(() => { - if (instanceQuery.isFetching) { - return ; - } - - const tl: DetailsField[] = [ - { - type: 'text', - name: 'reference', - label: t`Reference`, - copy: true - }, - { - type: 'text', - name: 'supplier_reference', - label: t`Supplier Reference`, - icon: 'reference', - hidden: !order.supplier_reference, - copy: true - }, - { - type: 'link', - name: 'supplier', - icon: 'suppliers', - label: t`Supplier`, - model: ModelType.company - }, - { - type: 'text', - name: 'description', - label: t`Description`, - copy: true - }, - { - type: 'status', - name: 'status', - label: t`Status`, - model: ModelType.purchaseorder - }, - { - type: 'status', - name: 'status_custom_key', - label: t`Custom Status`, - model: ModelType.purchaseorder, - icon: 'status', - hidden: - !order.status_custom_key || order.status_custom_key == order.status - } - ]; - - const tr: DetailsField[] = [ - { - type: 'progressbar', - name: 'completed', - icon: 'progress', - label: t`Completed Line Items`, - total: order.line_items, - progress: order.completed_lines - }, - { - type: 'link', - model: ModelType.stocklocation, - link: true, - name: 'destination', - label: t`Destination`, - hidden: !order.destination - }, - { - type: 'text', - name: 'currency', - label: t`Order Currency`, - value_formatter: () => orderCurrency - }, - { - type: 'text', - name: 'total_price', - label: t`Total Cost`, - value_formatter: () => { - return formatCurrency(order?.total_price, { - currency: order?.order_currency || order?.supplier_detail?.currency - }); - } - } - ]; - - const bl: DetailsField[] = [ - { - type: 'link', - external: true, - name: 'link', - label: t`Link`, - copy: true, - hidden: !order.link - }, - { - type: 'text', - name: 'contact_detail.name', - label: t`Contact`, - icon: 'user', - copy: true, - hidden: !order.contact - }, - { - type: 'text', - name: 'contact_detail.email', - label: t`Contact Email`, - icon: 'email', - copy: true, - hidden: !order.contact_detail?.email - }, - { - type: 'text', - name: 'contact_detail.phone', - label: t`Contact Phone`, - icon: 'phone', - copy: true, - hidden: !order.contact_detail?.phone - }, - { - type: 'text', - name: 'project_code_label', - label: t`Project Code`, - icon: 'reference', - copy: true, - hidden: !order.project_code - }, - { - type: 'text', - name: 'responsible', - label: t`Responsible`, - badge: 'owner', - hidden: !order.responsible - } - ]; - - const br: DetailsField[] = [ - { - type: 'date', - name: 'creation_date', - label: t`Creation Date`, - copy: true, - icon: 'calendar' - }, - { - type: 'date', - name: 'issue_date', - label: t`Issue Date`, - icon: 'calendar', - copy: true, - hidden: !order.issue_date - }, - { - type: 'date', - name: 'start_date', - label: t`Start Date`, - icon: 'calendar', - copy: true, - hidden: !order.start_date - }, - { - type: 'date', - name: 'target_date', - label: t`Target Date`, - icon: 'calendar', - copy: true, - hidden: !order.target_date - }, - { - type: 'date', - name: 'complete_date', - icon: 'calendar_check', - label: t`Completion Date`, - copy: true, - hidden: !order.complete_date - }, - { - type: 'date', - name: 'updated_at', - label: t`Last Updated`, - icon: 'calendar', - copy: true, - showTime: true, - hidden: !order.updated_at - } - ]; - - return ( - - - - - - - - - - - - - - - ); - }, [order, orderCurrency, instanceQuery]); - const orderPanels: PanelType[] = useMemo(() => { return [ { name: 'detail', label: t`Order Details`, icon: , - content: detailsPanel + content: ( + + ) }, { name: 'line-items', diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx new file mode 100644 index 0000000000..60156bf3dd --- /dev/null +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetailsPanel.tsx @@ -0,0 +1,258 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack } from '@mantine/core'; +import { useMemo } from 'react'; + +import TagsList from '@lib/components/TagsList'; +import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; +import { ModelType } from '@lib/enums/ModelType'; +import { UserRoles } from '@lib/enums/Roles'; +import { apiUrl } from '@lib/functions/Api'; + +import { + type DetailsField, + DetailsTable +} from '../../components/details/Details'; +import { DetailsImage } from '../../components/details/DetailsImage'; +import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; +import { formatCurrency } from '../../defaults/formatters'; +import { useGlobalSettingsState } from '../../states/SettingsStates'; + +export function PurchaseOrderDetailsPanel({ + instance, + allowImageEdit = false, + refreshInstance +}: Readonly<{ + instance: any; + allowImageEdit?: boolean; + refreshInstance?: () => void; +}>) { + const globalSettings = useGlobalSettingsState(); + + const orderCurrency = useMemo( + () => + instance?.order_currency || + instance?.supplier_detail?.currency || + globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY'), + [instance, globalSettings] + ); + + const tl: DetailsField[] = [ + { + type: 'text', + name: 'reference', + label: t`Reference`, + copy: true + }, + { + type: 'text', + name: 'supplier_reference', + label: t`Supplier Reference`, + icon: 'reference', + hidden: !instance?.supplier_reference, + copy: true + }, + { + type: 'link', + name: 'supplier', + icon: 'suppliers', + label: t`Supplier`, + model: ModelType.company + }, + { + type: 'text', + name: 'description', + label: t`Description`, + copy: true + }, + { + type: 'status', + name: 'status', + label: t`Status`, + model: ModelType.purchaseorder + }, + { + type: 'status', + name: 'status_custom_key', + label: t`Custom Status`, + model: ModelType.purchaseorder, + icon: 'status', + hidden: + !instance?.status_custom_key || + instance?.status_custom_key == instance?.status + } + ]; + + const tr: DetailsField[] = [ + { + type: 'progressbar', + name: 'completed', + icon: 'progress', + label: t`Completed Line Items`, + total: instance?.line_items, + progress: instance?.completed_lines + }, + { + type: 'link', + model: ModelType.stocklocation, + link: true, + name: 'destination', + label: t`Destination`, + hidden: !instance?.destination + }, + { + type: 'text', + name: 'currency', + label: t`Order Currency`, + value_formatter: () => orderCurrency + }, + { + type: 'text', + name: 'total_price', + label: t`Total Cost`, + value_formatter: () => + formatCurrency(instance?.total_price, { + currency: + instance?.order_currency || instance?.supplier_detail?.currency + }) + } + ]; + + const bl: DetailsField[] = [ + { + type: 'link', + external: true, + name: 'link', + label: t`Link`, + copy: true, + hidden: !instance?.link + }, + { + type: 'text', + name: 'contact_detail.name', + label: t`Contact`, + icon: 'user', + copy: true, + hidden: !instance?.contact + }, + { + type: 'text', + name: 'contact_detail.email', + label: t`Contact Email`, + icon: 'email', + copy: true, + hidden: !instance?.contact_detail?.email + }, + { + type: 'text', + name: 'contact_detail.phone', + label: t`Contact Phone`, + icon: 'phone', + copy: true, + hidden: !instance?.contact_detail?.phone + }, + { + type: 'text', + name: 'project_code_label', + label: t`Project Code`, + icon: 'reference', + copy: true, + hidden: !instance?.project_code + }, + { + type: 'text', + name: 'responsible', + label: t`Responsible`, + badge: 'owner', + hidden: !instance?.responsible + } + ]; + + const br: DetailsField[] = [ + { + type: 'date', + name: 'creation_date', + label: t`Creation Date`, + copy: true, + icon: 'calendar' + }, + { + type: 'date', + name: 'issue_date', + label: t`Issue Date`, + icon: 'calendar', + copy: true, + hidden: !instance?.issue_date + }, + { + type: 'date', + name: 'start_date', + label: t`Start Date`, + icon: 'calendar', + copy: true, + hidden: !instance?.start_date + }, + { + type: 'date', + name: 'target_date', + label: t`Target Date`, + icon: 'calendar', + copy: true, + hidden: !instance?.target_date + }, + { + type: 'date', + name: 'complete_date', + icon: 'calendar_check', + label: t`Completion Date`, + copy: true, + hidden: !instance?.complete_date + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !instance?.updated_at + } + ]; + + const parametersTable = useParameterDetailsGrid({ + model_type: ModelType.purchaseorder, + model_id: instance?.pk + }); + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + + ); +}