From ff7e4d4f3baed4fb8b8156137f860d674d922b0c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 30 Jun 2026 11:07:15 +0000 Subject: [PATCH] Updates --- .../src/components/calendar/OrderCalendar.tsx | 8 +- .../src/components/previews/AttributeGrid.tsx | 4 +- .../src/components/previews/PreviewDrawer.tsx | 27 ++- .../previews/models/PartPreview.tsx | 173 +++++++++++------- 4 files changed, 139 insertions(+), 73 deletions(-) diff --git a/src/frontend/src/components/calendar/OrderCalendar.tsx b/src/frontend/src/components/calendar/OrderCalendar.tsx index f855691dd4..c9d26f0ca2 100644 --- a/src/frontend/src/components/calendar/OrderCalendar.tsx +++ b/src/frontend/src/components/calendar/OrderCalendar.tsx @@ -7,7 +7,11 @@ import { ModelInformationDict } from '@lib/enums/ModelInformation'; import type { ModelType } from '@lib/enums/ModelType'; import type { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; -import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation'; +import { + eventModified, + getDetailUrl, + navigateToLink +} from '@lib/functions/Navigation'; import type { TableFilter } from '@lib/types/Filters'; import { t } from '@lingui/core/macro'; import { ActionIcon, Group, Text } from '@mantine/core'; @@ -171,7 +175,7 @@ export default function OrderCalendar({ const onClickOrder = (info: EventClickArg) => { if (!info.event.id) return; - if (info.jsEvent.ctrlKey || info.jsEvent.metaKey || info.jsEvent.shiftKey) { + if (eventModified(event as any)) { navigateToLink( getDetailUrl(model, info.event.id), navigate, diff --git a/src/frontend/src/components/previews/AttributeGrid.tsx b/src/frontend/src/components/previews/AttributeGrid.tsx index 0da3aeead5..93b7578712 100644 --- a/src/frontend/src/components/previews/AttributeGrid.tsx +++ b/src/frontend/src/components/previews/AttributeGrid.tsx @@ -28,10 +28,10 @@ export function AttributeGrid({ {valid.map((item) => ( - + {item.label} - {item.value} + {item.value} ))} diff --git a/src/frontend/src/components/previews/PreviewDrawer.tsx b/src/frontend/src/components/previews/PreviewDrawer.tsx index ebcb8ee58c..fe3c97e40c 100644 --- a/src/frontend/src/components/previews/PreviewDrawer.tsx +++ b/src/frontend/src/components/previews/PreviewDrawer.tsx @@ -1,8 +1,20 @@ -import { Anchor, Divider, Drawer, LoadingOverlay, Stack } from '@mantine/core'; +import { + ActionIcon, + Anchor, + Divider, + Drawer, + Group, + LoadingOverlay, + Stack, + Tooltip +} from '@mantine/core'; import { StylishText } from '@lib/components/StylishText'; import { ModelInformationDict } from '@lib/enums/ModelInformation'; +import { eventModified } from '@lib/functions/Navigation'; import { type ModelType, getDetailUrl, navigateToLink } from '@lib/index'; +import { t } from '@lingui/core/macro'; +import { IconArrowRight } from '@tabler/icons-react'; import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useInstance } from '../../hooks/UseInstance'; @@ -73,7 +85,9 @@ export default function PreviewDrawer({ (event: MouseEvent) => { if (!modelType || !id) return; - onClose(); + if (!eventModified(event as any)) { + onClose(); + } navigateToLink(getDetailUrl(modelType!, id!), navigate, event); }, [modelType, id, navigate] @@ -90,7 +104,14 @@ export default function PreviewDrawer({ href={getDetailUrl(modelType!, id, true)} onClick={(e) => clickTitle(e)} > - {previewComponent.title} + + + + + + + {previewComponent.title} + ) : ( {previewComponent.title} diff --git a/src/frontend/src/components/previews/models/PartPreview.tsx b/src/frontend/src/components/previews/models/PartPreview.tsx index 5bfc77d088..d253cac760 100644 --- a/src/frontend/src/components/previews/models/PartPreview.tsx +++ b/src/frontend/src/components/previews/models/PartPreview.tsx @@ -8,11 +8,12 @@ import { import { t } from '@lingui/core/macro'; import { Group, Stack, Text } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; import { useApi } from '../../../contexts/ApiContext'; -import { formatDate } from '../../../defaults/formatters'; +import { formatDate, formatPriceRange } from '../../../defaults/formatters'; import { useInstance } from '../../../hooks/UseInstance'; import { ApiImage } from '../../images/ApiImage'; -import { RenderPartCategory } from '../../render/Part'; +import { RenderRemoteInstance } from '../../render/Instance'; import { RenderUser } from '../../render/User'; import { AttributeGrid, type AttributeRow } from '../AttributeGrid'; import type { PreviewType } from '../PreviewType'; @@ -20,13 +21,11 @@ import type { PreviewType } from '../PreviewType'; function PartPreviewContent({ instance }: Readonly<{ instance: any }>) { const api = useApi(); - // Fetch part requirements const { instance: requirements } = useInstance({ endpoint: ApiEndpoints.part_requirements, pk: instance?.pk, hasPrimaryKey: true, disabled: !instance?.pk, - params: {}, defaultValue: {} }); @@ -45,49 +44,45 @@ function PartPreviewContent({ instance }: Readonly<{ instance: any }>) { .then((res) => res.data?.results ?? []) }); - const totalStock = requirements?.total_stock ?? instance?.total_in_stock ?? 0; - const availableStock = - requirements?.unallocated_stock ?? instance?.unallocated_stock ?? 0; - const minStock = instance?.minimum_stock ?? 0; - const ordering = requirements?.ordering ?? 0; - const isPurchaseable = instance?.purchaseable ?? false; + const units = instance?.units ? ` ${instance.units}` : ''; - const category = instance?.category_detail; + const stockItems: AttributeRow[] = useMemo(() => { + const totalStock = + requirements?.total_stock ?? instance?.total_in_stock ?? 0; + const availableStock = + requirements?.unallocated_stock ?? instance?.unallocated_stock ?? 0; + const minStock = instance?.minimum_stock ?? 0; + const ordering = requirements?.ordering ?? 0; + const canBuild = requirements?.can_build ?? 0; + const building = requirements?.building ?? 0; - const stockItems: AttributeRow[] = !instance?.virtual - ? [ - { - label: t`In Stock`, - value: - formatDecimal(totalStock) + - (instance?.units ? ` ${instance.units}` : '') - }, - availableStock !== totalStock - ? { - label: t`Available`, - value: - formatDecimal(availableStock) + - (instance?.units ? ` ${instance.units}` : '') - } - : null, - minStock > 0 - ? { - label: t`Minimum`, - value: - formatDecimal(minStock) + - (instance?.units ? ` ${instance.units}` : '') - } - : null, - isPurchaseable && ordering > 0 - ? { - label: t`On Order`, - value: - formatDecimal(ordering) + - (instance?.units ? ` ${instance.units}` : '') - } - : null - ].filter((x): x is NonNullable => x !== null) - : []; + return !instance?.virtual + ? [ + { + label: t`In Stock`, + value: formatDecimal(totalStock) + units + }, + availableStock !== totalStock + ? { + label: t`Available`, + value: formatDecimal(availableStock) + units + } + : null, + minStock > 0 + ? { label: t`Minimum`, value: formatDecimal(minStock) + units } + : null, + instance?.purchaseable && ordering > 0 + ? { label: t`On Order`, value: formatDecimal(ordering) + units } + : null, + instance?.assembly && canBuild >= 0 + ? { label: t`Can Build`, value: formatDecimal(canBuild) } + : null, + building > 0 + ? { label: t`In Production`, value: formatDecimal(building) } + : null + ].filter((x): x is NonNullable => x !== null) + : []; + }, [requirements, instance, units]); const parameterItems: AttributeRow[] = parameters.map((param: any) => ({ label: param.template_detail?.name, @@ -96,28 +91,64 @@ function PartPreviewContent({ instance }: Readonly<{ instance: any }>) { (param.template_detail?.units ? ` ${param.template_detail.units}` : '') })); - const infoItems: AttributeRow[] = [ - { - label: t`Category`, - value: instance?.category_detail - ? RenderPartCategory({ instance: instance.category_detail, link: true }) - : null - }, - { - label: t`Creation Date`, - value: instance?.creation_date ? formatDate(instance.creation_date) : null - }, - { - label: t`Created By`, - value: instance?.creation_user_detail - ? RenderUser({ instance: instance.creation_user_detail }) - : null - } - ]; + const infoItems: AttributeRow[] = useMemo(() => { + const priceRange = formatPriceRange( + instance?.pricing_min, + instance?.pricing_max + ); + + return [ + { + label: t`Category`, + value: instance?.category ? ( + + ) : null + }, + { + label: t`Revision Of`, + value: instance?.revision_of ? ( + + ) : null + }, + { + label: t`Variant Of`, + value: instance?.variant_of ? ( + + ) : null + }, + { + label: t`Price Range`, + value: + instance?.pricing_min != null || instance?.pricing_max != null + ? priceRange + : null + }, + { + label: t`Creation Date`, + value: instance?.creation_date + ? formatDate(instance.creation_date) + : null + }, + { + label: t`Created By`, + value: instance?.creation_user_detail + ? RenderUser({ instance: instance.creation_user_detail }) + : null + } + ]; + }, [instance]); return ( - {/* Headline: image + name / description / IPN / category */} ) { )} - {category && ( + {instance?.revision && ( + + + {t`Revision`}: + + + {instance.revision} + + + )} + {instance?.category_name && ( {t`Category`}: - {category.pathstring ?? category.name} + {instance?.category_name} )}