mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-25 04:23:33 +00:00
[UI] More sub-forms (#11785)
* Fix ApiFormFIeldType docstring * Case fix * Add sub-forms for company creation * Tweak component def * Add new company for manufacturer-part and supplier-part * Create new parameter template directly from parameter view * Add tooltip * Add playwright tests * Consolidate translation * Revert to left side * Fix title case --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@@ -37,7 +37,6 @@ export type ApiFormFieldHeader = {
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
* - However, they can be overridden by the user
|
||||
*
|
||||
* @param name : The name of the field
|
||||
* @param label : The label to display for the field
|
||||
* @param value : The value of the field
|
||||
* @param default : The default value of the field
|
||||
@@ -46,16 +45,21 @@ export type ApiFormFieldHeader = {
|
||||
* @param api_url : The API endpoint to fetch data from (for related fields)
|
||||
* @param pk_field : The primary key field for the related field (default = "pk")
|
||||
* @param model : The model to use for related fields
|
||||
* @param modelRenderer : Optional function to render the related model instance (for related fields)
|
||||
* @param filters : Optional API filters to apply to related fields
|
||||
* @param child: Optional definition of a child field (for nested objects)
|
||||
* @param children: Optional definitions of child fields (for nested objects with multiple fields)
|
||||
* @param required : Whether the field is required
|
||||
* @param allow_null: Whether the field allows null values
|
||||
* @param allow_blank: Whether the field allows blank values
|
||||
* @param error : Optional error message to display
|
||||
* @param hidden : Whether the field is hidden
|
||||
* @param disabled : Whether the field is disabled
|
||||
* @param error : Optional error message to display
|
||||
* @param allow_null: Whether the field allows null values
|
||||
* @param allow_blank: Whether the field allows blank values
|
||||
* @param exclude : Whether to exclude the field from the submitted data
|
||||
* @param read_only : Whether the field is read-only
|
||||
* @param placeholder : The placeholder text to display
|
||||
* @param placeholderAutofill: Whether to allow auto-filling of the placeholder value
|
||||
* @param addCreateFields : Fields to display when creating a new related object (for related fields)
|
||||
* @param description : The description to display for the field
|
||||
* @param preFieldContent : Content to render before the field
|
||||
* @param postFieldContent : Content to render after the field
|
||||
@@ -63,11 +67,11 @@ export type ApiFormFieldHeader = {
|
||||
* @param rightSection : Content to render in the right section of the field
|
||||
* @param autoFill: Whether to automatically fill the field with data from the API
|
||||
* @param autoFillFilters: Optional filters to apply when auto-filling the field
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
* @param headers : Optional definitions of table headers (for table fields)
|
||||
* @param singleFetchFunction : Optional function to fetch a single value for this field (used for fetching the initial value when editing an existing object)
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
|
||||
@@ -69,7 +69,7 @@ export function RelatedModelField({
|
||||
// Keep track of the primary key value for this field
|
||||
const [pk, setPk] = useState<number | null>(null);
|
||||
|
||||
function setValuefromPK(pk: number) {
|
||||
function setValueFromPK(pk: number) {
|
||||
fetchSingleField(pk);
|
||||
}
|
||||
|
||||
@@ -483,9 +483,14 @@ export function RelatedModelField({
|
||||
styles={{ description: { paddingBottom: '5px' } }}
|
||||
>
|
||||
<Group justify='space-between' wrap='nowrap' gap={3}>
|
||||
{addButton &&
|
||||
modelInfo &&
|
||||
InlineCreateButton(definition, modelInfo, form, setValuefromPK)}
|
||||
{addButton && modelInfo && (
|
||||
<InlineCreateButton
|
||||
definition={definition}
|
||||
modelInfo={modelInfo}
|
||||
form={form}
|
||||
setValue={setValueFromPK}
|
||||
/>
|
||||
)}
|
||||
<Expand>
|
||||
<Select
|
||||
id={fieldId}
|
||||
@@ -551,20 +556,29 @@ export function RelatedModelField({
|
||||
);
|
||||
}
|
||||
|
||||
function InlineCreateButton(
|
||||
definition: ApiFormFieldType,
|
||||
modelInfo: TranslatableModelInformationInterface,
|
||||
form: UseFormReturn<FieldValues, any, FieldValues>,
|
||||
setValue: (value: number) => void
|
||||
): ReactNode {
|
||||
function InlineCreateButton({
|
||||
definition,
|
||||
modelInfo,
|
||||
form,
|
||||
setValue
|
||||
}: {
|
||||
definition: ApiFormFieldType;
|
||||
modelInfo: TranslatableModelInformationInterface;
|
||||
form: UseFormReturn<FieldValues, any, FieldValues>;
|
||||
setValue: (value: number) => void;
|
||||
}): ReactNode {
|
||||
const relatedInitialData = useMemo(
|
||||
() => calculateModalData(definition, form),
|
||||
[definition.filters, definition.addCreateFields, form]
|
||||
);
|
||||
const model = useMemo(() => modelInfo?.label() ?? '', [modelInfo]);
|
||||
|
||||
const title: string = useMemo(() => {
|
||||
const model = modelInfo?.label() ?? t`Item`;
|
||||
return t`Create New ${model}`;
|
||||
}, [modelInfo]);
|
||||
|
||||
const create_modal = useCreateApiFormModal({
|
||||
title: t`Create New ${model}`,
|
||||
title: title,
|
||||
url: apiUrl(modelInfo.api_endpoint),
|
||||
modelType: definition.model,
|
||||
initialData: relatedInitialData,
|
||||
@@ -577,6 +591,8 @@ function InlineCreateButton(
|
||||
<>
|
||||
{create_modal.modal}
|
||||
<ActionButton
|
||||
tooltip={title}
|
||||
tooltipAlignment='top-start'
|
||||
onClick={() => {
|
||||
create_modal.open();
|
||||
}}
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
} from '../components/render/StatusRenderer';
|
||||
import { useApi } from '../contexts/ApiContext';
|
||||
import { useGlobalStatusState } from '../states/GlobalStatusState';
|
||||
import { useUserState } from '../states/UserState';
|
||||
|
||||
export function projectCodeFields(): ApiFormFieldSet {
|
||||
return {
|
||||
@@ -97,6 +98,25 @@ export function extraLineItemFields(): ApiFormFieldSet {
|
||||
};
|
||||
}
|
||||
|
||||
export function useParameterTemplateFields(): ApiFormFieldSet {
|
||||
return useMemo(() => {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
units: {},
|
||||
model_type: {},
|
||||
choices: {},
|
||||
checkbox: {},
|
||||
selectionlist: {
|
||||
filters: {
|
||||
active: true
|
||||
}
|
||||
},
|
||||
enabled: {}
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useParameterFields({
|
||||
modelType,
|
||||
modelId
|
||||
@@ -106,6 +126,10 @@ export function useParameterFields({
|
||||
}): ApiFormFieldSet {
|
||||
const api = useApi();
|
||||
|
||||
const user = useUserState.getState();
|
||||
|
||||
const templateCreateFields = useParameterTemplateFields();
|
||||
|
||||
const [selectionListId, setSelectionListId] = useState<number | null>(null);
|
||||
|
||||
// Valid field choices
|
||||
@@ -193,7 +217,8 @@ export function useParameterFields({
|
||||
} else if (record?.selectionlist) {
|
||||
setFieldType('related field');
|
||||
}
|
||||
}
|
||||
},
|
||||
addCreateFields: user.isStaff() ? templateCreateFields : undefined
|
||||
},
|
||||
data: {
|
||||
value: data,
|
||||
@@ -244,5 +269,14 @@ export function useParameterFields({
|
||||
},
|
||||
note: {}
|
||||
};
|
||||
}, [data, modelType, fieldType, choices, modelId, selectionListId]);
|
||||
}, [
|
||||
data,
|
||||
modelType,
|
||||
fieldType,
|
||||
choices,
|
||||
modelId,
|
||||
selectionListId,
|
||||
templateCreateFields,
|
||||
user
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ export function useSupplierPartFields({
|
||||
filters: {
|
||||
active: true,
|
||||
is_supplier: true
|
||||
},
|
||||
addCreateFields: {
|
||||
name: {},
|
||||
description: {},
|
||||
is_supplier: { value: true, hidden: true }
|
||||
}
|
||||
},
|
||||
SKU: {
|
||||
@@ -88,6 +93,11 @@ export function useManufacturerPartFields() {
|
||||
filters: {
|
||||
active: true,
|
||||
is_manufacturer: true
|
||||
},
|
||||
addCreateFields: {
|
||||
name: {},
|
||||
description: {},
|
||||
is_manufacturer: { value: true, hidden: true }
|
||||
}
|
||||
},
|
||||
MPN: {},
|
||||
|
||||
@@ -247,6 +247,11 @@ export function usePurchaseOrderFields({
|
||||
filters: {
|
||||
is_supplier: true,
|
||||
active: true
|
||||
},
|
||||
addCreateFields: {
|
||||
name: {},
|
||||
description: {},
|
||||
is_supplier: { value: true, hidden: true }
|
||||
}
|
||||
},
|
||||
supplier_reference: {},
|
||||
|
||||
@@ -48,6 +48,11 @@ export function useSalesOrderFields({
|
||||
filters: {
|
||||
is_customer: true,
|
||||
active: true
|
||||
},
|
||||
addCreateFields: {
|
||||
name: {},
|
||||
description: {},
|
||||
is_customer: { value: true, hidden: true }
|
||||
}
|
||||
},
|
||||
customer_reference: {},
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { RowAction, TableColumn } from '@lib/types/Tables';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useParameterTemplateFields } from '../../forms/CommonForms';
|
||||
import { useFilters } from '../../hooks/UseFilter';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
@@ -30,22 +31,7 @@ export default function ParameterTemplateTable() {
|
||||
const table = useTable('parameter-templates');
|
||||
const user = useUserState();
|
||||
|
||||
const parameterTemplateFields: ApiFormFieldSet = useMemo(() => {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
units: {},
|
||||
model_type: {},
|
||||
choices: {},
|
||||
checkbox: {},
|
||||
selectionlist: {
|
||||
filters: {
|
||||
active: true
|
||||
}
|
||||
},
|
||||
enabled: {}
|
||||
};
|
||||
}, []);
|
||||
const parameterTemplateFields: ApiFormFieldSet = useParameterTemplateFields();
|
||||
|
||||
const newTemplate = useCreateApiFormModal({
|
||||
url: ApiEndpoints.parameter_template_list,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '../baseFixtures.ts';
|
||||
import { readeruser } from '../defaults.ts';
|
||||
import { readeruser, stevenuser } from '../defaults.ts';
|
||||
import {
|
||||
activateCalendarView,
|
||||
activateTableView,
|
||||
@@ -84,11 +84,11 @@ test('Purchasing - Index', async ({ browser }) => {
|
||||
|
||||
test('Purchasing - Parameters', async ({ browser }) => {
|
||||
const page = await doCachedLogin(browser, {
|
||||
url: 'purchasing/purchase-order/11/parameters'
|
||||
url: 'purchasing/purchase-order/11/parameters',
|
||||
user: stevenuser
|
||||
});
|
||||
|
||||
// Create a new parameter against this purchase order
|
||||
// We will use a "SelectionList" to choose the value here
|
||||
await page
|
||||
.getByRole('button', { name: 'action-menu-add-parameters' })
|
||||
.click();
|
||||
@@ -98,6 +98,25 @@ test('Purchasing - Parameters', async ({ browser }) => {
|
||||
})
|
||||
.click();
|
||||
|
||||
// Check for button to add a new template
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'action-button-create-new-parameter-template'
|
||||
})
|
||||
.click();
|
||||
await page
|
||||
.getByText('Create New Parameter Template', { exact: true })
|
||||
.first()
|
||||
.waitFor();
|
||||
await page.getByRole('textbox', { name: 'text-field-description' }).waitFor();
|
||||
await page.getByRole('textbox', { name: 'text-field-choices' }).waitFor();
|
||||
await page
|
||||
.getByLabel('Create New Parameter Template')
|
||||
.getByRole('button', { name: 'Cancel' })
|
||||
.click();
|
||||
|
||||
// We will use a "SelectionList" to choose the value here
|
||||
|
||||
// Select the template
|
||||
await page
|
||||
.getByRole('combobox', { name: 'related-field-template' })
|
||||
|
||||
Reference in New Issue
Block a user