2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-02 03:30:54 +00:00

[UI] Query Improvements (#9791)

* Fix for stockOperationModal

* Re-check when opened status changes

* rename stockOperationModal -> useStockOperationModal

* Fix enabled status of query

* Add option to specify modalId

* Track modal state when open / close

* Prevent generators from running until forms are open

* Prevent double loading of tables

* Fix useQuery

* Fix queryKey

* Revert API change
This commit is contained in:
Oliver
2025-06-17 22:16:19 +10:00
committed by GitHub
parent 21d44d0039
commit 92667876fe
19 changed files with 222 additions and 55 deletions

View File

@ -174,6 +174,7 @@ export interface ApiFormProps {
*/ */
export interface ApiFormModalProps extends ApiFormProps { export interface ApiFormModalProps extends ApiFormProps {
title: string; title: string;
modalId?: string;
cancelText?: string; cancelText?: string;
cancelColor?: string; cancelColor?: string;
onClose?: () => void; onClose?: () => void;

View File

@ -1,6 +1,7 @@
import type { UiSizeType } from './Core'; import type { UiSizeType } from './Core';
export interface UseModalProps { export interface UseModalProps {
id: string;
title: string; title: string;
children: React.ReactElement; children: React.ReactElement;
size?: UiSizeType; size?: UiSizeType;

View File

@ -155,7 +155,7 @@ export function TableField({
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{value.length > 0 ? ( {(value?.length ?? 0) > 0 ? (
value.map((item: any, idx: number) => { value.map((item: any, idx: number) => {
return ( return (
<TableFieldRow <TableFieldRow

View File

@ -36,9 +36,11 @@ import { PartColumn } from '../tables/ColumnRenderers';
* Field set for BuildOrder forms * Field set for BuildOrder forms
*/ */
export function useBuildOrderFields({ export function useBuildOrderFields({
create create,
modalId
}: { }: {
create: boolean; create: boolean;
modalId: string;
}): ApiFormFieldSet { }): ApiFormFieldSet {
const [destination, setDestination] = useState<number | null | undefined>( const [destination, setDestination] = useState<number | null | undefined>(
null null
@ -47,6 +49,7 @@ export function useBuildOrderFields({
const [batchCode, setBatchCode] = useState<string>(''); const [batchCode, setBatchCode] = useState<string>('');
const batchGenerator = useBatchCodeGenerator({ const batchGenerator = useBatchCodeGenerator({
modalId: modalId,
onGenerate: (value: any) => { onGenerate: (value: any) => {
setBatchCode((batch: any) => batch || value); setBatchCode((batch: any) => batch || value);
} }
@ -152,9 +155,11 @@ export function useBuildOrderFields({
} }
export function useBuildOrderOutputFields({ export function useBuildOrderOutputFields({
build build,
modalId
}: { }: {
build: any; build: any;
modalId: string;
}): ApiFormFieldSet { }): ApiFormFieldSet {
const trackable: boolean = useMemo(() => { const trackable: boolean = useMemo(() => {
return build.part_detail?.trackable ?? false; return build.part_detail?.trackable ?? false;
@ -176,12 +181,14 @@ export function useBuildOrderOutputFields({
}, [build]); }, [build]);
const serialGenerator = useSerialNumberGenerator({ const serialGenerator = useSerialNumberGenerator({
modalId: modalId,
initialQuery: { initialQuery: {
part: build.part || build.part_detail?.pk part: build.part || build.part_detail?.pk
} }
}); });
const batchGenerator = useBatchCodeGenerator({ const batchGenerator = useBatchCodeGenerator({
modalId: modalId,
initialQuery: { initialQuery: {
part: build.part || build.part_detail?.pk, part: build.part || build.part_detail?.pk,
quantity: build.quantity quantity: build.quantity

View File

@ -296,6 +296,7 @@ function LineItemFormRow({
// Batch code generator // Batch code generator
const batchCodeGenerator = useBatchCodeGenerator({ const batchCodeGenerator = useBatchCodeGenerator({
isEnabled: () => batchOpen,
onGenerate: (value: any) => { onGenerate: (value: any) => {
if (value) { if (value) {
props.changeFn(props.idx, 'batch_code', value); props.changeFn(props.idx, 'batch_code', value);
@ -305,6 +306,7 @@ function LineItemFormRow({
// Serial number generator // Serial number generator
const serialNumberGenerator = useSerialNumberGenerator({ const serialNumberGenerator = useSerialNumberGenerator({
isEnabled: () => batchOpen && trackable,
onGenerate: (value: any) => { onGenerate: (value: any) => {
if (value) { if (value) {
props.changeFn(props.idx, 'serial_numbers', value); props.changeFn(props.idx, 'serial_numbers', value);

View File

@ -65,10 +65,12 @@ import { StatusFilterOptions } from '../tables/Filter';
export function useStockFields({ export function useStockFields({
partId, partId,
stockItem, stockItem,
modalId,
create = false create = false
}: { }: {
partId?: number; partId?: number;
stockItem?: any; stockItem?: any;
modalId: string;
create: boolean; create: boolean;
}): ApiFormFieldSet { }): ApiFormFieldSet {
const globalSettings = useGlobalSettingsState(); const globalSettings = useGlobalSettingsState();
@ -81,12 +83,14 @@ export function useStockFields({
const [expiryDate, setExpiryDate] = useState<string | null>(null); const [expiryDate, setExpiryDate] = useState<string | null>(null);
const batchGenerator = useBatchCodeGenerator({ const batchGenerator = useBatchCodeGenerator({
modalId: modalId,
initialQuery: { initialQuery: {
part: partInstance?.pk || partId part: partInstance?.pk || partId
} }
}); });
const serialGenerator = useSerialNumberGenerator({ const serialGenerator = useSerialNumberGenerator({
modalId: modalId,
initialQuery: { initialQuery: {
part: partInstance?.pk || partId part: partInstance?.pk || partId
} }
@ -235,11 +239,15 @@ export function useStockFields({
* Launch a form to create a new StockItem instance * Launch a form to create a new StockItem instance
*/ */
export function useCreateStockItem() { export function useCreateStockItem() {
const fields = useStockFields({ create: true }); const fields = useStockFields({
create: true,
modalId: 'create-stock-item'
});
return useCreateApiFormModal({ return useCreateApiFormModal({
url: ApiEndpoints.stock_item_list, url: ApiEndpoints.stock_item_list,
fields: fields, fields: fields,
modalId: 'create-stock-item',
title: t`Add Stock Item` title: t`Add Stock Item`
}); });
} }
@ -318,12 +326,15 @@ export function useStockItemInstallFields({
*/ */
export function useStockItemSerializeFields({ export function useStockItemSerializeFields({
partId, partId,
trackable trackable,
modalId
}: { }: {
partId: number; partId: number;
trackable: boolean; trackable: boolean;
modalId: string;
}) { }) {
const serialGenerator = useSerialNumberGenerator({ const serialGenerator = useSerialNumberGenerator({
modalId: modalId,
initialQuery: { initialQuery: {
part: partId part: partId
} }
@ -1018,7 +1029,7 @@ type apiModalFunc = (props: ApiFormModalProps) => {
modal: JSX.Element; modal: JSX.Element;
}; };
function stockOperationModal({ function useStockOperationModal({
items, items,
pk, pk,
model, model,
@ -1061,25 +1072,29 @@ function stockOperationModal({
return query_params; return query_params;
}, [baseParams, filters, model, pk]); }, [baseParams, filters, model, pk]);
const [opened, setOpened] = useState<boolean>(false);
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['stockitems', model, pk, items, params], queryKey: ['stockitems', opened, model, pk, items, params],
queryFn: async () => { queryFn: async () => {
if (items) { if (items) {
// If a list of items is provided, use that directly
return Array.isArray(items) ? items : [items]; return Array.isArray(items) ? items : [items];
} }
if (!pk || !opened) {
return [];
}
const url = apiUrl(ApiEndpoints.stock_item_list); const url = apiUrl(ApiEndpoints.stock_item_list);
return api return api
.get(url, { .get(url, {
params: params params: params
}) })
.then((response) => { .then((response) => response.data ?? [])
if (response.status === 200) {
return response.data;
}
})
.catch(() => { .catch(() => {
return null; return [];
}); });
} }
}); });
@ -1095,7 +1110,9 @@ function stockOperationModal({
title: title, title: title,
size: '80%', size: '80%',
successMessage: successMessage, successMessage: successMessage,
onFormSuccess: () => refresh() onFormSuccess: () => refresh(),
onClose: () => setOpened(false),
onOpen: () => setOpened(true)
}); });
} }
@ -1108,7 +1125,7 @@ export type StockOperationProps = {
}; };
export function useAddStockItem(props: StockOperationProps) { export function useAddStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockAddFields, fieldGenerator: stockAddFields,
endpoint: ApiEndpoints.stock_add, endpoint: ApiEndpoints.stock_add,
@ -1118,7 +1135,7 @@ export function useAddStockItem(props: StockOperationProps) {
} }
export function useRemoveStockItem(props: StockOperationProps) { export function useRemoveStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockRemoveFields, fieldGenerator: stockRemoveFields,
endpoint: ApiEndpoints.stock_remove, endpoint: ApiEndpoints.stock_remove,
@ -1128,7 +1145,7 @@ export function useRemoveStockItem(props: StockOperationProps) {
} }
export function useTransferStockItem(props: StockOperationProps) { export function useTransferStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockTransferFields, fieldGenerator: stockTransferFields,
endpoint: ApiEndpoints.stock_transfer, endpoint: ApiEndpoints.stock_transfer,
@ -1138,7 +1155,7 @@ export function useTransferStockItem(props: StockOperationProps) {
} }
export function useCountStockItem(props: StockOperationProps) { export function useCountStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockCountFields, fieldGenerator: stockCountFields,
endpoint: ApiEndpoints.stock_count, endpoint: ApiEndpoints.stock_count,
@ -1148,7 +1165,7 @@ export function useCountStockItem(props: StockOperationProps) {
} }
export function useChangeStockStatus(props: StockOperationProps) { export function useChangeStockStatus(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockChangeStatusFields, fieldGenerator: stockChangeStatusFields,
endpoint: ApiEndpoints.stock_change_status, endpoint: ApiEndpoints.stock_change_status,
@ -1158,7 +1175,7 @@ export function useChangeStockStatus(props: StockOperationProps) {
} }
export function useMergeStockItem(props: StockOperationProps) { export function useMergeStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockMergeFields, fieldGenerator: stockMergeFields,
endpoint: ApiEndpoints.stock_merge, endpoint: ApiEndpoints.stock_merge,
@ -1182,7 +1199,7 @@ export function useAssignStockItem(props: StockOperationProps) {
return props.items?.filter((item) => item?.part_detail?.salable); return props.items?.filter((item) => item?.part_detail?.salable);
}, [props.items]); }, [props.items]);
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
items: items, items: items,
fieldGenerator: stockAssignFields, fieldGenerator: stockAssignFields,
@ -1193,7 +1210,7 @@ export function useAssignStockItem(props: StockOperationProps) {
} }
export function useDeleteStockItem(props: StockOperationProps) { export function useDeleteStockItem(props: StockOperationProps) {
return stockOperationModal({ return useStockOperationModal({
...props, ...props,
fieldGenerator: stockDeleteFields, fieldGenerator: stockDeleteFields,
endpoint: ApiEndpoints.stock_item_list, endpoint: ApiEndpoints.stock_item_list,

View File

@ -8,6 +8,7 @@ import type {
BulkEditApiFormModalProps BulkEditApiFormModalProps
} from '@lib/types/Forms'; } from '@lib/types/Forms';
import { OptionsApiForm } from '../components/forms/ApiForm'; import { OptionsApiForm } from '../components/forms/ApiForm';
import { useModalState } from '../states/ModalState';
import { useModal } from './UseModal'; import { useModal } from './UseModal';
/** /**
@ -17,6 +18,12 @@ export function useApiFormModal(props: ApiFormModalProps) {
const id = useId(); const id = useId();
const modalClose = useRef(() => {}); const modalClose = useRef(() => {});
const modalState = useModalState();
const modalId = useMemo(() => {
return props.modalId ?? id;
}, [props.modalId, id]);
const formProps = useMemo<ApiFormModalProps>( const formProps = useMemo<ApiFormModalProps>(
() => ({ () => ({
...props, ...props,
@ -44,15 +51,22 @@ export function useApiFormModal(props: ApiFormModalProps) {
); );
const modal = useModal({ const modal = useModal({
id: modalId,
title: formProps.title, title: formProps.title,
onOpen: formProps.onOpen, onOpen: () => {
onClose: formProps.onClose, modalState.setModalOpen(modalId, true);
formProps.onOpen?.();
},
onClose: () => {
modalState.setModalOpen(modalId, false);
formProps.onClose?.();
},
closeOnClickOutside: formProps.closeOnClickOutside, closeOnClickOutside: formProps.closeOnClickOutside,
size: props.size ?? 'xl', size: props.size ?? 'xl',
children: ( children: (
<Stack gap={'xs'}> <Stack gap={'xs'}>
<Divider /> <Divider />
<OptionsApiForm props={formProps} id={id} /> <OptionsApiForm props={formProps} id={modalId} />
</Stack> </Stack>
) )
}); });

View File

@ -5,12 +5,15 @@ import { useCallback, useState } from 'react';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { api } from '../App'; import { api } from '../App';
import { useModalState } from '../states/ModalState';
export type GeneratorProps = { export type GeneratorProps = {
endpoint: ApiEndpoints; endpoint: ApiEndpoints;
key: string; key: string;
initialQuery?: Record<string, any>; initialQuery?: Record<string, any>;
onGenerate?: (value: any) => void; onGenerate?: (value: any) => void;
isEnabled?: () => boolean;
modalId?: string;
}; };
export type GeneratorState = { export type GeneratorState = {
@ -25,6 +28,8 @@ export type GeneratorState = {
* Each update calls a new query to the API, and the result is stored in the state. * Each update calls a new query to the API, and the result is stored in the state.
*/ */
export function useGenerator(props: GeneratorProps): GeneratorState { export function useGenerator(props: GeneratorProps): GeneratorState {
const modalState = useModalState();
// Track the result // Track the result
const [result, setResult] = useState<any>(null); const [result, setResult] = useState<any>(null);
@ -34,6 +39,24 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
// Prevent rapid updates // Prevent rapid updates
const [debouncedQuery] = useDebouncedValue<Record<string, any>>(query, 100); const [debouncedQuery] = useDebouncedValue<Record<string, any>>(query, 100);
// Callback to determine if the function is enabled
const isEnabled = useCallback(() => {
if (props.isEnabled?.() == false) {
return false;
}
if (props.modalId && !modalState.isModalOpen(props.modalId)) {
return false;
}
return true;
}, [
modalState.isModalOpen,
modalState.openModals,
props.isEnabled,
props.modalId
]);
// Callback to update the generator query // Callback to update the generator query
const update = useCallback( const update = useCallback(
(params: Record<string, any>, overwrite?: boolean) => { (params: Record<string, any>, overwrite?: boolean) => {
@ -57,6 +80,7 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
props.key, props.key,
props.endpoint, props.endpoint,
props.initialQuery, props.initialQuery,
modalState.openModals,
debouncedQuery debouncedQuery
], ],
refetchOnMount: false, refetchOnMount: false,
@ -67,6 +91,11 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
...(props.initialQuery ?? {}) ...(props.initialQuery ?? {})
}; };
if (!isEnabled()) {
setResult(null);
return;
}
return api return api
.post(apiUrl(props.endpoint), generatorQuery) .post(apiUrl(props.endpoint), generatorQuery)
.then((response) => { .then((response) => {
@ -96,31 +125,43 @@ export function useGenerator(props: GeneratorProps): GeneratorState {
// Generate a batch code with provided data // Generate a batch code with provided data
export function useBatchCodeGenerator({ export function useBatchCodeGenerator({
initialQuery, initialQuery,
onGenerate onGenerate,
isEnabled,
modalId
}: { }: {
initialQuery?: Record<string, any>; initialQuery?: Record<string, any>;
onGenerate?: (value: any) => void; onGenerate?: (value: any) => void;
isEnabled?: () => boolean;
modalId?: string;
}): GeneratorState { }): GeneratorState {
return useGenerator({ return useGenerator({
endpoint: ApiEndpoints.generate_batch_code, endpoint: ApiEndpoints.generate_batch_code,
key: 'batch_code', key: 'batch_code',
initialQuery: initialQuery, initialQuery: initialQuery,
onGenerate: onGenerate onGenerate: onGenerate,
isEnabled: isEnabled,
modalId: modalId
}); });
} }
// Generate a serial number with provided data // Generate a serial number with provided data
export function useSerialNumberGenerator({ export function useSerialNumberGenerator({
initialQuery, initialQuery,
onGenerate onGenerate,
isEnabled,
modalId
}: { }: {
initialQuery?: Record<string, any>; initialQuery?: Record<string, any>;
onGenerate?: (value: any) => void; onGenerate?: (value: any) => void;
isEnabled?: () => boolean;
modalId?: string;
}): GeneratorState { }): GeneratorState {
return useGenerator({ return useGenerator({
endpoint: ApiEndpoints.generate_serial_number, endpoint: ApiEndpoints.generate_serial_number,
key: 'serial_number', key: 'serial_number',
initialQuery: initialQuery, initialQuery: initialQuery,
onGenerate: onGenerate onGenerate: onGenerate,
isEnabled: isEnabled,
modalId: modalId
}); });
} }

View File

@ -25,6 +25,7 @@ export function useModal(props: UseModalProps): UseModalReturn {
toggle, toggle,
modal: ( modal: (
<Modal <Modal
key={props.id}
opened={opened} opened={opened}
onClose={close} onClose={close}
closeOnClickOutside={props.closeOnClickOutside} closeOnClickOutside={props.closeOnClickOutside}

View File

@ -416,13 +416,17 @@ export default function BuildDetail() {
]; ];
}, [build, id, user, buildStatus, globalSettings]); }, [build, id, user, buildStatus, globalSettings]);
const buildOrderFields = useBuildOrderFields({ create: false }); const editBuildOrderFields = useBuildOrderFields({
create: false,
modalId: 'edit-build-order'
});
const editBuild = useEditApiFormModal({ const editBuild = useEditApiFormModal({
url: ApiEndpoints.build_order_list, url: ApiEndpoints.build_order_list,
pk: build.pk, pk: build.pk,
title: t`Edit Build Order`, title: t`Edit Build Order`,
fields: buildOrderFields, modalId: 'edit-build-order',
fields: editBuildOrderFields,
onFormSuccess: refreshInstance onFormSuccess: refreshInstance
}); });
@ -435,10 +439,16 @@ export default function BuildDetail() {
return data; return data;
}, [build]); }, [build]);
const duplicateBuildOrderFields = useBuildOrderFields({
create: false,
modalId: 'duplicate-build-order'
});
const duplicateBuild = useCreateApiFormModal({ const duplicateBuild = useCreateApiFormModal({
url: ApiEndpoints.build_order_list, url: ApiEndpoints.build_order_list,
title: t`Add Build Order`, title: t`Add Build Order`,
fields: buildOrderFields, modalId: 'duplicate-build-order',
fields: duplicateBuildOrderFields,
initialData: duplicateBuildOrderInitialData, initialData: duplicateBuildOrderInitialData,
follow: true, follow: true,
modelType: ModelType.build modelType: ModelType.build

View File

@ -1005,11 +1005,13 @@ export default function PartDetail() {
return ( return (
<> <>
{duplicatePart.modal}
{editPart.modal} {editPart.modal}
{deletePart.modal} {deletePart.modal}
{findBySerialNumber.modal} {duplicatePart.modal}
{countStockItems.modal}
{orderPartsWizard.wizard} {orderPartsWizard.wizard}
{findBySerialNumber.modal}
{transferStockItems.modal}
<InstanceDetail <InstanceDetail
status={requestStatus} status={requestStatus}
loading={instanceQuery.isFetching} loading={instanceQuery.isFetching}
@ -1096,8 +1098,6 @@ export default function PartDetail() {
model={ModelType.part} model={ModelType.part}
id={part.pk} id={part.pk}
/> />
{transferStockItems.modal}
{countStockItems.modal}
</Stack> </Stack>
</InstanceDetail> </InstanceDetail>
</> </>

View File

@ -640,22 +640,28 @@ export default function StockDetail() {
const editStockItemFields = useStockFields({ const editStockItemFields = useStockFields({
create: false, create: false,
stockItem: stockitem, stockItem: stockitem,
partId: stockitem.part partId: stockitem.part,
modalId: 'edit-stock-item'
}); });
const editStockItem = useEditApiFormModal({ const editStockItem = useEditApiFormModal({
url: ApiEndpoints.stock_item_list, url: ApiEndpoints.stock_item_list,
pk: stockitem.pk, pk: stockitem.pk,
title: t`Edit Stock Item`, title: t`Edit Stock Item`,
modalId: 'edit-stock-item',
fields: editStockItemFields, fields: editStockItemFields,
onFormSuccess: refreshInstance onFormSuccess: refreshInstance
}); });
const duplicateStockItemFields = useStockFields({ create: true }); const duplicateStockItemFields = useStockFields({
create: true,
modalId: 'duplicate-stock-item'
});
const duplicateStockItem = useCreateApiFormModal({ const duplicateStockItem = useCreateApiFormModal({
url: ApiEndpoints.stock_item_list, url: ApiEndpoints.stock_item_list,
title: t`Add Stock Item`, title: t`Add Stock Item`,
modalId: 'duplicate-stock-item',
fields: duplicateStockItemFields, fields: duplicateStockItemFields,
initialData: { initialData: {
...stockitem ...stockitem
@ -700,13 +706,15 @@ export default function StockDetail() {
const serializeStockFields = useStockItemSerializeFields({ const serializeStockFields = useStockItemSerializeFields({
partId: stockitem.part, partId: stockitem.part,
trackable: stockitem.part_detail?.trackable trackable: stockitem.part_detail?.trackable,
modalId: 'stock-item-serialize'
}); });
const serializeStockItem = useCreateApiFormModal({ const serializeStockItem = useCreateApiFormModal({
url: ApiEndpoints.stock_serialize, url: ApiEndpoints.stock_serialize,
pk: stockitem.pk, pk: stockitem.pk,
title: t`Serialize Stock Item`, title: t`Serialize Stock Item`,
modalId: 'stock-item-serialize',
fields: serializeStockFields, fields: serializeStockFields,
initialData: { initialData: {
quantity: stockitem.quantity, quantity: stockitem.quantity,

View File

@ -0,0 +1,28 @@
import { create } from 'zustand';
interface ModalStateProps {
openModals: Record<string, boolean>;
isModalOpen: (modalKey: string) => boolean;
setModalOpen: (modalKey: string, isOpen: boolean) => void;
}
/**
* Global state manager for determining modal visibility.
* Useful to share modal state (open / closed) between components.
*/
export const useModalState = create<ModalStateProps>()((set, get) => ({
openModals: {},
isModalOpen: (modalKey: string) => {
return get().openModals[modalKey] ?? false;
},
setModalOpen: (modalKey: string, isOpen: boolean) => {
set((state) => ({
openModals: {
...state.openModals,
[modalKey]: isOpen
}
}));
}
}));

View File

@ -176,10 +176,24 @@ export function InvenTreeTable<T extends Record<string, any>>({
); );
}, [props.tableFilters, fieldNames]); }, [props.tableFilters, fieldNames]);
// Build table properties based on provided props (and default props)
const tableProps: InvenTreeTableProps<T> = useMemo(() => {
return {
...defaultInvenTreeTableProps,
...props
};
}, [props]);
// Request OPTIONS data from the API, before we load the table // Request OPTIONS data from the API, before we load the table
const tableOptionQuery = useQuery({ const tableOptionQuery = useQuery({
enabled: !!url && !tableData, enabled: !!url && !tableData,
queryKey: ['options', url, cacheKey, props.enableColumnCaching], queryKey: [
'options',
url,
cacheKey,
tableProps.params,
props.enableColumnCaching
],
retry: 3, retry: 3,
refetchOnMount: true, refetchOnMount: true,
gcTime: 5000, gcTime: 5000,
@ -255,14 +269,6 @@ export function InvenTreeTable<T extends Record<string, any>>({
tableOptionQuery.refetch(); tableOptionQuery.refetch();
}, [cacheKey, url, props.params, props.enableColumnCaching]); }, [cacheKey, url, props.params, props.enableColumnCaching]);
// Build table properties based on provided props (and default props)
const tableProps: InvenTreeTableProps<T> = useMemo(() => {
return {
...defaultInvenTreeTableProps,
...props
};
}, [props]);
const enableSelection: boolean = useMemo(() => { const enableSelection: boolean = useMemo(() => {
return tableProps.enableSelection || tableProps.enableBulkDelete || false; return tableProps.enableSelection || tableProps.enableBulkDelete || false;
}, [tableProps]); }, [tableProps]);
@ -450,13 +456,17 @@ export function InvenTreeTable<T extends Record<string, any>>({
] ]
); );
const [sortingLoaded, setSortingLoaded] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
const tableKey: string = tableState.tableKey.split('-')[0]; const tableKey: string = tableState.tableKey.split('-')[0];
const sorting: DataTableSortStatus = getTableSorting(tableKey); const sorting: DataTableSortStatus = getTableSorting(tableKey);
if (sorting) { if (sorting && !!sorting.columnAccessor && !!sorting.direction) {
setSortStatus(sorting); setSortStatus(sorting);
} }
setSortingLoaded(true);
}, []); }, []);
// Return the ordering parameter // Return the ordering parameter
@ -492,6 +502,12 @@ export function InvenTreeTable<T extends Record<string, any>>({
const queryParams = getTableFilters(true); const queryParams = getTableFilters(true);
if (!url) { if (!url) {
// No URL supplied - do not load!
return [];
}
if (!sortingLoaded) {
// Sorting not yet loaded - do not load!
return []; return [];
} }
@ -560,6 +576,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
url, url,
tableState.page, tableState.page,
props.params, props.params,
sortingLoaded,
sortStatus.columnAccessor, sortStatus.columnAccessor,
sortStatus.direction, sortStatus.direction,
tableState.tableKey, tableState.tableKey,

View File

@ -419,7 +419,10 @@ export default function BuildLineTable({
]; ];
}, [hasOutput, isActive, table, output]); }, [hasOutput, isActive, table, output]);
const buildOrderFields = useBuildOrderFields({ create: true }); const buildOrderFields = useBuildOrderFields({
create: true,
modalId: 'new-build-order'
});
const [initialData, setInitialData] = useState<any>({}); const [initialData, setInitialData] = useState<any>({});
@ -431,6 +434,7 @@ export default function BuildLineTable({
url: ApiEndpoints.build_order_list, url: ApiEndpoints.build_order_list,
title: t`Create Build Order`, title: t`Create Build Order`,
fields: buildOrderFields, fields: buildOrderFields,
modalId: 'new-build-order',
initialData: initialData, initialData: initialData,
follow: true, follow: true,
modelType: ModelType.build modelType: ModelType.build

View File

@ -194,11 +194,15 @@ export function BuildOrderTable({
const user = useUserState(); const user = useUserState();
const buildOrderFields = useBuildOrderFields({ create: true }); const buildOrderFields = useBuildOrderFields({
create: true,
modalId: 'create-build-order'
});
const newBuild = useCreateApiFormModal({ const newBuild = useCreateApiFormModal({
url: ApiEndpoints.build_order_list, url: ApiEndpoints.build_order_list,
title: t`Add Build Order`, title: t`Add Build Order`,
modalId: 'create-build-order',
fields: buildOrderFields, fields: buildOrderFields,
initialData: { initialData: {
part: partId, part: partId,

View File

@ -256,11 +256,15 @@ export default function BuildOutputTable({
[partId, buildId, testTemplates, trackedItems] [partId, buildId, testTemplates, trackedItems]
); );
const buildOutputFields = useBuildOrderOutputFields({ build: build }); const buildOutputFields = useBuildOrderOutputFields({
build: build,
modalId: 'add-build-output'
});
const addBuildOutput = useCreateApiFormModal({ const addBuildOutput = useCreateApiFormModal({
url: apiUrl(ApiEndpoints.build_output_create, buildId), url: apiUrl(ApiEndpoints.build_output_create, buildId),
title: t`Add Build Output`, title: t`Add Build Output`,
modalId: 'add-build-output',
fields: buildOutputFields, fields: buildOutputFields,
timeout: 10000, timeout: 10000,
initialData: { initialData: {
@ -302,13 +306,15 @@ export default function BuildOutputTable({
const editStockItemFields = useStockFields({ const editStockItemFields = useStockFields({
create: false, create: false,
partId: partId, partId: partId,
stockItem: selectedOutputs[0] stockItem: selectedOutputs[0],
modalId: 'edit-build-output'
}); });
const editBuildOutput = useEditApiFormModal({ const editBuildOutput = useEditApiFormModal({
url: ApiEndpoints.stock_item_list, url: ApiEndpoints.stock_item_list,
pk: selectedOutputs[0]?.pk, pk: selectedOutputs[0]?.pk,
title: t`Edit Build Output`, title: t`Edit Build Output`,
modalId: 'edit-build-output',
fields: editStockItemFields, fields: editStockItemFields,
table: table table: table
}); });

View File

@ -276,11 +276,15 @@ export default function SalesOrderLineItemTable({
table: table table: table
}); });
const buildOrderFields = useBuildOrderFields({ create: true }); const buildOrderFields = useBuildOrderFields({
create: true,
modalId: 'build-order-create-from-sales-order'
});
const newBuildOrder = useCreateApiFormModal({ const newBuildOrder = useCreateApiFormModal({
url: ApiEndpoints.build_order_list, url: ApiEndpoints.build_order_list,
title: t`Create Build Order`, title: t`Create Build Order`,
modalId: 'build-order-create-from-sales-order',
fields: buildOrderFields, fields: buildOrderFields,
initialData: initialData, initialData: initialData,
follow: true, follow: true,

View File

@ -526,12 +526,14 @@ export function StockItemTable({
const newStockItemFields = useStockFields({ const newStockItemFields = useStockFields({
create: true, create: true,
partId: params.part partId: params.part,
modalId: 'add-stock-item'
}); });
const newStockItem = useCreateApiFormModal({ const newStockItem = useCreateApiFormModal({
url: ApiEndpoints.stock_item_list, url: ApiEndpoints.stock_item_list,
title: t`Add Stock Item`, title: t`Add Stock Item`,
modalId: 'add-stock-item',
fields: newStockItemFields, fields: newStockItemFields,
initialData: { initialData: {
part: params.part, part: params.part,