2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +00:00
InvenTree/src/frontend/src/pages/part/CategoryDetail.tsx
Lavissa 0196dd2f60
[PUI/Feature] Integrate Part "Default Location" into UX (#5972)
* Add default parts to location page

* Fix name strings

* Add Stock Transfer modal

* Add ApiForm Table field

* temp

* Add stock transfer form to part, stock item and location

* All stock operations for Item, Part, and Location added (except order new)

* Add default_location category traversal, and initial PO Line Item Receive form

* .

* Remove debug values

* Added PO line receive form

* Add functionality to PO receive extra fields

* .

* Forgot to bump API version

* Add Category Default to details panel

* Fix stockItem query count

* Fix reviewed issues

* .

* .

* .

* Prevent root category from checking parent for default location
2024-03-15 12:06:18 +11:00

248 lines
6.3 KiB
TypeScript

import { t } from '@lingui/macro';
import { LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
import {
IconCategory,
IconDots,
IconInfoCircle,
IconListDetails,
IconSitemap
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
ActionDropdown,
EditItemAction
} from '../../components/items/ActionDropdown';
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 { UserRoles } from '../../enums/Roles';
import { partCategoryFields } from '../../forms/PartForms';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import ParametricPartTable from '../../tables/part/ParametricPartTable';
import { PartCategoryTable } from '../../tables/part/PartCategoryTable';
import { PartListTable } from '../../tables/part/PartTable';
/**
* Detail view for a single PartCategory instance.
*
* Note: If no category ID is supplied, this acts as the top-level part category page
*/
export default function CategoryDetail({}: {}) {
const { id: _id } = useParams();
const id = useMemo(
() => (!isNaN(parseInt(_id || '')) ? _id : undefined),
[_id]
);
const user = useUserState();
const [treeOpen, setTreeOpen] = useState(false);
const {
instance: category,
refreshInstance,
instanceQuery
} = useInstance({
endpoint: ApiEndpoints.category_list,
hasPrimaryKey: true,
pk: id,
params: {
path_detail: true
}
});
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'
},
{
type: 'link',
name: 'parent_default_location',
label: t`Parent default location`,
model: ModelType.stocklocation,
hidden: !category.parent_default_location || category.default_location
},
{
type: 'link',
name: 'default_location',
label: t`Default location`,
model: ModelType.stocklocation,
hidden: !category.default_location
}
];
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 editCategory = useEditApiFormModal({
url: ApiEndpoints.category_list,
pk: id,
title: t`Edit Part Category`,
fields: partCategoryFields({}),
onFormSuccess: refreshInstance
});
const categoryActions = useMemo(() => {
return [
<ActionDropdown
key="category"
tooltip={t`Category Actions`}
icon={<IconDots />}
actions={[
EditItemAction({
hidden: !id || !user.hasChangeRole(UserRoles.part_category),
tooltip: t`Edit Part Category`,
onClick: () => editCategory.open()
})
]}
/>
];
}, [id, user]);
const categoryPanels: PanelType[] = useMemo(
() => [
{
name: 'details',
label: t`Category Details`,
icon: <IconInfoCircle />,
content: detailsPanel
// hidden: !category?.pk,
},
{
name: 'parts',
label: t`Parts`,
icon: <IconCategory />,
content: (
<PartListTable
props={{
params: {
category: id
}
}}
/>
)
},
{
name: 'subcategories',
label: t`Part Categories`,
icon: <IconSitemap />,
content: <PartCategoryTable parentId={id} />
},
{
name: 'parameters',
label: t`Parameters`,
icon: <IconListDetails />,
content: <ParametricPartTable categoryId={id} />
}
],
[category, id]
);
const breadcrumbs = useMemo(
() => [
{ name: t`Parts`, url: '/part' },
...(category.path ?? []).map((c: any) => ({
name: c.name,
url: `/part/category/${c.pk}`
}))
],
[category]
);
return (
<>
{editCategory.modal}
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PartCategoryTree
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedCategory={category?.pk}
/>
<PageDetail
title={t`Part Category`}
detail={<Text>{category.name ?? 'Top level'}</Text>}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
}}
actions={categoryActions}
/>
<PanelGroup pageKey="partcategory" panels={categoryPanels} />
</Stack>
</>
);
}