2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-14 20:06:44 +00:00
* Remove debouncing from text field

* Add debounce to data import field

* Only apply for strings values

* Fix unit test

* More unit test tweaks

(cherry picked from commit ba9b5438b4)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
github-actions[bot]
2025-11-14 17:53:27 +11:00
committed by GitHub
parent 56f09e1aa6
commit 8cbce3f335
4 changed files with 43 additions and 25 deletions

View File

@@ -1,6 +1,5 @@
import { t } from '@lingui/core/macro';
import { TextInput, Tooltip } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconCopyCheck, IconX } from '@tabler/icons-react';
import {
type ReactNode,
@@ -40,30 +39,26 @@ export default function TextField({
const { value } = useMemo(() => field, [field]);
const [rawText, setRawText] = useState<string>(value || '');
const [textValue, setTextValue] = useState<string>(value || '');
const [debouncedText] = useDebouncedValue(rawText, 100);
const onTextChange = useCallback(
(value: any) => {
setTextValue(value);
onChange(value);
},
[onChange]
);
useEffect(() => {
setRawText(value || '');
setTextValue(value || '');
}, [value]);
const onTextChange = useCallback((value: any) => {
setRawText(value);
}, []);
useEffect(() => {
if (debouncedText !== value) {
onChange(debouncedText);
}
}, [debouncedText]);
// Construct a "right section" for the text field
const textFieldRightSection: ReactNode = useMemo(() => {
if (definition.rightSection) {
// Use the specified override value
return definition.rightSection;
} else if (value) {
} else if (textValue) {
if (!definition.required && !definition.disabled) {
// Render a button to clear the text field
return (
@@ -78,7 +73,7 @@ export default function TextField({
);
}
} else if (
!value &&
!textValue &&
definition.placeholder &&
placeholderAutofill &&
!definition.disabled
@@ -94,7 +89,7 @@ export default function TextField({
</Tooltip>
);
}
}, [placeholderAutofill, definition, value]);
}, [placeholderAutofill, definition, textValue]);
return (
<TextInput
@@ -103,19 +98,19 @@ export default function TextField({
id={fieldId}
aria-label={`text-field-${field.name}`}
type={definition.field_type}
value={rawText || ''}
value={textValue || ''}
error={definition.error ?? error?.message}
radius='sm'
onChange={(event) => onTextChange(event.currentTarget.value)}
onBlur={(event) => {
if (event.currentTarget.value != value) {
onChange(event.currentTarget.value);
if (event.currentTarget.value != textValue) {
onTextChange(event.currentTarget.value);
}
}}
onKeyDown={(event) => {
if (event.code === 'Enter') {
// Bypass debounce on enter key
onChange(event.currentTarget.value);
onTextChange(event.currentTarget.value);
}
onKeyDown(event.code);
}}

View File

@@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import type { ApiFormFieldType } from '@lib/types/Forms';
import { useDebouncedValue } from '@mantine/hooks';
import { useApi } from '../../contexts/ApiContext';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import { StandaloneField } from '../forms/StandaloneField';
@@ -83,6 +84,14 @@ function ImporterDefaultField({
}) {
const api = useApi();
const [rawValue, setRawValue] = useState<any>('');
const fieldType: string = useMemo(() => {
return session.availableFields[fieldName]?.type;
}, [fieldName, session.availableFields]);
const [value] = useDebouncedValue(rawValue, fieldType == 'string' ? 500 : 10);
const onChange = useCallback(
(value: any) => {
// Update the default value for the field
@@ -105,6 +114,11 @@ function ImporterDefaultField({
[fieldName, session, session.fieldDefaults]
);
// Update the default value after the debounced value changes
useEffect(() => {
onChange(value);
}, [value]);
const fieldDef: ApiFormFieldType = useMemo(() => {
let def: any = session.availableFields[fieldName];
@@ -114,7 +128,10 @@ function ImporterDefaultField({
value: session.fieldDefaults[fieldName],
field_type: def.type,
description: def.help_text,
onValueChange: onChange
required: false,
onValueChange: (value: string) => {
setRawValue(value);
}
};
}

View File

@@ -98,7 +98,7 @@ test('Forms - Supplier Validation', async ({ browser }) => {
// Check for validation errors
await page.getByText('Form Error').waitFor();
await page.getByText('Errors exist for one or more').waitFor();
await page.getByText('This field may not be blank.').waitFor();
await page.getByText('This field is required').waitFor();
await page.getByText('Enter a valid URL.').waitFor();
// Fill out another field, expect that the errors persist
@@ -106,7 +106,7 @@ test('Forms - Supplier Validation', async ({ browser }) => {
.getByLabel('text-field-description', { exact: true })
.fill('A description');
await page.waitForTimeout(250);
await page.getByText('This field may not be blank.').waitFor();
await page.getByText('This field is required').waitFor();
await page.getByText('Enter a valid URL.').waitFor();
// Generate a unique supplier name

View File

@@ -111,9 +111,15 @@ test('Importing - BOM', async ({ browser }) => {
// Delete selected rows
await page
.getByRole('dialog', { name: 'Importing Data Upload File 2' })
.getByRole('dialog', { name: 'Importing Data Upload File' })
.getByLabel('action-button-delete-selected')
.waitFor();
await page.waitForTimeout(200);
await page
.getByRole('dialog', { name: 'Importing Data Upload File' })
.getByLabel('action-button-delete-selected')
.click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
await page.getByText('Success', { exact: true }).waitFor();