diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx index 8131348bf0..3dfb2eb19a 100644 --- a/src/frontend/src/components/previews/PreviewType.tsx +++ b/src/frontend/src/components/previews/PreviewType.tsx @@ -1,5 +1,7 @@ import { ModelType } from '@lib/enums/ModelType'; import type { ReactNode } from 'react'; +import { BuildOrderPreviewComponent } from './models/BuildOrderPreview'; +import { CompanyPreviewComponent } from './models/CompanyPreview'; import { ManufacturerPartPreviewComponent } from './models/ManufacturerPartPreview'; import { PartPreviewComponent } from './models/PartPreview'; import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview'; @@ -43,6 +45,10 @@ export function getPreviewComponentForModel({ return SupplierPartPreviewComponent({ instance, modelId }); case ModelType.manufacturerpart: return ManufacturerPartPreviewComponent({ instance, modelId }); + case ModelType.company: + return CompanyPreviewComponent({ instance, modelId }); + case ModelType.build: + return BuildOrderPreviewComponent({ instance, modelId }); default: return null; } diff --git a/src/frontend/src/components/previews/models/BuildOrderPreview.tsx b/src/frontend/src/components/previews/models/BuildOrderPreview.tsx new file mode 100644 index 0000000000..5735da0c80 --- /dev/null +++ b/src/frontend/src/components/previews/models/BuildOrderPreview.tsx @@ -0,0 +1,25 @@ +import { t } from '@lingui/core/macro'; +import { BuildOrderDetailsPanel } from '../../../pages/build/BuildOrderDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function BuildOrderPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const part = instance?.part_detail?.full_name ?? instance?.part_detail?.name; + const ref = instance?.reference ?? `#${modelId}`; + + let title = `${t`Build Order`} ${ref}`; + + if (part) { + title += ` (${part})`; + } + + return { + title, + preview: + }; +} diff --git a/src/frontend/src/components/previews/models/CompanyPreview.tsx b/src/frontend/src/components/previews/models/CompanyPreview.tsx new file mode 100644 index 0000000000..772ad33944 --- /dev/null +++ b/src/frontend/src/components/previews/models/CompanyPreview.tsx @@ -0,0 +1,18 @@ +import { t } from '@lingui/core/macro'; +import { CompanyDetailsPanel } from '../../../pages/company/CompanyDetailsPanel'; +import type { PreviewType } from '../PreviewType'; + +export function CompanyPreviewComponent({ + instance, + modelId +}: Readonly<{ + instance: any; + modelId: number; +}>): PreviewType { + const name = instance?.name ?? `#${modelId}`; + + return { + title: `${t`Company`} - ${name}`, + preview: + }; +} diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index 85689fe3cb..bd11686939 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { Alert, Grid, Skeleton, Stack, Text } from '@mantine/core'; +import { Alert, Skeleton, Stack, Text } from '@mantine/core'; import { IconChecklist, IconCircleCheck, @@ -21,19 +21,12 @@ import { ModelType } from '@lib/enums/ModelType'; import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; import { getDetailUrl } from '@lib/functions/Navigation'; -import { TagsList } from '@lib/index'; import type { ApiFormFieldSet } from '@lib/types/Forms'; 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, @@ -66,6 +59,7 @@ import BuildOutputTable from '../../tables/build/BuildOutputTable'; import PartTestResultTable from '../../tables/part/PartTestResultTable'; import { PurchaseOrderTable } from '../../tables/purchasing/PurchaseOrderTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; +import { BuildOrderDetailsPanel } from './BuildOrderDetailsPanel'; function NoItems() { return ( @@ -235,242 +229,19 @@ export default function BuildDetail() { refetchOnMount: true }); - const { instance: partRequirements, instanceQuery: partRequirementsQuery } = - useInstance({ - endpoint: ApiEndpoints.part_requirements, - pk: build?.part, - hasPrimaryKey: true, - defaultValue: {} - }); - - const detailsPanel = useMemo(() => { - if (instanceQuery.isFetching) { - return ; - } - - const data = { - ...build, - can_build: partRequirements?.can_build ?? 0 - }; - - const tl: DetailsField[] = [ - { - type: 'link', - name: 'part', - label: t`Part`, - model: ModelType.part - }, - { - type: 'text', - name: 'part_detail.IPN', - icon: 'part', - label: t`IPN`, - hidden: !build.part_detail?.IPN, - copy: true - }, - { - type: 'string', - name: 'part_detail.revision', - icon: 'revision', - label: t`Revision`, - hidden: !build.part_detail?.revision, - copy: true - }, - { - type: 'status', - name: 'status', - label: t`Status`, - model: ModelType.build - }, - { - type: 'status', - name: 'status_custom_key', - label: t`Custom Status`, - model: ModelType.build, - icon: 'status', - hidden: - !build.status_custom_key || build.status_custom_key == build.status - }, - { - type: 'boolean', - name: 'external', - label: t`External`, - icon: 'manufacturers', - hidden: !build.external - }, - { - type: 'text', - name: 'reference', - label: t`Reference`, - copy: true - }, - { - type: 'text', - name: 'title', - label: t`Description`, - icon: 'description', - copy: true - }, - { - type: 'link', - name: 'parent', - icon: 'builds', - label: t`Parent Build`, - model_field: 'reference', - model: ModelType.build, - hidden: !build.parent - } - ]; - - const tr: DetailsField[] = [ - { - type: 'number', - name: 'quantity', - label: t`Build Quantity` - }, - { - type: 'number', - name: 'can_build', - unit: build.part_detail?.units, - label: t`Can Build`, - hidden: partRequirements?.can_build === undefined - }, - { - 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 - } - ]; - - const bl: DetailsField[] = [ - { - type: 'text', - name: 'issued_by', - label: t`Issued By`, - icon: 'user', - badge: 'user', - hidden: !build.issued_by - }, - { - type: 'text', - name: 'responsible', - label: t`Responsible`, - badge: 'owner', - hidden: !build.responsible - }, - { - type: 'text', - name: 'project_code_label', - label: t`Project Code`, - icon: 'reference', - copy: true, - hidden: !build.project_code - }, - { - type: 'link', - name: 'take_from', - icon: 'location', - model: ModelType.stocklocation, - label: t`Source Location`, - backup_value: t`Any location` - }, - { - type: 'link', - name: 'destination', - icon: 'location', - model: ModelType.stocklocation, - label: t`Destination Location`, - hidden: !build.destination - }, - { - type: 'text', - name: 'batch', - label: t`Batch Code`, - hidden: !build.batch, - copy: true - } - ]; - - const br: DetailsField[] = [ - { - type: 'date', - name: 'creation_date', - label: t`Created`, - icon: 'calendar', - copy: true, - hidden: !build.creation_date - }, - { - type: 'date', - name: 'start_date', - label: t`Start Date`, - icon: 'calendar', - copy: true, - hidden: !build.start_date - }, - { - type: 'date', - name: 'target_date', - label: t`Target Date`, - icon: 'calendar', - copy: true, - hidden: !build.target_date - }, - { - type: 'date', - name: 'completion_date', - label: t`Completed`, - icon: 'calendar', - copy: true, - hidden: !build.completion_date - } - ]; - - return ( - - - - - - - - - - - - ); - }, [build, instanceQuery, partRequirements, partRequirementsQuery]); - const buildPanels: PanelType[] = useMemo(() => { return [ { name: 'details', label: t`Build Details`, icon: , - content: detailsPanel + content: ( + + ) }, { name: 'line-items', @@ -600,7 +371,7 @@ export default function BuildDetail() { build, id, user, - partRequirements, + buildStatus, globalSettings, showChildBuilds, diff --git a/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx b/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx new file mode 100644 index 0000000000..4e09e1b59a --- /dev/null +++ b/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx @@ -0,0 +1,262 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack } 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 { useInstance } from '../../hooks/UseInstance'; + +export function BuildOrderDetailsPanel({ + instance, + allowImageEdit = false, + refreshInstance +}: Readonly<{ + instance: any; + allowImageEdit?: boolean; + refreshInstance?: () => void; +}>) { + const { instance: partRequirements } = useInstance({ + endpoint: ApiEndpoints.part_requirements, + pk: instance?.part, + hasPrimaryKey: true, + defaultValue: {} + }); + + const data = useMemo( + () => ({ ...instance, can_build: partRequirements?.can_build ?? 0 }), + [instance, partRequirements] + ); + + const tl: DetailsField[] = [ + { + type: 'link', + name: 'part', + label: t`Part`, + model: ModelType.part + }, + { + type: 'text', + name: 'part_detail.IPN', + icon: 'part', + label: t`IPN`, + hidden: !instance?.part_detail?.IPN, + copy: true + }, + { + type: 'string', + name: 'part_detail.revision', + icon: 'revision', + label: t`Revision`, + hidden: !instance?.part_detail?.revision, + copy: true + }, + { + type: 'status', + name: 'status', + label: t`Status`, + model: ModelType.build + }, + { + type: 'status', + name: 'status_custom_key', + label: t`Custom Status`, + model: ModelType.build, + icon: 'status', + hidden: + !instance?.status_custom_key || + instance?.status_custom_key == instance?.status + }, + { + type: 'boolean', + name: 'external', + label: t`External`, + icon: 'manufacturers', + hidden: !instance?.external + }, + { + type: 'text', + name: 'reference', + label: t`Reference`, + copy: true + }, + { + type: 'text', + name: 'title', + label: t`Description`, + icon: 'description', + copy: true + }, + { + type: 'link', + name: 'parent', + icon: 'builds', + label: t`Parent Build`, + model_field: 'reference', + model: ModelType.build, + hidden: !instance?.parent + } + ]; + + const tr: DetailsField[] = [ + { + type: 'number', + name: 'quantity', + label: t`Build Quantity` + }, + { + type: 'number', + name: 'can_build', + unit: instance?.part_detail?.units, + label: t`Can Build`, + hidden: partRequirements?.can_build === undefined + }, + { + type: 'progressbar', + name: 'completed', + icon: 'progress', + total: instance?.quantity, + progress: instance?.completed, + label: t`Completed Outputs` + }, + { + type: 'link', + name: 'sales_order', + label: t`Sales Order`, + icon: 'sales_orders', + model: ModelType.salesorder, + model_field: 'reference', + hidden: !instance?.sales_order + } + ]; + + const bl: DetailsField[] = [ + { + type: 'text', + name: 'issued_by', + label: t`Issued By`, + icon: 'user', + badge: 'user', + hidden: !instance?.issued_by + }, + { + type: 'text', + name: 'responsible', + label: t`Responsible`, + badge: 'owner', + hidden: !instance?.responsible + }, + { + type: 'text', + name: 'project_code_label', + label: t`Project Code`, + icon: 'reference', + copy: true, + hidden: !instance?.project_code + }, + { + type: 'link', + name: 'take_from', + icon: 'location', + model: ModelType.stocklocation, + label: t`Source Location`, + backup_value: t`Any location` + }, + { + type: 'link', + name: 'destination', + icon: 'location', + model: ModelType.stocklocation, + label: t`Destination Location`, + hidden: !instance?.destination + }, + { + type: 'text', + name: 'batch', + label: t`Batch Code`, + hidden: !instance?.batch, + copy: true + } + ]; + + const br: DetailsField[] = [ + { + type: 'date', + name: 'creation_date', + label: t`Created`, + icon: 'calendar', + copy: true, + hidden: !instance?.creation_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: 'completion_date', + label: t`Completed`, + icon: 'calendar', + copy: true, + hidden: !instance?.completion_date + } + ]; + + const parametersTable = useParameterDetailsGrid({ + model_type: ModelType.build, + model_id: instance?.pk + }); + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx index 4a55122c77..c1501b6b6d 100644 --- a/src/frontend/src/pages/company/CompanyDetail.tsx +++ b/src/frontend/src/pages/company/CompanyDetail.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { Grid, Skeleton, Stack } from '@mantine/core'; +import { Skeleton, Stack } from '@mantine/core'; import { IconBuildingWarehouse, IconInfoCircle, @@ -17,18 +17,10 @@ import { useNavigate, useParams } from 'react-router-dom'; 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 { 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 { DeleteItemAction, EditItemAction, @@ -56,6 +48,7 @@ import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable'; import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable'; import { SalesOrderTable } from '../../tables/sales/SalesOrderTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; +import { CompanyDetailsPanel } from './CompanyDetailsPanel'; export type CompanyDetailProps = { title: string; @@ -85,100 +78,15 @@ export default function CompanyDetail(props: Readonly) { refetchOnMount: true }); - const detailsPanel = useMemo(() => { - if (instanceQuery.isFetching) { - return ; - } - - const tl: DetailsField[] = [ - { - type: 'text', - name: 'description', - label: t`Description`, - copy: true - }, - { - type: 'link', - name: 'website', - label: t`Website`, - external: true, - copy: true, - hidden: !company.website - }, - { - type: 'text', - name: 'phone', - label: t`Phone Number`, - copy: true, - hidden: !company.phone - }, - { - type: 'text', - name: 'email', - label: t`Email Address`, - copy: true, - hidden: !company.email - }, - { - type: 'text', - name: 'tax_id', - label: t`Tax ID`, - copy: true, - hidden: !company.tax_id - } - ]; - - const tr: DetailsField[] = [ - { - type: 'string', - name: 'currency', - label: t`Default Currency` - }, - { - type: 'boolean', - name: 'is_supplier', - label: t`Supplier`, - icon: 'suppliers' - }, - { - type: 'boolean', - name: 'is_manufacturer', - label: t`Manufacturer`, - icon: 'manufacturers' - }, - { - type: 'boolean', - name: 'is_customer', - label: t`Customer`, - icon: 'customers' - } - ]; - - return ( - - - - - - - - - - - - ); - }, [company, instanceQuery]); + const detailsPanel = instanceQuery.isFetching ? ( + + ) : ( + + ); const companyPanels: PanelType[] = useMemo(() => { return [ diff --git a/src/frontend/src/pages/company/CompanyDetailsPanel.tsx b/src/frontend/src/pages/company/CompanyDetailsPanel.tsx new file mode 100644 index 0000000000..b98d67515c --- /dev/null +++ b/src/frontend/src/pages/company/CompanyDetailsPanel.tsx @@ -0,0 +1,119 @@ +import { t } from '@lingui/core/macro'; +import { Grid, Skeleton, Stack } from '@mantine/core'; + +import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; +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'; + +export function CompanyDetailsPanel({ + instance, + allowImageEdit = false, + refreshInstance +}: Readonly<{ + instance: any; + allowImageEdit?: boolean; + refreshInstance?: () => void; +}>) { + const tl: DetailsField[] = [ + { + type: 'text', + name: 'description', + label: t`Description`, + copy: true + }, + { + type: 'link', + name: 'website', + label: t`Website`, + external: true, + copy: true, + hidden: !instance?.website + }, + { + type: 'text', + name: 'phone', + label: t`Phone Number`, + copy: true, + hidden: !instance?.phone + }, + { + type: 'text', + name: 'email', + label: t`Email Address`, + copy: true, + hidden: !instance?.email + }, + { + type: 'text', + name: 'tax_id', + label: t`Tax ID`, + copy: true, + hidden: !instance?.tax_id + } + ]; + + const tr: DetailsField[] = [ + { + type: 'string', + name: 'currency', + label: t`Default Currency` + }, + { + type: 'boolean', + name: 'is_supplier', + label: t`Supplier`, + icon: 'suppliers' + }, + { + type: 'boolean', + name: 'is_manufacturer', + label: t`Manufacturer`, + icon: 'manufacturers' + }, + { + type: 'boolean', + name: 'is_customer', + label: t`Customer`, + icon: 'customers' + } + ]; + + if (!instance?.pk) return ; + + return ( + + + + + + + + + + + + ); +} diff --git a/src/frontend/src/tables/company/CompanyTable.tsx b/src/frontend/src/tables/company/CompanyTable.tsx index a3ebc82422..ccd0cc61c0 100644 --- a/src/frontend/src/tables/company/CompanyTable.tsx +++ b/src/frontend/src/tables/company/CompanyTable.tsx @@ -8,7 +8,7 @@ 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 { navigateToLink } from '@lib/functions/Navigation'; +import { eventModified, navigateToLink } from '@lib/functions/Navigation'; import useTable from '@lib/hooks/UseTable'; import type { TableFilter } from '@lib/types/Filters'; import { companyFields } from '../../forms/CompanyForms'; @@ -16,6 +16,7 @@ import { useCreateApiFormModal, useEditApiFormModal } from '../../hooks/UseForm'; +import { usePreviewDrawerState } from '../../states/PreviewDrawerState'; import { useUserState } from '../../states/UserState'; import { BooleanColumn, @@ -49,6 +50,7 @@ export function CompanyTable({ const navigate = useNavigate(); const user = useUserState(); + const previewDrawer = usePreviewDrawerState(); const columns = useMemo(() => { return [ @@ -166,10 +168,16 @@ export function CompanyTable({ params: { ...params }, - onRowClick: (record: any, index: number, event: any) => { - if (record.pk) { - const base = path ?? 'company'; - navigateToLink(`/${base}/${record.pk}`, navigate, event); + onRowClick: (record: any, _index: number, event: any) => { + if (!record.pk) return; + if (eventModified(event as any)) { + navigateToLink( + `/${path ?? 'company'}/${record.pk}`, + navigate, + event + ); + } else { + previewDrawer.openPreview(ModelType.company, Number(record.pk)); } }, modelType: ModelType.company,