diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx index 18f36069fb..13a81117d8 100644 --- a/src/frontend/src/components/previews/PreviewType.tsx +++ b/src/frontend/src/components/previews/PreviewType.tsx @@ -2,6 +2,7 @@ import { ModelType } from '@lib/enums/ModelType'; import type { ReactNode } from 'react'; import { PartPreviewComponent } from './models/PartPreview'; import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview'; +import { SalesOrderPreviewComponent } from './models/SalesOrderPreview'; import { StockPreviewComponent } from './models/StockPreview'; export interface PreviewType { @@ -31,6 +32,8 @@ export function getPreviewComponentForModel({ return StockPreviewComponent({ instance, modelId }); case ModelType.purchaseorder: return PurchaseOrderPreviewComponent({ instance, modelId }); + case ModelType.salesorder: + return SalesOrderPreviewComponent({ instance, modelId }); default: return null; } diff --git a/src/frontend/src/components/previews/models/SalesOrderPreview.tsx b/src/frontend/src/components/previews/models/SalesOrderPreview.tsx new file mode 100644 index 0000000000..83880b0e91 --- /dev/null +++ b/src/frontend/src/components/previews/models/SalesOrderPreview.tsx @@ -0,0 +1,25 @@ +import { t } from '@lingui/core/macro'; +import { SalesOrderDetailsPanel } from '../../../pages/sales/SalesOrderDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function SalesOrderPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const customer = instance?.customer_detail?.name ?? instance?.customer_name; + const ref = instance?.reference ?? `#${modelId}`; + + let title = `${t`Sales Order`} ${ref}`; + + if (customer) { + title += ` (${customer})`; + } + + return { + title, + preview: + }; +} diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index 086400a0f8..4b5c4df018 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { Accordion, Grid, Skeleton, Stack, Text } from '@mantine/core'; +import { Accordion, Skeleton, Stack } from '@mantine/core'; import { IconBookmark, IconCubeSend, @@ -11,7 +11,6 @@ import { type ReactNode, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { StylishText } from '@lib/components/StylishText'; -import TagsList from '@lib/components/TagsList'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; @@ -20,12 +19,6 @@ 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, @@ -40,9 +33,7 @@ import AttachmentPanel from '../../components/panels/AttachmentPanel'; import NotesPanel from '../../components/panels/NotesPanel'; import { PanelGroup } from '../../components/panels/PanelGroup'; import ParametersPanel from '../../components/panels/ParametersPanel'; -import { RenderAddress } from '../../components/render/Company'; import { StatusRenderer } from '../../components/render/StatusRenderer'; -import { formatCurrency } from '../../defaults/formatters'; import { useSalesOrderFields } from '../../forms/SalesOrderForms'; import { useCreateApiFormModal, @@ -57,6 +48,7 @@ import ExtraLineItemTable from '../../tables/general/ExtraLineItemTable'; import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable'; import SalesOrderLineItemTable from '../../tables/sales/SalesOrderLineItemTable'; import SalesOrderShipmentTable from '../../tables/sales/SalesOrderShipmentTable'; +import { SalesOrderDetailsPanel } from './SalesOrderDetailsPanel'; /** * Detail page for a single SalesOrder @@ -89,230 +81,6 @@ export default function SalesOrderDetail() { ); }, [order, globalSettings]); - const detailsPanel = useMemo(() => { - if (instanceQuery.isFetching) { - return ; - } - - const tl: DetailsField[] = [ - { - type: 'text', - name: 'reference', - label: t`Reference`, - copy: true - }, - { - type: 'text', - name: 'customer_reference', - label: t`Customer Reference`, - copy: true, - icon: 'reference', - hidden: !order.customer_reference - }, - { - type: 'link', - name: 'customer', - icon: 'customers', - label: t`Customer`, - model: ModelType.company - }, - { - type: 'text', - name: 'description', - label: t`Description`, - copy: true - }, - { - type: 'status', - name: 'status', - label: t`Status`, - model: ModelType.salesorder - }, - { - type: 'status', - name: 'status_custom_key', - label: t`Custom Status`, - model: ModelType.salesorder, - 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, - hidden: !order.line_items - }, - { - type: 'progressbar', - name: 'shipments', - icon: 'shipment', - label: t`Completed Shipments`, - total: order.shipments_count, - progress: order.completed_shipments_count, - hidden: !order.shipments_count - }, - { - 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: orderCurrency - }); - } - } - ]; - - const bl: DetailsField[] = [ - { - type: 'link', - external: true, - name: 'link', - label: t`Link`, - copy: true, - hidden: !order.link - }, - { - type: 'text', - name: 'address', - label: t`Shipping Address`, - icon: 'address', - value_formatter: () => - order.address_detail ? ( - - ) : ( - {t`Not specified`} - ) - }, - { - 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, - hidden: !order.creation_date - }, - { - 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', - hidden: !order.start_date, - copy: true - }, - { - type: 'date', - name: 'target_date', - label: t`Target Date`, - hidden: !order.target_date, - copy: true - }, - { - type: 'date', - name: 'shipment_date', - label: t`Completion Date`, - hidden: !order.shipment_date, - copy: true - }, - { - type: 'date', - name: 'updated_at', - label: t`Last Updated`, - icon: 'calendar', - copy: true, - showTime: true, - hidden: !order.updated_at - } - ]; - - return ( - - - - - - - - - - - - ); - }, [order, orderCurrency, instanceQuery]); - const soStatus = useStatusCodes({ modelType: ModelType.salesorder }); const lineItemsEditable: boolean = useMemo(() => { @@ -367,7 +135,13 @@ export default function SalesOrderDetail() { name: 'detail', label: t`Order Details`, icon: , - content: detailsPanel + content: ( + + ) }, { name: 'line-items', diff --git a/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx b/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx new file mode 100644 index 0000000000..cddd8dc626 --- /dev/null +++ b/src/frontend/src/pages/sales/SalesOrderDetailsPanel.tsx @@ -0,0 +1,270 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack, Text } 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 { RenderAddress } from '../../components/render/Company'; +import { formatCurrency } from '../../defaults/formatters'; +import { useGlobalSettingsState } from '../../states/SettingsStates'; + +export function SalesOrderDetailsPanel({ + instance, + allowImageEdit = false, + refreshInstance +}: Readonly<{ + instance: any; + allowImageEdit?: boolean; + refreshInstance?: () => void; +}>) { + const globalSettings = useGlobalSettingsState(); + + const orderCurrency = useMemo( + () => + instance?.order_currency || + instance?.customer_detail?.currency || + globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY'), + [instance, globalSettings] + ); + + const tl: DetailsField[] = [ + { + type: 'text', + name: 'reference', + label: t`Reference`, + copy: true + }, + { + type: 'text', + name: 'customer_reference', + label: t`Customer Reference`, + copy: true, + icon: 'reference', + hidden: !instance?.customer_reference + }, + { + type: 'link', + name: 'customer', + icon: 'customers', + label: t`Customer`, + model: ModelType.company + }, + { + type: 'text', + name: 'description', + label: t`Description`, + copy: true + }, + { + type: 'status', + name: 'status', + label: t`Status`, + model: ModelType.salesorder + }, + { + type: 'status', + name: 'status_custom_key', + label: t`Custom Status`, + model: ModelType.salesorder, + 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, + hidden: !instance?.line_items + }, + { + type: 'progressbar', + name: 'shipments', + icon: 'shipment', + label: t`Completed Shipments`, + total: instance?.shipments_count, + progress: instance?.completed_shipments_count, + hidden: !instance?.shipments_count + }, + { + 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: orderCurrency + }) + } + ]; + + const bl: DetailsField[] = [ + { + type: 'link', + external: true, + name: 'link', + label: t`Link`, + copy: true, + hidden: !instance?.link + }, + { + type: 'text', + name: 'address', + label: t`Shipping Address`, + icon: 'address', + value_formatter: () => + instance?.address_detail ? ( + + ) : ( + {t`Not specified`} + ) + }, + { + type: 'text', + name: 'contact_detail.name', + label: t`Contact`, + icon: 'user', + copy: true, + hidden: !instance?.contact_detail?.name + }, + { + 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_label + }, + { + 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, + hidden: !instance?.creation_date + }, + { + 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', + hidden: !instance?.start_date, + copy: true + }, + { + type: 'date', + name: 'target_date', + label: t`Target Date`, + hidden: !instance?.target_date, + copy: true + }, + { + type: 'date', + name: 'shipment_date', + label: t`Completion Date`, + hidden: !instance?.shipment_date, + copy: true + }, + { + 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.salesorder, + model_id: instance?.pk + }); + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + + ); +}