[PUI] Small updates (#6320)

* Ensure .ts files are generated

- "yarn run compile" before "yarn run dev"
- ensures that .ts locale files are all generated

* Implement "Add Part Category" button

* Create new stock location

* Rename customActionGroups to tableActions

* Rename customFilters to tableFilters

* Edit category from table

* Edit stock location from table

* Add some placeholder buttons

* More placeholders
This commit is contained in:
Oliver 2024-01-23 00:55:44 +11:00 committed by GitHub
parent ab921ccb31
commit d502d93380
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 287 additions and 71 deletions

View File

@ -20,8 +20,10 @@ export type ActionButtonProps = {
* Construct a simple action button with consistent styling * Construct a simple action button with consistent styling
*/ */
export function ActionButton(props: ActionButtonProps) { export function ActionButton(props: ActionButtonProps) {
const hidden = props.hidden ?? false;
return ( return (
!props.hidden && ( !hidden && (
<Tooltip <Tooltip
key={`tooltip-${props.key}`} key={`tooltip-${props.key}`}
disabled={!props.tooltip && !props.text} disabled={!props.tooltip && !props.text}

View File

@ -234,7 +234,6 @@ export function SearchDrawer({
// Re-fetch data whenever the search term is updated // Re-fetch data whenever the search term is updated
useEffect(() => { useEffect(() => {
// TODO: Implement search functionality
searchQuery.refetch(); searchQuery.refetch();
}, [searchText]); }, [searchText]);

View File

@ -1,3 +1,4 @@
import { t } from '@lingui/macro';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { RenderInlineModel } from './Instance'; import { RenderInlineModel } from './Instance';
@ -6,10 +7,13 @@ import { RenderInlineModel } from './Instance';
* Inline rendering of a single Part instance * Inline rendering of a single Part instance
*/ */
export function RenderPart({ instance }: { instance: any }): ReactNode { export function RenderPart({ instance }: { instance: any }): ReactNode {
const stock = t`Stock` + `: ${instance.in_stock}`;
return ( return (
<RenderInlineModel <RenderInlineModel
primary={instance.name} primary={instance.name}
secondary={instance.description} secondary={instance.description}
suffix={stock}
image={instance.thumnbnail || instance.image} image={instance.thumnbnail || instance.image}
/> />
); );

View File

@ -46,8 +46,8 @@ const defaultPageSize: number = 25;
* @param enableRefresh : boolean - Enable refresh actions * @param enableRefresh : boolean - Enable refresh actions
* @param pageSize : number - Number of records per page * @param pageSize : number - Number of records per page
* @param barcodeActions : any[] - List of barcode actions * @param barcodeActions : any[] - List of barcode actions
* @param customFilters : TableFilter[] - List of custom filters * @param tableFilters : TableFilter[] - List of custom filters
* @param customActionGroups : any[] - List of custom action groups * @param tableActions : any[] - List of custom action groups
* @param printingActions : any[] - List of printing actions * @param printingActions : any[] - List of printing actions
* @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format) * @param dataFormatter : (data: any) => any - Callback function to reformat data returned by server (if not in default format)
* @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions * @param rowActions : (record: any) => RowAction[] - Callback function to generate row actions
@ -66,8 +66,8 @@ export type InvenTreeTableProps<T = any> = {
enableRefresh?: boolean; enableRefresh?: boolean;
pageSize?: number; pageSize?: number;
barcodeActions?: any[]; barcodeActions?: any[];
customFilters?: TableFilter[]; tableFilters?: TableFilter[];
customActionGroups?: React.ReactNode[]; tableActions?: React.ReactNode[];
printingActions?: any[]; printingActions?: any[];
idAccessor?: string; idAccessor?: string;
dataFormatter?: (data: T) => any; dataFormatter?: (data: T) => any;
@ -91,8 +91,8 @@ const defaultInvenTreeTableProps: InvenTreeTableProps = {
defaultSortColumn: '', defaultSortColumn: '',
printingActions: [], printingActions: [],
barcodeActions: [], barcodeActions: [],
customFilters: [], tableFilters: [],
customActionGroups: [], tableActions: [],
idAccessor: 'pk', idAccessor: 'pk',
onRowClick: (record: any, index: number, event: any) => {} onRowClick: (record: any, index: number, event: any) => {}
}; };
@ -425,9 +425,9 @@ export function InvenTreeTable<T = any>({
return ( return (
<> <>
{tableProps.enableFilters && {tableProps.enableFilters &&
(tableProps.customFilters?.length ?? 0) > 0 && ( (tableProps.tableFilters?.length ?? 0) > 0 && (
<FilterSelectDrawer <FilterSelectDrawer
availableFilters={tableProps.customFilters ?? []} availableFilters={tableProps.tableFilters ?? []}
tableState={tableState} tableState={tableState}
opened={filtersVisible} opened={filtersVisible}
onClose={() => setFiltersVisible(false)} onClose={() => setFiltersVisible(false)}
@ -436,7 +436,7 @@ export function InvenTreeTable<T = any>({
<Stack spacing="sm"> <Stack spacing="sm">
<Group position="apart"> <Group position="apart">
<Group position="left" key="custom-actions" spacing={5}> <Group position="left" key="custom-actions" spacing={5}>
{tableProps.customActionGroups?.map((group, idx) => ( {tableProps.tableActions?.map((group, idx) => (
<Fragment key={idx}>{group}</Fragment> <Fragment key={idx}>{group}</Fragment>
))} ))}
{(tableProps.barcodeActions?.length ?? 0 > 0) && ( {(tableProps.barcodeActions?.length ?? 0 > 0) && (
@ -490,7 +490,7 @@ export function InvenTreeTable<T = any>({
/> />
)} )}
{tableProps.enableFilters && {tableProps.enableFilters &&
(tableProps.customFilters?.length ?? 0 > 0) && ( (tableProps.tableFilters?.length ?? 0 > 0) && (
<Indicator <Indicator
size="xs" size="xs"
label={tableState.activeFilters.length} label={tableState.activeFilters.length}

View File

@ -363,7 +363,7 @@ export function BomTable({
part_detail: true, part_detail: true,
sub_part_detail: true sub_part_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
onRowClick: (row) => navigate(`/part/${row.sub_part}`), onRowClick: (row) => navigate(`/part/${row.sub_part}`),
rowActions: rowActions rowActions: rowActions
}} }}

View File

@ -116,7 +116,7 @@ export function UsedInTable({
part_detail: true, part_detail: true,
sub_part_detail: true sub_part_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
onRowClick: (row) => navigate(`/part/${row.part}`) onRowClick: (row) => navigate(`/part/${row.part}`)
}} }}
/> />

View File

@ -155,7 +155,7 @@ export function BuildOrderTable({ params = {} }: { params?: any }) {
...params, ...params,
part_detail: true part_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
onRowClick: (row) => navigate(`/build/${row.pk}`) onRowClick: (row) => navigate(`/build/${row.pk}`)
}} }}
/> />

View File

@ -185,7 +185,7 @@ export function AddressTable({
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions, tableActions: tableActions,
params: { params: {
...params, ...params,
company: companyId company: companyId

View File

@ -133,7 +133,7 @@ export function ContactTable({
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions, tableActions: tableActions,
params: { params: {
...params, ...params,
company: companyId company: companyId

View File

@ -178,7 +178,7 @@ export function AttachmentTable({
}); });
} }
const customActionGroups: ReactNode[] = useMemo(() => { const tableActions: ReactNode[] = useMemo(() => {
let actions = []; let actions = [];
if (allowEdit) { if (allowEdit) {
@ -235,7 +235,7 @@ export function AttachmentTable({
props={{ props={{
noRecordsText: t`No attachments found`, noRecordsText: t`No attachments found`,
enableSelection: true, enableSelection: true,
customActionGroups: customActionGroups, tableActions: tableActions,
rowActions: allowEdit && allowDelete ? rowActions : undefined, rowActions: allowEdit && allowDelete ? rowActions : undefined,
params: { params: {
[model]: pk [model]: pk

View File

@ -1,23 +1,30 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints'; import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles';
import { partCategoryFields } from '../../../forms/PartForms';
import { openCreateApiForm, openEditApiForm } from '../../../functions/forms';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { YesNoButton } from '../../items/YesNoButton'; import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column'; import { TableColumn } from '../Column';
import { DescriptionColumn } from '../ColumnRenderers'; import { DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter'; import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { RowEditAction } from '../RowActions';
/** /**
* PartCategoryTable - Displays a table of part categories * PartCategoryTable - Displays a table of part categories
*/ */
export function PartCategoryTable({ params = {} }: { params?: any }) { export function PartCategoryTable({ parentId }: { parentId?: any }) {
const navigate = useNavigate(); const navigate = useNavigate();
const table = useTable('partcategory'); const table = useTable('partcategory');
const user = useUserState();
const tableColumns: TableColumn[] = useMemo(() => { const tableColumns: TableColumn[] = useMemo(() => {
return [ return [
@ -64,6 +71,62 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
]; ];
}, []); }, []);
const addCategory = useCallback(() => {
let fields = partCategoryFields({});
if (parentId) {
fields['parent'].value = parentId;
}
openCreateApiForm({
url: apiUrl(ApiPaths.category_list),
title: t`Add Part Category`,
fields: fields,
onFormSuccess(data: any) {
if (data.pk) {
navigate(`/part/category/${data.pk}`);
} else {
table.refreshTable();
}
}
});
}, [parentId]);
const tableActions = useMemo(() => {
let can_add = user.hasAddRole(UserRoles.part_category);
return [
<AddItemButton
tooltip={t`Add Part Category`}
onClick={addCategory}
disabled={!can_add}
/>
];
}, [user]);
const rowActions = useCallback(
(record: any) => {
let can_edit = user.hasChangeRole(UserRoles.part_category);
return [
RowEditAction({
hidden: !can_edit,
onClick: () => {
openEditApiForm({
url: ApiPaths.category_list,
pk: record.pk,
title: t`Edit Part Category`,
fields: partCategoryFields({}),
successMessage: t`Part category updated`,
onFormSuccess: table.refreshTable
});
}
})
];
},
[user]
);
return ( return (
<InvenTreeTable <InvenTreeTable
url={apiUrl(ApiPaths.category_list)} url={apiUrl(ApiPaths.category_list)}
@ -71,11 +134,12 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
columns={tableColumns} columns={tableColumns}
props={{ props={{
enableDownload: true, enableDownload: true,
enableSelection: true,
params: { params: {
...params parent: parentId ?? 'null'
}, },
customFilters: tableFilters, tableFilters: tableFilters,
tableActions: tableActions,
rowActions: rowActions,
onRowClick: (record, index, event) => { onRowClick: (record, index, event) => {
navigate(`/part/category/${record.pk}`); navigate(`/part/category/${record.pk}`);
} }

View File

@ -180,8 +180,8 @@ export function PartParameterTable({ partId }: { partId: any }) {
columns={tableColumns} columns={tableColumns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions, tableActions: tableActions,
customFilters: [ tableFilters: [
{ {
name: 'include_variants', name: 'include_variants',
label: t`Include Variants`, label: t`Include Variants`,

View File

@ -131,8 +131,8 @@ export default function PartParameterTemplateTable() {
columns={tableColumns} columns={tableColumns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customFilters: tableFilters, tableFilters: tableFilters,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
); );

View File

@ -275,7 +275,7 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
props={{ props={{
...props, ...props,
enableDownload: true, enableDownload: true,
customFilters: tableFilters, tableFilters: tableFilters,
params: { params: {
...props.params, ...props.params,
category_detail: true category_detail: true

View File

@ -140,8 +140,8 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
params: { params: {
part: partId part: partId
}, },
customFilters: tableFilters, tableFilters: tableFilters,
customActionGroups: tableActions, tableActions: tableActions,
rowActions: rowActions rowActions: rowActions
}} }}
/> />

View File

@ -37,7 +37,7 @@ export function PartVariantTable({ partId }: { partId: string }) {
<PartListTable <PartListTable
props={{ props={{
enableDownload: false, enableDownload: false,
customFilters: tableFilters, tableFilters: tableFilters,
params: { params: {
ancestor: partId ancestor: partId
} }

View File

@ -133,7 +133,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
category_detail: true category_detail: true
}, },
rowActions: rowActions, rowActions: rowActions,
customActionGroups: customActions tableActions: customActions
}} }}
/> />
); );

View File

@ -493,8 +493,8 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
}, },
rowActions: rowActions, rowActions: rowActions,
onRowClick: (plugin) => navigate(`${plugin.pk}/`), onRowClick: (plugin) => navigate(`${plugin.pk}/`),
customActionGroups: tableActions, tableActions: tableActions,
customFilters: [ tableFilters: [
{ {
name: 'active', name: 'active',
label: t`Active`, label: t`Active`,

View File

@ -5,9 +5,11 @@ import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles'; import { UserRoles } from '../../../enums/Roles';
import { useManufacturerPartFields } from '../../../forms/CompanyForms'; import { useManufacturerPartFields } from '../../../forms/CompanyForms';
import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms'; import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms';
import { notYetImplemented } from '../../../functions/notifications';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState'; import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail'; import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column'; import { TableColumn } from '../Column';
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers'; import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
@ -57,9 +59,22 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
]; ];
}, [params]); }, [params]);
const addManufacturerPart = useCallback(() => {
notYetImplemented();
}, []);
const tableActions = useMemo(() => { const tableActions = useMemo(() => {
// TODO: Custom actions let can_add =
return []; user.hasAddRole(UserRoles.purchase_order) &&
user.hasAddRole(UserRoles.part);
return [
<AddItemButton
tooltip={t`Add Manufacturer Part`}
onClick={addManufacturerPart}
hidden={!can_add}
/>
];
}, [user]); }, [user]);
const editManufacturerPartFields = useManufacturerPartFields(); const editManufacturerPartFields = useManufacturerPartFields();
@ -112,7 +127,7 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
manufacturer_detail: true manufacturer_detail: true
}, },
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
); );

View File

@ -260,7 +260,7 @@ export function PurchaseOrderLineItemTable({
part_detail: true part_detail: true
}, },
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
); );

View File

@ -1,11 +1,15 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints'; import { ApiPaths } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType'; import { ModelType } from '../../../enums/ModelType';
import { UserRoles } from '../../../enums/Roles';
import { notYetImplemented } from '../../../functions/notifications';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail'; import { Thumbnail } from '../../images/Thumbnail';
import { import {
CreationDateColumn, CreationDateColumn,
@ -33,6 +37,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
const navigate = useNavigate(); const navigate = useNavigate();
const table = useTable('purchase-order'); const table = useTable('purchase-order');
const user = useUserState();
const tableFilters: TableFilter[] = useMemo(() => { const tableFilters: TableFilter[] = useMemo(() => {
return [ return [
@ -94,6 +99,20 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
]; ];
}, []); }, []);
const addPurchaseOrder = useCallback(() => {
notYetImplemented();
}, []);
const tableActions = useMemo(() => {
return [
<AddItemButton
tooltip={t`Add Purchase Order`}
onClick={addPurchaseOrder}
hidden={!user.hasAddRole(UserRoles.purchase_order)}
/>
];
}, [user]);
return ( return (
<InvenTreeTable <InvenTreeTable
url={apiUrl(ApiPaths.purchase_order_list)} url={apiUrl(ApiPaths.purchase_order_list)}
@ -104,7 +123,8 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
...params, ...params,
supplier_detail: true supplier_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
tableActions: tableActions,
onRowClick: (row: any) => { onRowClick: (row: any) => {
if (row.pk) { if (row.pk) {
navigate(`/purchasing/purchase-order/${row.pk}`); navigate(`/purchasing/purchase-order/${row.pk}`);

View File

@ -229,7 +229,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
manufacturer_detail: true manufacturer_detail: true
}, },
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
</> </>

View File

@ -1,11 +1,15 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints'; import { ApiPaths } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType'; import { ModelType } from '../../../enums/ModelType';
import { UserRoles } from '../../../enums/Roles';
import { notYetImplemented } from '../../../functions/notifications';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail'; import { Thumbnail } from '../../images/Thumbnail';
import { import {
CreationDateColumn, CreationDateColumn,
@ -27,6 +31,7 @@ import { InvenTreeTable } from '../InvenTreeTable';
export function ReturnOrderTable({ params }: { params?: any }) { export function ReturnOrderTable({ params }: { params?: any }) {
const table = useTable('return-orders'); const table = useTable('return-orders');
const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
@ -90,6 +95,20 @@ export function ReturnOrderTable({ params }: { params?: any }) {
]; ];
}, []); }, []);
const addReturnOrder = useCallback(() => {
notYetImplemented();
}, []);
const tableActions = useMemo(() => {
return [
<AddItemButton
tooltip={t`Add Return Order`}
onClick={addReturnOrder}
hidden={!user.hasAddRole(UserRoles.sales_order)}
/>
];
}, [user]);
return ( return (
<InvenTreeTable <InvenTreeTable
url={apiUrl(ApiPaths.return_order_list)} url={apiUrl(ApiPaths.return_order_list)}
@ -100,7 +119,8 @@ export function ReturnOrderTable({ params }: { params?: any }) {
...params, ...params,
customer_detail: true customer_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
tableActions: tableActions,
onRowClick: (row: any) => { onRowClick: (row: any) => {
if (row.pk) { if (row.pk) {
navigate(`/sales/return-order/${row.pk}/`); navigate(`/sales/return-order/${row.pk}/`);

View File

@ -1,11 +1,15 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints'; import { ApiPaths } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType'; import { ModelType } from '../../../enums/ModelType';
import { UserRoles } from '../../../enums/Roles';
import { notYetImplemented } from '../../../functions/notifications';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail'; import { Thumbnail } from '../../images/Thumbnail';
import { import {
CreationDateColumn, CreationDateColumn,
@ -28,6 +32,7 @@ import { InvenTreeTable } from '../InvenTreeTable';
export function SalesOrderTable({ params }: { params?: any }) { export function SalesOrderTable({ params }: { params?: any }) {
const table = useTable('sales-order'); const table = useTable('sales-order');
const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
@ -91,6 +96,20 @@ export function SalesOrderTable({ params }: { params?: any }) {
]; ];
}, []); }, []);
const addSalesOrder = useCallback(() => {
notYetImplemented();
}, []);
const tableActions = useMemo(() => {
return [
<AddItemButton
tooltip={t`Add Sales Order`}
onClick={addSalesOrder}
hidden={!user.hasAddRole(UserRoles.sales_order)}
/>
];
}, [user]);
return ( return (
<InvenTreeTable <InvenTreeTable
url={apiUrl(ApiPaths.sales_order_list)} url={apiUrl(ApiPaths.sales_order_list)}
@ -101,7 +120,8 @@ export function SalesOrderTable({ params }: { params?: any }) {
...params, ...params,
customer_detail: true customer_detail: true
}, },
customFilters: tableFilters, tableFilters: tableFilters,
tableActions: tableActions,
onRowClick: (row: any) => { onRowClick: (row: any) => {
if (row.pk) { if (row.pk) {
navigate(`/sales/sales-order/${row.pk}/`); navigate(`/sales/sales-order/${row.pk}/`);

View File

@ -66,7 +66,7 @@ export function CurrencyTable() {
tableState={table} tableState={table}
columns={columns} columns={columns}
props={{ props={{
customActionGroups: tableActions, tableActions: tableActions,
dataFormatter: (data) => { dataFormatter: (data) => {
let rates = data?.exchange_rates ?? {}; let rates = data?.exchange_rates ?? {};

View File

@ -117,7 +117,7 @@ export default function CustomUnitsTable() {
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
); );

View File

@ -166,7 +166,7 @@ export function GroupTable() {
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions, tableActions: tableActions,
onRowClick: (record) => openDetailDrawer(record.pk) onRowClick: (record) => openDetailDrawer(record.pk)
}} }}
/> />

View File

@ -106,7 +106,7 @@ export default function ProjectCodeTable() {
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions tableActions: tableActions
}} }}
/> />
); );

View File

@ -261,7 +261,7 @@ export function UserTable() {
columns={columns} columns={columns}
props={{ props={{
rowActions: rowActions, rowActions: rowActions,
customActionGroups: tableActions, tableActions: tableActions,
onRowClick: (record) => openDetailDrawer(record.pk) onRowClick: (record) => openDetailDrawer(record.pk)
}} }}
/> />

View File

@ -344,7 +344,7 @@ export function StockItemTable({ params = {} }: { params?: any }) {
props={{ props={{
enableDownload: true, enableDownload: true,
enableSelection: true, enableSelection: true,
customFilters: tableFilters, tableFilters: tableFilters,
onRowClick: (record) => navigate(`/stock/item/${record.pk}`), onRowClick: (record) => navigate(`/stock/item/${record.pk}`),
params: { params: {
...params, ...params,

View File

@ -1,21 +1,28 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ApiPaths } from '../../../enums/ApiEndpoints'; import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles';
import { stockLocationFields } from '../../../forms/StockForms';
import { openCreateApiForm, openEditApiForm } from '../../../functions/forms';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { YesNoButton } from '../../items/YesNoButton'; import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column'; import { TableColumn } from '../Column';
import { DescriptionColumn } from '../ColumnRenderers'; import { DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter'; import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { RowEditAction } from '../RowActions';
/** /**
* Stock location table * Stock location table
*/ */
export function StockLocationTable({ params = {} }: { params?: any }) { export function StockLocationTable({ parentId }: { parentId?: any }) {
const table = useTable('stocklocation'); const table = useTable('stocklocation');
const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
@ -85,7 +92,63 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
render: (record: any) => record.location_type_detail?.name render: (record: any) => record.location_type_detail?.name
} }
]; ];
}, [params]); }, []);
const addLocation = useCallback(() => {
let fields = stockLocationFields({});
if (parentId) {
fields['parent'].value = parentId;
}
openCreateApiForm({
url: apiUrl(ApiPaths.stock_location_list),
title: t`Add Stock Location`,
fields: fields,
onFormSuccess(data: any) {
if (data.pk) {
navigate(`/stock/location/${data.pk}`);
} else {
table.refreshTable();
}
}
});
}, [parentId]);
const tableActions = useMemo(() => {
let can_add = user.hasAddRole(UserRoles.stock_location);
return [
<AddItemButton
tooltip={t`Add Stock Location`}
onClick={addLocation}
disabled={!can_add}
/>
];
}, [user]);
const rowActions = useCallback(
(record: any) => {
let can_edit = user.hasChangeRole(UserRoles.stock_location);
return [
RowEditAction({
hidden: !can_edit,
onClick: () => {
openEditApiForm({
url: ApiPaths.stock_location_list,
pk: record.pk,
title: t`Edit Stock Location`,
fields: stockLocationFields({}),
successMessage: t`Stock location updated`,
onFormSuccess: table.refreshTable
});
}
})
];
},
[user]
);
return ( return (
<InvenTreeTable <InvenTreeTable
@ -94,8 +157,12 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
columns={tableColumns} columns={tableColumns}
props={{ props={{
enableDownload: true, enableDownload: true,
params: params, params: {
customFilters: tableFilters, parent: parentId ?? 'null'
},
tableFilters: tableFilters,
tableActions: tableActions,
rowActions: rowActions,
onRowClick: (record) => { onRowClick: (record) => {
navigate(`/stock/location/${record.pk}`); navigate(`/stock/location/${record.pk}`);
} }

View File

@ -133,3 +133,19 @@ export function useEditStockItem({
onFormSuccess: callback onFormSuccess: callback
}); });
} }
export function stockLocationFields({}: {}): ApiFormFieldSet {
let fields: ApiFormFieldSet = {
parent: {
description: t`Parent stock location`,
required: false
},
name: {},
description: {},
structural: {},
external: {},
location_type: {}
};
return fields;
}

View File

@ -64,13 +64,7 @@ export default function CategoryDetail({}: {}) {
name: 'subcategories', name: 'subcategories',
label: t`Part Categories`, label: t`Part Categories`,
icon: <IconSitemap />, icon: <IconSitemap />,
content: ( content: <PartCategoryTable parentId={id} />
<PartCategoryTable
params={{
parent: id
}}
/>
)
}, },
{ {
name: 'parameters', name: 'parameters',

View File

@ -53,13 +53,7 @@ export default function Stock() {
name: 'sublocations', name: 'sublocations',
label: t`Stock Locations`, label: t`Stock Locations`,
icon: <IconSitemap />, icon: <IconSitemap />,
content: ( content: <StockLocationTable parentId={id} />
<StockLocationTable
params={{
parent: id
}}
/>
)
} }
]; ];
}, [location, id]); }, [location, id]);

View File

@ -978,6 +978,7 @@ def frontend_dev(c):
c: Context variable c: Context variable
""" """
print('Starting frontend development server') print('Starting frontend development server')
yarn(c, 'yarn run compile')
yarn(c, 'yarn run dev') yarn(c, 'yarn run dev')