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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}