2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[PUI] Tweaks (#7673)

* Fix typo

* Filter fix for "supplier" field

* Fix supplier part actions

* Fix actions for manufacturer part

* Improve bulk-delete modal

- Prevents duplicate copies of modal being displayed

* Cleanup admin / settings panels

* Adjust playwright test
This commit is contained in:
Oliver 2024-07-18 10:38:41 +10:00 committed by GitHub
parent ca3f5c096c
commit 58807d575c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 79 additions and 77 deletions

View File

@ -41,7 +41,8 @@ export function useSupplierPartFields() {
}, },
supplier: { supplier: {
filters: { filters: {
active: true active: true,
is_supplier: true
} }
}, },
SKU: { SKU: {
@ -69,7 +70,12 @@ export function useManufacturerPartFields() {
return useMemo(() => { return useMemo(() => {
const fields: ApiFormFieldSet = { const fields: ApiFormFieldSet = {
part: {}, part: {},
manufacturer: {}, manufacturer: {
filters: {
active: true,
is_manufacturer: true
}
},
MPN: {}, MPN: {},
description: {}, description: {},
link: {} link: {}

View File

@ -167,7 +167,7 @@ export default function AdminCenter() {
}, },
{ {
name: 'location-types', name: 'location-types',
label: t`Location types`, label: t`Location Types`,
icon: <IconPackages />, icon: <IconPackages />,
content: <LocationTypesTable /> content: <LocationTypesTable />
}, },

View File

@ -167,12 +167,6 @@ export default function SystemSettings() {
/> />
) )
}, },
{
name: 'categories',
label: t`Part Categories`,
icon: <IconSitemap />,
content: <PlaceholderPanel />
},
{ {
name: 'parts', name: 'parts',
label: t`Parts`, label: t`Parts`,

View File

@ -9,7 +9,7 @@ import {
IconPaperclip IconPaperclip
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import AdminButton from '../../components/buttons/AdminButton'; import AdminButton from '../../components/buttons/AdminButton';
import { DetailsField, DetailsTable } from '../../components/details/Details'; import { DetailsField, DetailsTable } from '../../components/details/Details';
@ -29,8 +29,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
import { useManufacturerPartFields } from '../../forms/CompanyForms'; import { useManufacturerPartFields } from '../../forms/CompanyForms';
import { getDetailUrl } from '../../functions/urls';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal useEditApiFormModal
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance'; import { useInstance } from '../../hooks/UseInstance';
@ -43,6 +45,7 @@ import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
export default function ManufacturerPartDetail() { export default function ManufacturerPartDetail() {
const { id } = useParams(); const { id } = useParams();
const user = useUserState(); const user = useUserState();
const navigate = useNavigate();
const { const {
instance: manufacturerPart, instance: manufacturerPart,
@ -218,6 +221,15 @@ export default function ManufacturerPartDetail() {
modelType: ModelType.manufacturerpart modelType: ModelType.manufacturerpart
}); });
const deleteManufacturerPart = useDeleteApiFormModal({
url: ApiEndpoints.manufacturer_part_list,
pk: manufacturerPart?.pk,
title: t`Delete Manufacturer Part`,
onFormSuccess: () => {
navigate(getDetailUrl(ModelType.part, manufacturerPart.part));
}
});
const manufacturerPartActions = useMemo(() => { const manufacturerPartActions = useMemo(() => {
return [ return [
<AdminButton <AdminButton
@ -237,7 +249,8 @@ export default function ManufacturerPartDetail() {
onClick: () => editManufacturerPart.open() onClick: () => editManufacturerPart.open()
}), }),
DeleteItemAction({ DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order) hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => deleteManufacturerPart.open()
}) })
]} ]}
/> />
@ -259,6 +272,8 @@ export default function ManufacturerPartDetail() {
return ( return (
<> <>
{deleteManufacturerPart.modal}
{duplicateManufacturerPart.modal}
{editManufacturerPart.modal} {editManufacturerPart.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}> <InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<Stack gap="xs"> <Stack gap="xs">

View File

@ -9,7 +9,7 @@ import {
IconShoppingCart IconShoppingCart
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react'; import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import AdminButton from '../../components/buttons/AdminButton'; import AdminButton from '../../components/buttons/AdminButton';
import { DetailsField, DetailsTable } from '../../components/details/Details'; import { DetailsField, DetailsTable } from '../../components/details/Details';
@ -30,8 +30,10 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
import { useSupplierPartFields } from '../../forms/CompanyForms'; import { useSupplierPartFields } from '../../forms/CompanyForms';
import { getDetailUrl } from '../../functions/urls';
import { import {
useCreateApiFormModal, useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal useEditApiFormModal
} from '../../hooks/UseForm'; } from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance'; import { useInstance } from '../../hooks/UseInstance';
@ -46,6 +48,8 @@ export default function SupplierPartDetail() {
const user = useUserState(); const user = useUserState();
const navigate = useNavigate();
const { const {
instance: supplierPart, instance: supplierPart,
instanceQuery, instanceQuery,
@ -271,10 +275,11 @@ export default function SupplierPartDetail() {
}), }),
EditItemAction({ EditItemAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order), hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => editSuppliertPart.open() onClick: () => editSupplierPart.open()
}), }),
DeleteItemAction({ DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order) hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => deleteSupplierPart.open()
}) })
]} ]}
/> />
@ -283,7 +288,7 @@ export default function SupplierPartDetail() {
const supplierPartFields = useSupplierPartFields(); const supplierPartFields = useSupplierPartFields();
const editSuppliertPart = useEditApiFormModal({ const editSupplierPart = useEditApiFormModal({
url: ApiEndpoints.supplier_part_list, url: ApiEndpoints.supplier_part_list,
pk: supplierPart?.pk, pk: supplierPart?.pk,
title: t`Edit Supplier Part`, title: t`Edit Supplier Part`,
@ -291,6 +296,15 @@ export default function SupplierPartDetail() {
onFormSuccess: refreshInstance onFormSuccess: refreshInstance
}); });
const deleteSupplierPart = useDeleteApiFormModal({
url: ApiEndpoints.supplier_part_list,
pk: supplierPart?.pk,
title: t`Delete Supplier Part`,
onFormSuccess: () => {
navigate(getDetailUrl(ModelType.part, supplierPart.part));
}
});
const duplicateSupplierPart = useCreateApiFormModal({ const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list, url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`, title: t`Add Supplier Part`,
@ -327,7 +341,9 @@ export default function SupplierPartDetail() {
return ( return (
<> <>
{editSuppliertPart.modal} {deleteSupplierPart.modal}
{duplicateSupplierPart.modal}
{editSupplierPart.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}> <InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<Stack gap="xs"> <Stack gap="xs">
<PageDetail <PageDetail

View File

@ -45,6 +45,7 @@ import { cancelEvent } from '../functions/events';
import { extractAvailableFields, mapFields } from '../functions/forms'; import { extractAvailableFields, mapFields } from '../functions/forms';
import { navigateToLink } from '../functions/navigation'; import { navigateToLink } from '../functions/navigation';
import { getDetailUrl } from '../functions/urls'; import { getDetailUrl } from '../functions/urls';
import { useDeleteApiFormModal } from '../hooks/UseForm';
import { TableState } from '../hooks/UseTable'; import { TableState } from '../hooks/UseTable';
import { useLocalState } from '../states/LocalState'; import { useLocalState } from '../states/LocalState';
import { TableColumn } from './Column'; import { TableColumn } from './Column';
@ -495,66 +496,34 @@ export function InvenTreeTable<T = any>({
tableState.setRecords(data ?? []); tableState.setRecords(data ?? []);
}, [data]); }, [data]);
// Callback function to delete the selected records in the table const deleteRecords = useDeleteApiFormModal({
const deleteSelectedRecords = useCallback((ids: number[]) => { url: url,
if (ids.length == 0) { title: t`Delete Selected Items`,
// Ignore if no records are selected preFormContent: (
return;
}
modals.openConfirmModal({
title: t`Delete selected records`,
children: (
<Alert <Alert
color="red" color="red"
title={t`Are you sure you want to delete the selected records?`} title={t`Are you sure you want to delete the selected items?`}
> >
{t`This action cannot be undone!`} {t`This action cannot be undone!`}
</Alert> </Alert>
), ),
labels: { initialData: {
confirm: t`Delete`, items: tableState.selectedIds
cancel: t`Cancel`
}, },
confirmProps: { fields: {
color: 'red' items: {
}, hidden: true
onConfirm: () => {
api
.delete(url, {
data: {
items: ids
} }
}) },
.then((_response) => { onFormSuccess: () => {
// Refresh the table
refetch();
// Show notification
showNotification({
title: t`Deleted records`,
message: t`Records were deleted successfully`,
color: 'green'
});
})
.catch((_error) => {
console.warn(`Bulk delete operation failed at ${url}`);
showNotification({
title: t`Error`,
message: t`Failed to delete records`,
color: 'red'
});
})
.finally(() => {
tableState.clearSelectedRecords(); tableState.clearSelectedRecords();
tableState.refreshTable();
if (props.afterBulkDelete) { if (props.afterBulkDelete) {
props.afterBulkDelete(); props.afterBulkDelete();
} }
});
} }
}); });
}, []);
// Callback when a row is clicked // Callback when a row is clicked
const handleRowClick = useCallback( const handleRowClick = useCallback(
@ -587,6 +556,7 @@ export function InvenTreeTable<T = any>({
return ( return (
<> <>
{deleteRecords.modal}
{tableProps.enableFilters && (filters.length ?? 0) > 0 && ( {tableProps.enableFilters && (filters.length ?? 0) > 0 && (
<Boundary label="table-filter-drawer"> <Boundary label="table-filter-drawer">
<FilterSelectDrawer <FilterSelectDrawer
@ -623,7 +593,9 @@ export function InvenTreeTable<T = any>({
icon={<IconTrash />} icon={<IconTrash />}
color="red" color="red"
tooltip={t`Delete selected records`} tooltip={t`Delete selected records`}
onClick={() => deleteSelectedRecords(tableState.selectedIds)} onClick={() => {
deleteRecords.open();
}}
/> />
)} )}
{tableProps.tableActions?.map((group, idx) => ( {tableProps.tableActions?.map((group, idx) => (

View File

@ -28,7 +28,6 @@ test('PUI - Admin', async ({ page }) => {
await page.getByRole('tab', { name: 'Pricing' }).click(); await page.getByRole('tab', { name: 'Pricing' }).click();
await page.getByRole('tab', { name: 'Labels' }).click(); await page.getByRole('tab', { name: 'Labels' }).click();
await page.getByRole('tab', { name: 'Reporting' }).click(); await page.getByRole('tab', { name: 'Reporting' }).click();
await page.getByRole('tab', { name: 'Part Categories' }).click();
await page.getByRole('tab', { name: 'Stocktake' }).click(); await page.getByRole('tab', { name: 'Stocktake' }).click();
await page.getByRole('tab', { name: 'Build Orders' }).click(); await page.getByRole('tab', { name: 'Build Orders' }).click();