From 0d8a863dab13f331a4d154b9f48783601b5d6811 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 1 Mar 2024 04:22:28 +0000 Subject: [PATCH] Part Category detail --- InvenTree/part/serializers.py | 10 ++- .../src/pages/part/CategoryDetail.tsx | 85 ++++++++++++++++++- .../src/pages/stock/LocationDetail.tsx | 4 +- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index c09504853c..f8dc0b948e 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -74,6 +74,7 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer): 'level', 'parent', 'part_count', + 'subcategories', 'pathstring', 'path', 'starred', @@ -99,13 +100,18 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer): def annotate_queryset(queryset): """Annotate extra information to the queryset.""" # Annotate the number of 'parts' which exist in each category (including subcategories!) - queryset = queryset.annotate(part_count=part.filters.annotate_category_parts()) + queryset = queryset.annotate( + part_count=part.filters.annotate_category_parts(), + subcategories=part.filters.annotate_sub_categories(), + ) return queryset url = serializers.CharField(source='get_absolute_url', read_only=True) - part_count = serializers.IntegerField(read_only=True) + part_count = serializers.IntegerField(read_only=True, label=_('Parts')) + + subcategories = serializers.IntegerField(read_only=True, label=_('Subcategories')) level = serializers.IntegerField(read_only=True) diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx index 938fe0356e..c602ac5cd3 100644 --- a/src/frontend/src/pages/part/CategoryDetail.tsx +++ b/src/frontend/src/pages/part/CategoryDetail.tsx @@ -1,21 +1,24 @@ import { t } from '@lingui/macro'; -import { LoadingOverlay, Stack, Text } from '@mantine/core'; +import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core'; import { IconCategory, + IconInfoCircle, IconListDetails, IconSitemap } from '@tabler/icons-react'; import { useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { ItemDetailsGrid } from '../../components/details/ItemDetails'; import { PageDetail } from '../../components/nav/PageDetail'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; import { PartCategoryTree } from '../../components/nav/PartCategoryTree'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { ModelType } from '../../enums/ModelType'; import { useInstance } from '../../hooks/UseInstance'; +import { DetailsField, DetailsTable } from '../../tables/Details'; import ParametricPartTable from '../../tables/part/ParametricPartTable'; import { PartCategoryTable } from '../../tables/part/PartCategoryTable'; -import { PartParameterTable } from '../../tables/part/PartParameterTable'; import { PartListTable } from '../../tables/part/PartTable'; /** @@ -45,8 +48,86 @@ export default function CategoryDetail({}: {}) { } }); + const detailsPanel = useMemo(() => { + if (id && instanceQuery.isFetching) { + return ; + } + + let left: DetailsField[] = [ + { + type: 'text', + name: 'name', + label: t`Name`, + copy: true + }, + { + type: 'text', + name: 'pathstring', + label: t`Path`, + icon: 'sitemap', + copy: true, + hidden: !id + }, + { + type: 'text', + name: 'description', + label: t`Description`, + copy: true + }, + { + type: 'link', + name: 'parent', + model_field: 'name', + icon: 'location', + label: t`Parent Category`, + model: ModelType.partcategory, + hidden: !category?.parent + } + ]; + + let right: DetailsField[] = [ + { + type: 'text', + name: 'part_count', + label: t`Parts`, + icon: 'part' + }, + { + type: 'text', + name: 'subcategories', + label: t`Subcategories`, + icon: 'sitemap', + hidden: !category?.subcategories + }, + { + type: 'boolean', + name: 'structural', + label: t`Structural`, + icon: 'sitemap' + } + ]; + + return ( + + {id && category?.pk ? ( + + ) : ( + {t`Top level part category`} + )} + {id && category?.pk && } + + ); + }, [category, instanceQuery]); + const categoryPanels: PanelType[] = useMemo( () => [ + { + name: 'details', + label: t`Details`, + icon: , + content: detailsPanel + // hidden: !category?.pk, + }, { name: 'parts', label: t`Parts`, diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx index 15edf1ebf6..875664e894 100644 --- a/src/frontend/src/pages/stock/LocationDetail.tsx +++ b/src/frontend/src/pages/stock/LocationDetail.tsx @@ -104,12 +104,12 @@ export default function Stock() { return ( - {id && location ? ( + {id && location?.pk ? ( ) : ( {t`Top level stock location`} )} - {id && location && } + {id && location?.pk && } ); }, [location, instanceQuery]);