mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-05 06:32:55 +00:00
[UI] Edit cat param (#12166)
* Refactor form hook components * Reset values when opening form * Rebuild form field
This commit is contained in:
@@ -68,7 +68,7 @@ export function ApiFormField({
|
|||||||
: definition.value
|
: definition.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [definition.value]);
|
}, [definition.value, definition.field_type]);
|
||||||
|
|
||||||
const fieldDefinition: ApiFormFieldType = useMemo(() => {
|
const fieldDefinition: ApiFormFieldType = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||||
import { ModelType } from '@lib/enums/ModelType';
|
import { ModelType } from '@lib/enums/ModelType';
|
||||||
import { apiUrl } from '@lib/functions/Api';
|
import { apiUrl } from '@lib/functions/Api';
|
||||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import type {
|
import type {
|
||||||
StatusCodeInterface,
|
StatusCodeInterface,
|
||||||
@@ -117,32 +117,44 @@ export function useParameterTemplateFields(): ApiFormFieldSet {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useParameterFields({
|
/**
|
||||||
modelType,
|
* Shared hook for the dynamic "value" field on parameter forms.
|
||||||
modelId
|
*
|
||||||
}: {
|
* When the user selects a parameter template, the field type for the
|
||||||
modelType: ModelType;
|
* corresponding value input (data / default_value) must change to match the
|
||||||
modelId: number;
|
* template's data type (boolean, choice, related-field selection list, or
|
||||||
}): ApiFormFieldSet {
|
* plain string). This hook encapsulates that state so it can be reused
|
||||||
|
* across the "Add Parameter" and "Add Category Parameter" forms.
|
||||||
|
*
|
||||||
|
* @param resetDep - When this value changes all internal state is reset to
|
||||||
|
* defaults. Pass a stringified key derived from the form's context (e.g.
|
||||||
|
* `${modelType}-${modelId}`) so the field resets when the context switches.
|
||||||
|
*/
|
||||||
|
export function useDynamicParameterValueField(resetDep?: any): {
|
||||||
|
onTemplateValueChange: (value: any, record: any) => void;
|
||||||
|
valueFieldConfig: ApiFormFieldType;
|
||||||
|
reset: () => void;
|
||||||
|
} {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
const user = useUserState.getState();
|
|
||||||
|
|
||||||
const templateCreateFields = useParameterTemplateFields();
|
|
||||||
|
|
||||||
const [selectionListId, setSelectionListId] = useState<number | null>(null);
|
const [selectionListId, setSelectionListId] = useState<number | null>(null);
|
||||||
|
|
||||||
// Valid field choices
|
|
||||||
const [choices, setChoices] = useState<any[]>([]);
|
const [choices, setChoices] = useState<any[]>([]);
|
||||||
|
|
||||||
// Field type for "data" input
|
|
||||||
const [fieldType, setFieldType] = useState<
|
const [fieldType, setFieldType] = useState<
|
||||||
'string' | 'boolean' | 'choice' | 'related field'
|
'string' | 'boolean' | 'choice' | 'related field'
|
||||||
>('string');
|
>('string');
|
||||||
|
|
||||||
// Memoized value for the "data" field
|
|
||||||
const [data, setData] = useState<string>('');
|
const [data, setData] = useState<string>('');
|
||||||
|
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
setSelectionListId(null);
|
||||||
|
setFieldType('string');
|
||||||
|
setChoices([]);
|
||||||
|
setData('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset();
|
||||||
|
}, [resetDep, reset]);
|
||||||
|
|
||||||
const fetchSelectionEntry = useCallback(
|
const fetchSelectionEntry = useCallback(
|
||||||
(value: any) => {
|
(value: any) => {
|
||||||
if (!value || !selectionListId) {
|
if (!value || !selectionListId) {
|
||||||
@@ -151,9 +163,7 @@ export function useParameterFields({
|
|||||||
|
|
||||||
return api
|
return api
|
||||||
.get(apiUrl(ApiEndpoints.selectionentry_list, selectionListId), {
|
.get(apiUrl(ApiEndpoints.selectionentry_list, selectionListId), {
|
||||||
params: {
|
params: { value: value }
|
||||||
value: value
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data && response.data.length == 1) {
|
if (response.data && response.data.length == 1) {
|
||||||
@@ -166,13 +176,102 @@ export function useParameterFields({
|
|||||||
[selectionListId]
|
[selectionListId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset the field type and choices when the model changes
|
const onTemplateValueChange = useCallback(
|
||||||
useEffect(() => {
|
(value: any, record: any) => {
|
||||||
setSelectionListId(null);
|
setSelectionListId(record?.selectionlist || null);
|
||||||
setFieldType('string');
|
setData('');
|
||||||
setChoices([]);
|
|
||||||
setData('');
|
if (record?.checkbox) {
|
||||||
}, [modelType, modelId]);
|
setChoices([]);
|
||||||
|
setFieldType('boolean');
|
||||||
|
setData('false');
|
||||||
|
} else if (record?.choices) {
|
||||||
|
const _choices: string[] = record.choices.split(',');
|
||||||
|
|
||||||
|
if (_choices.length > 0) {
|
||||||
|
setChoices(
|
||||||
|
_choices.map((choice) => ({
|
||||||
|
display_name: choice.trim(),
|
||||||
|
value: choice.trim()
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
setFieldType('choice');
|
||||||
|
} else {
|
||||||
|
setChoices([]);
|
||||||
|
setFieldType('string');
|
||||||
|
setData('');
|
||||||
|
}
|
||||||
|
} else if (record?.selectionlist) {
|
||||||
|
setFieldType('related field');
|
||||||
|
setData('');
|
||||||
|
} else {
|
||||||
|
setFieldType('string');
|
||||||
|
setData('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setFieldType, setData, setChoices]
|
||||||
|
);
|
||||||
|
|
||||||
|
const valueFieldConfig: ApiFormFieldType = useMemo(
|
||||||
|
() => ({
|
||||||
|
value: data,
|
||||||
|
onValueChange: (value: any, record: any) => {
|
||||||
|
if (fieldType === 'related field' && selectionListId) {
|
||||||
|
// For related fields, store the primary key value (not the string representation)
|
||||||
|
setData(record?.value ?? value);
|
||||||
|
} else {
|
||||||
|
setData(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
field_type: fieldType,
|
||||||
|
choices: fieldType === 'choice' ? choices : undefined,
|
||||||
|
default: fieldType === 'boolean' ? false : undefined,
|
||||||
|
pk_field:
|
||||||
|
fieldType === 'related field' && selectionListId ? 'value' : undefined,
|
||||||
|
model:
|
||||||
|
fieldType === 'related field' && selectionListId
|
||||||
|
? ModelType.selectionentry
|
||||||
|
: undefined,
|
||||||
|
api_url:
|
||||||
|
fieldType === 'related field' && selectionListId
|
||||||
|
? apiUrl(ApiEndpoints.selectionentry_list, selectionListId)
|
||||||
|
: undefined,
|
||||||
|
filters: fieldType === 'related field' ? { active: true } : undefined,
|
||||||
|
adjustValue: (value: any) => {
|
||||||
|
let v: string = value.toString().trim();
|
||||||
|
|
||||||
|
if (fieldType === 'boolean') {
|
||||||
|
if (v.toLowerCase() !== 'true') {
|
||||||
|
v = 'false';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
singleFetchFunction: fetchSelectionEntry
|
||||||
|
}),
|
||||||
|
[data, fieldType, choices, selectionListId, fetchSelectionEntry]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { onTemplateValueChange, valueFieldConfig, reset };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useParameterFields({
|
||||||
|
modelType,
|
||||||
|
modelId
|
||||||
|
}: {
|
||||||
|
modelType: ModelType;
|
||||||
|
modelId: number;
|
||||||
|
}): ApiFormFieldSet {
|
||||||
|
const user = useUserState.getState();
|
||||||
|
const templateCreateFields = useParameterTemplateFields();
|
||||||
|
|
||||||
|
const resetKey = useMemo(
|
||||||
|
() => `${modelType}-${modelId}`,
|
||||||
|
[modelType, modelId]
|
||||||
|
);
|
||||||
|
const { onTemplateValueChange, valueFieldConfig } =
|
||||||
|
useDynamicParameterValueField(resetKey);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@@ -189,97 +288,17 @@ export function useParameterFields({
|
|||||||
for_model: modelType,
|
for_model: modelType,
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
onValueChange: (value: any, record: any) => {
|
onValueChange: onTemplateValueChange,
|
||||||
setSelectionListId(record?.selectionlist || null);
|
|
||||||
|
|
||||||
// Adjust the type of the "data" field based on the selected template
|
|
||||||
if (record?.checkbox) {
|
|
||||||
// This is a "checkbox" field
|
|
||||||
setChoices([]);
|
|
||||||
setFieldType('boolean');
|
|
||||||
setData('false');
|
|
||||||
} else if (record?.choices) {
|
|
||||||
const _choices: string[] = record.choices.split(',');
|
|
||||||
|
|
||||||
if (_choices.length > 0) {
|
|
||||||
setChoices(
|
|
||||||
_choices.map((choice) => {
|
|
||||||
return {
|
|
||||||
display_name: choice.trim(),
|
|
||||||
value: choice.trim()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setFieldType('choice');
|
|
||||||
} else {
|
|
||||||
setChoices([]);
|
|
||||||
setFieldType('string');
|
|
||||||
}
|
|
||||||
} else if (record?.selectionlist) {
|
|
||||||
setFieldType('related field');
|
|
||||||
} else {
|
|
||||||
// Default to a simple string field
|
|
||||||
setFieldType('string');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addCreateFields: user.isStaff() ? templateCreateFields : undefined
|
addCreateFields: user.isStaff() ? templateCreateFields : undefined
|
||||||
},
|
},
|
||||||
data: {
|
data: valueFieldConfig,
|
||||||
value: data,
|
|
||||||
onValueChange: (value: any, record: any) => {
|
|
||||||
if (fieldType === 'related field' && selectionListId) {
|
|
||||||
// For related fields, we need to store the selected primary key value (not the string representation)
|
|
||||||
setData(record?.value ?? value);
|
|
||||||
} else {
|
|
||||||
setData(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: fieldType,
|
|
||||||
field_type: fieldType,
|
|
||||||
choices: fieldType === 'choice' ? choices : undefined,
|
|
||||||
default: fieldType === 'boolean' ? false : undefined,
|
|
||||||
pk_field:
|
|
||||||
fieldType === 'related field' && selectionListId
|
|
||||||
? 'value'
|
|
||||||
: undefined,
|
|
||||||
model:
|
|
||||||
fieldType === 'related field' && selectionListId
|
|
||||||
? ModelType.selectionentry
|
|
||||||
: undefined,
|
|
||||||
api_url:
|
|
||||||
fieldType === 'related field' && selectionListId
|
|
||||||
? apiUrl(ApiEndpoints.selectionentry_list, selectionListId)
|
|
||||||
: undefined,
|
|
||||||
filters:
|
|
||||||
fieldType === 'related field'
|
|
||||||
? {
|
|
||||||
active: true
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
adjustValue: (value: any) => {
|
|
||||||
// Coerce boolean value into a string (required by backend)
|
|
||||||
|
|
||||||
let v: string = value.toString().trim();
|
|
||||||
|
|
||||||
if (fieldType === 'boolean') {
|
|
||||||
if (v.toLowerCase() !== 'true') {
|
|
||||||
v = 'false';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
},
|
|
||||||
singleFetchFunction: fetchSelectionEntry
|
|
||||||
},
|
|
||||||
note: {}
|
note: {}
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
data,
|
|
||||||
modelType,
|
modelType,
|
||||||
fieldType,
|
|
||||||
choices,
|
|
||||||
modelId,
|
modelId,
|
||||||
selectionListId,
|
onTemplateValueChange,
|
||||||
|
valueFieldConfig,
|
||||||
templateCreateFields,
|
templateCreateFields,
|
||||||
user
|
user
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type { TableFilter } from '@lib/types/Filters';
|
|||||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||||
import type { TableColumn } from '@lib/types/Tables';
|
import type { TableColumn } from '@lib/types/Tables';
|
||||||
import { IconInfoCircle } from '@tabler/icons-react';
|
import { IconInfoCircle } from '@tabler/icons-react';
|
||||||
|
import { useDynamicParameterValueField } from '../../forms/CommonForms';
|
||||||
import {
|
import {
|
||||||
useCreateApiFormModal,
|
useCreateApiFormModal,
|
||||||
useDeleteApiFormModal,
|
useDeleteApiFormModal,
|
||||||
@@ -32,16 +33,21 @@ export default function PartCategoryTemplateTable({
|
|||||||
const table = useTable('part-category-parameter-templates');
|
const table = useTable('part-category-parameter-templates');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
|
const { onTemplateValueChange, valueFieldConfig, reset } =
|
||||||
|
useDynamicParameterValueField(categoryId);
|
||||||
|
|
||||||
const formFields: ApiFormFieldSet = useMemo(() => {
|
const formFields: ApiFormFieldSet = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
category: {
|
category: {
|
||||||
value: categoryId,
|
value: categoryId,
|
||||||
disabled: categoryId !== undefined
|
disabled: categoryId !== undefined
|
||||||
},
|
},
|
||||||
template: {},
|
template: {
|
||||||
default_value: {}
|
onValueChange: onTemplateValueChange
|
||||||
|
},
|
||||||
|
default_value: valueFieldConfig
|
||||||
};
|
};
|
||||||
}, [categoryId]);
|
}, [categoryId, onTemplateValueChange, valueFieldConfig]);
|
||||||
|
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
|
const [selectedTemplate, setSelectedTemplate] = useState<number>(0);
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ export default function PartCategoryTemplateTable({
|
|||||||
url: ApiEndpoints.category_parameter_list,
|
url: ApiEndpoints.category_parameter_list,
|
||||||
title: t`Add Category Parameter`,
|
title: t`Add Category Parameter`,
|
||||||
fields: useMemo(() => ({ ...formFields }), [formFields]),
|
fields: useMemo(() => ({ ...formFields }), [formFields]),
|
||||||
|
onOpen: reset,
|
||||||
table: table
|
table: table
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,6 +64,7 @@ export default function PartCategoryTemplateTable({
|
|||||||
pk: selectedTemplate,
|
pk: selectedTemplate,
|
||||||
title: t`Edit Category Parameter`,
|
title: t`Edit Category Parameter`,
|
||||||
fields: useMemo(() => ({ ...formFields }), [formFields]),
|
fields: useMemo(() => ({ ...formFields }), [formFields]),
|
||||||
|
onOpen: reset,
|
||||||
table: table
|
table: table
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user