diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx index 3dfb2eb19a..c3b0c3a433 100644 --- a/src/frontend/src/components/previews/PreviewType.tsx +++ b/src/frontend/src/components/previews/PreviewType.tsx @@ -7,8 +7,10 @@ import { PartPreviewComponent } from './models/PartPreview'; import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview'; import { ReturnOrderPreviewComponent } from './models/ReturnOrderPreview'; import { SalesOrderPreviewComponent } from './models/SalesOrderPreview'; +import { SalesOrderShipmentPreviewComponent } from './models/SalesOrderShipmentPreview'; import { StockPreviewComponent } from './models/StockPreview'; import { SupplierPartPreviewComponent } from './models/SupplierPartPreview'; +import { TransferOrderPreviewComponent } from './models/TransferOrderPreview'; export interface PreviewType { preview: ReactNode; @@ -49,6 +51,10 @@ export function getPreviewComponentForModel({ return CompanyPreviewComponent({ instance, modelId }); case ModelType.build: return BuildOrderPreviewComponent({ instance, modelId }); + case ModelType.salesordershipment: + return SalesOrderShipmentPreviewComponent({ instance, modelId }); + case ModelType.transferorder: + return TransferOrderPreviewComponent({ instance, modelId }); default: return null; } diff --git a/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx b/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx new file mode 100644 index 0000000000..ce85b40946 --- /dev/null +++ b/src/frontend/src/components/previews/models/SalesOrderShipmentPreview.tsx @@ -0,0 +1,25 @@ +import { t } from '@lingui/core/macro'; +import { SalesOrderShipmentDetailsPanel } from '../../../pages/sales/SalesOrderShipmentDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function SalesOrderShipmentPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const order = instance?.order_detail?.reference; + const ref = instance?.reference ?? `#${modelId}`; + + let title = `${t`Shipment`} ${ref}`; + + if (order) { + title += ` (${order})`; + } + + return { + title, + preview: + }; +} diff --git a/src/frontend/src/components/previews/models/TransferOrderPreview.tsx b/src/frontend/src/components/previews/models/TransferOrderPreview.tsx new file mode 100644 index 0000000000..0d8458d51b --- /dev/null +++ b/src/frontend/src/components/previews/models/TransferOrderPreview.tsx @@ -0,0 +1,18 @@ +import { t } from '@lingui/core/macro'; +import { TransferOrderDetailsPanel } from '../../../pages/stock/TransferOrderDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function TransferOrderPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const ref = instance?.reference ?? `#${modelId}`; + + return { + title: `${t`Transfer Order`} ${ref}`, + preview: + }; +} diff --git a/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx b/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx index 83d956b817..44de746498 100644 --- a/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderShipmentDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { Grid, Skeleton, Stack, Text } from '@mantine/core'; +import { Stack } from '@mantine/core'; import { IconBookmark, IconCircleCheck, @@ -13,18 +13,11 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { getDetailUrl } from '@lib/functions/Navigation'; -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 DetailsBadge from '../../components/details/DetailsBadge'; -import { DetailsImage } from '../../components/details/DetailsImage'; -import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { BarcodeActionDropdown, CancelItemAction, @@ -37,9 +30,6 @@ 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 { RenderUser } from '../../components/render/User'; -import { formatDate } from '../../defaults/formatters'; import { useCheckShipmentForm, useCompleteShipmentForm, @@ -53,6 +43,7 @@ import { import { useInstance } from '../../hooks/UseInstance'; import { useUserState } from '../../states/UserState'; import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTable'; +import { SalesOrderShipmentDetailsPanel } from './SalesOrderShipmentDetailsPanel'; export default function SalesOrderShipmentDetail() { const { id } = useParams(); @@ -74,193 +65,21 @@ export default function SalesOrderShipmentDetail() { } }); - const { instance: customer, instanceQuery: customerQuery } = useInstance({ - endpoint: ApiEndpoints.company_list, - pk: shipment.order_detail?.customer, - hasPrimaryKey: true - }); - const isPending = useMemo(() => !shipment.shipment_date, [shipment]); - const isChecked = useMemo(() => !!shipment.checked_by, [shipment]); - const detailsPanel = useMemo(() => { - if (shipmentQuery.isFetching || customerQuery.isFetching) { - return ; - } - - const data: any = { - ...shipment, - customer: customer?.pk, - customer_name: customer?.name, - customer_reference: shipment.order_detail?.customer_reference - }; - - // Top Left: Order / customer information - const tl: DetailsField[] = [ - { - type: 'link', - model: ModelType.salesorder, - name: 'order', - label: t`Sales Order`, - icon: 'sales_orders', - model_field: 'reference' - }, - { - type: 'link', - name: 'customer', - icon: 'customers', - label: t`Customer`, - model: ModelType.company, - model_field: 'name', - hidden: !data.customer - }, - { - type: 'link', - external: true, - name: 'link', - label: t`Link`, - copy: true, - hidden: !shipment.link - } - ]; - - // Top right: Shipment information - const tr: DetailsField[] = [ - { - type: 'text', - name: 'customer_reference', - icon: 'serial', - label: t`Customer Reference`, - hidden: !data.customer_reference, - copy: true - }, - { - type: 'text', - name: 'reference', - icon: 'serial', - label: t`Shipment Reference`, - copy: true - }, - { - type: 'text', - name: 'tracking_number', - label: t`Tracking Number`, - icon: 'trackable', - value_formatter: () => shipment.tracking_number || '---', - copy: !!shipment.tracking_number - }, - { - type: 'text', - name: 'invoice_number', - label: t`Invoice Number`, - icon: 'serial', - value_formatter: () => shipment.invoice_number || '---', - copy: !!shipment.invoice_number - } - ]; - - const address: any = - shipment.shipment_address_detail || shipment.order_detail?.address_detail; - - const bl: DetailsField[] = [ - { - type: 'text', - name: 'address', - label: t`Shipping Address`, - icon: 'address', - value_formatter: () => - address ? ( - - ) : ( - {t`Not specified`} - ) - } - ]; - - const br: DetailsField[] = [ - { - type: 'text', - name: 'allocated_items', - icon: 'packages', - label: t`Allocated Items` - }, - { - type: 'text', - name: 'checked_by', - label: t`Checked By`, - icon: 'check', - value_formatter: () => - shipment.checked_by_detail ? ( - - ) : ( - {t`Not checked`} - ) - }, - { - type: 'text', - name: 'shipment_date', - label: t`Shipment Date`, - icon: 'calendar', - value_formatter: () => formatDate(shipment.shipment_date), - hidden: !shipment.shipment_date - }, - { - type: 'text', - name: 'delivery_date', - label: t`Delivery Date`, - icon: 'calendar', - value_formatter: () => formatDate(shipment.delivery_date), - hidden: !shipment.delivery_date - } - ]; - - return ( - <> - - - - - - - - - - - - - ); - }, [shipment, shipmentQuery, customer, customerQuery]); - const shipmentPanels: PanelType[] = useMemo(() => { return [ { name: 'detail', label: t`Shipment Details`, icon: , - content: detailsPanel + content: ( + + ) }, { name: 'items', @@ -291,7 +110,7 @@ export default function SalesOrderShipmentDetail() { has_note: !!shipment.notes }) ]; - }, [isPending, shipment, detailsPanel]); + }, [isPending, shipment]); const editShipmentFields = useSalesOrderShipmentFields({ pending: isPending, @@ -457,7 +276,7 @@ export default function SalesOrderShipmentDetail() { } ]} badges={shipmentBadges} - imageUrl={customer?.image} + imageUrl={shipment.order_detail?.customer_detail?.image} editAction={editShipment.open} editEnabled={user.hasChangePermission(ModelType.salesordershipment)} actions={shipmentActions} diff --git a/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx b/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx new file mode 100644 index 0000000000..5280abac94 --- /dev/null +++ b/src/frontend/src/pages/sales/SalesOrderShipmentDetailsPanel.tsx @@ -0,0 +1,205 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack, Text } from '@mantine/core'; +import { useMemo } from 'react'; + +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 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 { RenderUser } from '../../components/render/User'; +import { formatDate } from '../../defaults/formatters'; +import { useInstance } from '../../hooks/UseInstance'; + +export function SalesOrderShipmentDetailsPanel({ + instance, + refreshInstance +}: Readonly<{ + instance: any; + refreshInstance?: () => void; +}>) { + const { instance: customer } = useInstance({ + endpoint: ApiEndpoints.company_list, + pk: instance?.order_detail?.customer, + hasPrimaryKey: true + }); + + const data = useMemo( + () => ({ + ...instance, + customer: customer?.pk, + customer_name: customer?.name, + customer_reference: instance?.order_detail?.customer_reference + }), + [instance, customer] + ); + + const address = useMemo( + () => + instance?.shipment_address_detail || + instance?.order_detail?.address_detail, + [instance] + ); + + const tl: DetailsField[] = [ + { + type: 'link', + model: ModelType.salesorder, + name: 'order', + label: t`Sales Order`, + icon: 'sales_orders', + model_field: 'reference' + }, + { + type: 'link', + name: 'customer', + icon: 'customers', + label: t`Customer`, + model: ModelType.company, + model_field: 'name', + hidden: !data.customer + }, + { + type: 'link', + external: true, + name: 'link', + label: t`Link`, + copy: true, + hidden: !instance?.link + } + ]; + + const tr: DetailsField[] = [ + { + type: 'text', + name: 'customer_reference', + icon: 'serial', + label: t`Customer Reference`, + hidden: !data.customer_reference, + copy: true + }, + { + type: 'text', + name: 'reference', + icon: 'serial', + label: t`Shipment Reference`, + copy: true + }, + { + type: 'text', + name: 'tracking_number', + label: t`Tracking Number`, + icon: 'trackable', + value_formatter: () => instance?.tracking_number || '---', + copy: !!instance?.tracking_number + }, + { + type: 'text', + name: 'invoice_number', + label: t`Invoice Number`, + icon: 'serial', + value_formatter: () => instance?.invoice_number || '---', + copy: !!instance?.invoice_number + } + ]; + + const bl: DetailsField[] = [ + { + type: 'text', + name: 'address', + label: t`Shipping Address`, + icon: 'address', + value_formatter: () => + address ? ( + + ) : ( + {t`Not specified`} + ) + } + ]; + + const br: DetailsField[] = [ + { + type: 'text', + name: 'allocated_items', + icon: 'packages', + label: t`Allocated Items` + }, + { + type: 'text', + name: 'checked_by', + label: t`Checked By`, + icon: 'check', + value_formatter: () => + instance?.checked_by_detail ? ( + + ) : ( + {t`Not checked`} + ) + }, + { + type: 'text', + name: 'shipment_date', + label: t`Shipment Date`, + icon: 'calendar', + value_formatter: () => formatDate(instance?.shipment_date), + hidden: !instance?.shipment_date + }, + { + type: 'text', + name: 'delivery_date', + label: t`Delivery Date`, + icon: 'calendar', + value_formatter: () => formatDate(instance?.delivery_date), + hidden: !instance?.delivery_date + } + ]; + + const parametersTable = useParameterDetailsGrid({ + model_type: ModelType.salesordershipment, + model_id: instance?.pk + }); + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/frontend/src/pages/stock/TransferOrderDetail.tsx b/src/frontend/src/pages/stock/TransferOrderDetail.tsx index 41efe4e8b4..edc9dba94d 100644 --- a/src/frontend/src/pages/stock/TransferOrderDetail.tsx +++ b/src/frontend/src/pages/stock/TransferOrderDetail.tsx @@ -1,12 +1,12 @@ import { t } from '@lingui/core/macro'; -import { Grid, Skeleton, Stack } from '@mantine/core'; +import { Skeleton, Stack } from '@mantine/core'; import { type ReactNode, useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; -import { type PanelType, TagsList, apiUrl } from '@lib/index'; +import { type PanelType, apiUrl } from '@lib/index'; import { IconBookmark, IconInfoCircle, @@ -16,11 +16,6 @@ import { 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 { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { BarcodeActionDropdown, CancelItemAction, @@ -47,6 +42,7 @@ import { useGlobalSettingsState } from '../../states/SettingsStates'; import { useUserState } from '../../states/UserState'; import TransferOrderAllocationTable from '../../tables/stock/TransferOrderAllocationTable'; import TransferOrderLineItemTable from '../../tables/stock/TransferOrderLineItemTable'; +import { TransferOrderDetailsPanel } from './TransferOrderDetailsPanel'; export default function TransferOrderDetail() { const { id } = useParams(); @@ -93,172 +89,11 @@ export default function TransferOrderDetail() { ); }, [order, toStatus]); - const detailsPanel = useMemo(() => { - if (instanceQuery.isFetching) { - return ; - } - - const tl: DetailsField[] = [ - { - type: 'text', - name: 'reference', - label: t`Reference`, - copy: true - }, - { - type: 'link', - name: 'take_from', - icon: 'location', - label: t`Source Location`, - model: ModelType.stocklocation - }, - { - type: 'link', - name: 'destination', - icon: 'location', - label: t`Destination Location`, - model: ModelType.stocklocation - }, - { - type: 'text', - name: 'description', - label: t`Description`, - copy: true - }, - { - type: 'status', - name: 'status', - label: t`Status`, - model: ModelType.transferorder - }, - { - type: 'status', - name: 'status_custom_key', - label: t`Custom Status`, - model: ModelType.transferorder, - icon: 'status', - hidden: - !order.status_custom_key || order.status_custom_key == order.status - } - ]; - - const tr: DetailsField[] = [ - { - type: 'boolean', - name: 'consume', - icon: 'consume', - label: t`Consume Stock` - }, - { - type: 'text', - name: 'line_items', - label: t`Line Items`, - icon: 'list' - }, - { - type: 'progressbar', - name: 'completed', - icon: 'progress', - label: t`Completed Line Items`, - total: order.line_items, - progress: order.completed_lines - } - ]; - - const bl: DetailsField[] = [ - { - type: 'link', - external: true, - name: 'link', - label: t`Link`, - copy: true, - hidden: !order.link - }, - { - 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`, - icon: 'calendar', - 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', - copy: true, - hidden: !order.start_date - }, - { - type: 'date', - name: 'target_date', - label: t`Target Date`, - copy: true, - hidden: !order.target_date - }, - { - type: 'date', - name: 'complete_date', - icon: 'calendar_check', - label: t`Completion Date`, - copy: true, - hidden: !order.complete_date - } - ]; - - return ( - - - - {/* TODO: what image do we show for a Transfer Order? */} - {/* */} - - - - - - - - ); - }, [order, instanceQuery]); + const detailsPanel = instanceQuery.isFetching ? ( + + ) : ( + + ); const orderPanels: PanelType[] = useMemo(() => { return [ diff --git a/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx new file mode 100644 index 0000000000..a3388b7bc9 --- /dev/null +++ b/src/frontend/src/pages/stock/TransferOrderDetailsPanel.tsx @@ -0,0 +1,181 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack } from '@mantine/core'; + +import { ModelType } from '@lib/enums/ModelType'; +import { TagsList } from '@lib/index'; + +import { + type DetailsField, + DetailsTable +} from '../../components/details/Details'; +import { ItemDetailsGrid } from '../../components/details/ItemDetails'; +import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid'; + +export function TransferOrderDetailsPanel({ + instance +}: Readonly<{ + instance: any; +}>) { + const tl: DetailsField[] = [ + { + type: 'text', + name: 'reference', + label: t`Reference`, + copy: true + }, + { + type: 'link', + name: 'take_from', + icon: 'location', + label: t`Source Location`, + model: ModelType.stocklocation + }, + { + type: 'link', + name: 'destination', + icon: 'location', + label: t`Destination Location`, + model: ModelType.stocklocation + }, + { + type: 'text', + name: 'description', + label: t`Description`, + copy: true + }, + { + type: 'status', + name: 'status', + label: t`Status`, + model: ModelType.transferorder + }, + { + type: 'status', + name: 'status_custom_key', + label: t`Custom Status`, + model: ModelType.transferorder, + icon: 'status', + hidden: + !instance?.status_custom_key || + instance?.status_custom_key == instance?.status + } + ]; + + const tr: DetailsField[] = [ + { + type: 'boolean', + name: 'consume', + icon: 'consume', + label: t`Consume Stock` + }, + { + type: 'text', + name: 'line_items', + label: t`Line Items`, + icon: 'list' + }, + { + type: 'progressbar', + name: 'completed', + icon: 'progress', + label: t`Completed Line Items`, + total: instance?.line_items, + progress: instance?.completed_lines + } + ]; + + const bl: DetailsField[] = [ + { + type: 'link', + external: true, + name: 'link', + label: t`Link`, + copy: true, + hidden: !instance?.link + }, + { + 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`, + icon: 'calendar', + 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', + copy: true, + hidden: !instance?.start_date + }, + { + type: 'date', + name: 'target_date', + label: t`Target Date`, + copy: true, + hidden: !instance?.target_date + }, + { + type: 'date', + name: 'complete_date', + icon: 'calendar_check', + label: t`Completion Date`, + copy: true, + hidden: !instance?.complete_date + } + ]; + + const parametersTable = useParameterDetailsGrid({ + model_type: ModelType.transferorder, + model_id: instance?.pk + }); + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + ); +}