2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 20:45:44 +00:00

Part Category detail

This commit is contained in:
Oliver
2024-03-01 04:22:28 +00:00
parent ca34e97ab8
commit 0d8a863dab
3 changed files with 93 additions and 6 deletions

View File

@ -74,6 +74,7 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
'level', 'level',
'parent', 'parent',
'part_count', 'part_count',
'subcategories',
'pathstring', 'pathstring',
'path', 'path',
'starred', 'starred',
@ -99,13 +100,18 @@ class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer):
def annotate_queryset(queryset): def annotate_queryset(queryset):
"""Annotate extra information to the queryset.""" """Annotate extra information to the queryset."""
# Annotate the number of 'parts' which exist in each category (including subcategories!) # 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 return queryset
url = serializers.CharField(source='get_absolute_url', read_only=True) 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) level = serializers.IntegerField(read_only=True)

View File

@ -1,21 +1,24 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { LoadingOverlay, Stack, Text } from '@mantine/core'; import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
import { import {
IconCategory, IconCategory,
IconInfoCircle,
IconListDetails, IconListDetails,
IconSitemap IconSitemap
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import { PageDetail } from '../../components/nav/PageDetail'; import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { PartCategoryTree } from '../../components/nav/PartCategoryTree'; import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { useInstance } from '../../hooks/UseInstance'; import { useInstance } from '../../hooks/UseInstance';
import { DetailsField, DetailsTable } from '../../tables/Details';
import ParametricPartTable from '../../tables/part/ParametricPartTable'; import ParametricPartTable from '../../tables/part/ParametricPartTable';
import { PartCategoryTable } from '../../tables/part/PartCategoryTable'; import { PartCategoryTable } from '../../tables/part/PartCategoryTable';
import { PartParameterTable } from '../../tables/part/PartParameterTable';
import { PartListTable } from '../../tables/part/PartTable'; import { PartListTable } from '../../tables/part/PartTable';
/** /**
@ -45,8 +48,86 @@ export default function CategoryDetail({}: {}) {
} }
}); });
const detailsPanel = useMemo(() => {
if (id && instanceQuery.isFetching) {
return <Skeleton />;
}
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 (
<ItemDetailsGrid>
{id && category?.pk ? (
<DetailsTable item={category} fields={left} />
) : (
<Text>{t`Top level part category`}</Text>
)}
{id && category?.pk && <DetailsTable item={category} fields={right} />}
</ItemDetailsGrid>
);
}, [category, instanceQuery]);
const categoryPanels: PanelType[] = useMemo( const categoryPanels: PanelType[] = useMemo(
() => [ () => [
{
name: 'details',
label: t`Details`,
icon: <IconInfoCircle />,
content: detailsPanel
// hidden: !category?.pk,
},
{ {
name: 'parts', name: 'parts',
label: t`Parts`, label: t`Parts`,

View File

@ -104,12 +104,12 @@ export default function Stock() {
return ( return (
<ItemDetailsGrid> <ItemDetailsGrid>
{id && location ? ( {id && location?.pk ? (
<DetailsTable item={location} fields={left} /> <DetailsTable item={location} fields={left} />
) : ( ) : (
<Text>{t`Top level stock location`}</Text> <Text>{t`Top level stock location`}</Text>
)} )}
{id && location && <DetailsTable item={location} fields={right} />} {id && location?.pk && <DetailsTable item={location} fields={right} />}
</ItemDetailsGrid> </ItemDetailsGrid>
); );
}, [location, instanceQuery]); }, [location, instanceQuery]);