mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-27 22:26:00 +00:00
[UI] Table field refactor (#12274)
* Use callback funcs * Don't use idx to identify rows * Add debug function for finding why a component re-rendered * Do not pass 'control' through to each row * Prevent unnecessary re-rendering of table rows * Adjust order of operations for hooks * Keep props hidden * Use lightweight NumberInput * Use NumberInput elsewhere * Add comment * use rowId instead of idx * Generic row memos * Compare errors too * Fix for BomItemSubstituteRow * Adjust more forms * memoize quantity * Memoize build lines * Fix re-rendering issues for build allocation * Fix for useConsumeBuildLinesForm * Fix for transfer order table * Fix useReceiveLineItems * Remove memoized pattern * Fix row keys * Cleanup * Create useStockItems hook for memoizing items * Refactoring * More refactoring * Remove obj reference - preventing shallow comparison from working * Add error message to useWhyDidYouUpdate * Cleanup * Cleanup dead code * Adjust modal width * Change attr name * Remove autoFillFilters prop * Adjustments for serialized stock * Fix typing * Bump frontend version * Adjustments for playwright testing * Fix ref issue * Remove debug entry * Update CHANGELOG.md * Reintroduce index to table header * Refactor common component
This commit is contained in:
@@ -45,6 +45,7 @@ import type {
|
||||
} from '@lib/types/Forms';
|
||||
import {
|
||||
TableFieldExtraRow,
|
||||
TableFieldQuantityInput,
|
||||
type TableFieldRowProps
|
||||
} from '../components/forms/fields/TableField';
|
||||
import { Thumbnail } from '../components/images/Thumbnail';
|
||||
@@ -348,11 +349,11 @@ function LineItemFormRow({
|
||||
}>) {
|
||||
// Barcode Modal state
|
||||
const [opened, { open, close }] = useDisclosure(false, {
|
||||
onClose: () => props.changeFn(props.idx, 'barcode', undefined)
|
||||
onClose: () => props.changeFn(props.rowId, 'barcode', undefined)
|
||||
});
|
||||
|
||||
const [locationOpen, locationHandlers] = useDisclosure(false, {
|
||||
onClose: () => props.changeFn(props.idx, 'location', undefined)
|
||||
onClose: () => props.changeFn(props.rowId, 'location', undefined)
|
||||
});
|
||||
|
||||
// Is this a trackable part?
|
||||
@@ -365,7 +366,7 @@ function LineItemFormRow({
|
||||
|
||||
useEffect(() => {
|
||||
if (!!record.destination) {
|
||||
props.changeFn(props.idx, 'location', record.destination);
|
||||
props.changeFn(props.rowId, 'location', record.destination);
|
||||
locationHandlers.open();
|
||||
}
|
||||
}, [record.destination]);
|
||||
@@ -375,7 +376,7 @@ function LineItemFormRow({
|
||||
isEnabled: () => batchOpen,
|
||||
onGenerate: (value: any) => {
|
||||
if (value) {
|
||||
props.changeFn(props.idx, 'batch_code', value);
|
||||
props.changeFn(props.rowId, 'batch_code', value);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -387,19 +388,19 @@ function LineItemFormRow({
|
||||
|
||||
const [packagingOpen, packagingHandlers] = useDisclosure(false, {
|
||||
onClose: () => {
|
||||
props.changeFn(props.idx, 'packaging', undefined);
|
||||
props.changeFn(props.rowId, 'packaging', undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const [noteOpen, noteHandlers] = useDisclosure(false, {
|
||||
onClose: () => {
|
||||
props.changeFn(props.idx, 'note', undefined);
|
||||
props.changeFn(props.rowId, 'note', undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const [batchOpen, batchHandlers] = useDisclosure(false, {
|
||||
onClose: () => {
|
||||
props.changeFn(props.idx, 'batch_code', undefined);
|
||||
props.changeFn(props.rowId, 'batch_code', undefined);
|
||||
},
|
||||
onOpen: () => {
|
||||
// Generate a new batch code
|
||||
@@ -412,7 +413,7 @@ function LineItemFormRow({
|
||||
|
||||
const [serialOpen, serialHandlers] = useDisclosure(false, {
|
||||
onClose: () => {
|
||||
props.changeFn(props.idx, 'serial_numbers', undefined);
|
||||
props.changeFn(props.rowId, 'serial_numbers', undefined);
|
||||
},
|
||||
onOpen: () => {
|
||||
// Generate new serial numbers
|
||||
@@ -422,7 +423,7 @@ function LineItemFormRow({
|
||||
quantity: props.item.quantity
|
||||
});
|
||||
} else {
|
||||
props.changeFn(props.idx, 'serial_numbers', undefined);
|
||||
props.changeFn(props.rowId, 'serial_numbers', undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -433,20 +434,20 @@ function LineItemFormRow({
|
||||
const defaultExpiry = record.part_detail?.default_expiry;
|
||||
if (defaultExpiry !== undefined && defaultExpiry > 0) {
|
||||
props.changeFn(
|
||||
props.idx,
|
||||
props.rowId,
|
||||
'expiry_date',
|
||||
dayjs().add(defaultExpiry, 'day').format('YYYY-MM-DD')
|
||||
);
|
||||
}
|
||||
},
|
||||
onClose: () => {
|
||||
props.changeFn(props.idx, 'expiry_date', undefined);
|
||||
props.changeFn(props.rowId, 'expiry_date', undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// Status value
|
||||
const [statusOpen, statusHandlers] = useDisclosure(false, {
|
||||
onClose: () => props.changeFn(props.idx, 'status', undefined)
|
||||
onClose: () => props.changeFn(props.rowId, 'status', undefined)
|
||||
});
|
||||
|
||||
// Barcode value
|
||||
@@ -455,7 +456,7 @@ function LineItemFormRow({
|
||||
|
||||
// Change form value when state is altered
|
||||
useEffect(() => {
|
||||
props.changeFn(props.idx, 'barcode', barcode);
|
||||
props.changeFn(props.rowId, 'barcode', barcode);
|
||||
}, [barcode]);
|
||||
|
||||
// Update location field description on state change
|
||||
@@ -574,15 +575,14 @@ function LineItemFormRow({
|
||||
/>
|
||||
</Table.Td>
|
||||
<Table.Td style={{ whiteSpace: 'nowrap' }}>
|
||||
<StandaloneField
|
||||
fieldName='quantity'
|
||||
fieldDefinition={{
|
||||
field_type: 'number',
|
||||
value: props.item.quantity,
|
||||
onValueChange: (value) => {
|
||||
props.changeFn(props.idx, 'quantity', value);
|
||||
serialNumberGenerator.update({ quantity: value });
|
||||
}
|
||||
<TableFieldQuantityInput
|
||||
min={0}
|
||||
value={props.item.quantity ?? ''}
|
||||
onChange={(value) => {
|
||||
props.changeFn(props.rowId, 'quantity', value);
|
||||
serialNumberGenerator.update({
|
||||
quantity: value === '' ? undefined : value
|
||||
});
|
||||
}}
|
||||
error={props.rowErrors?.quantity?.message}
|
||||
/>
|
||||
@@ -678,7 +678,7 @@ function LineItemFormRow({
|
||||
</Flex>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.rowId)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
{locationOpen && (
|
||||
@@ -697,7 +697,7 @@ function LineItemFormRow({
|
||||
structural: false
|
||||
},
|
||||
onValueChange: (value) => {
|
||||
props.changeFn(props.idx, 'location', value);
|
||||
props.changeFn(props.rowId, 'location', value);
|
||||
},
|
||||
description: locationDescription,
|
||||
value: props.item.location,
|
||||
@@ -719,7 +719,7 @@ function LineItemFormRow({
|
||||
tooltip={t`Store at default location`}
|
||||
onClick={() =>
|
||||
props.changeFn(
|
||||
props.idx,
|
||||
props.rowId,
|
||||
'location',
|
||||
record.part_detail?.default_location ??
|
||||
record.part_detail?.category_default_location
|
||||
@@ -733,7 +733,11 @@ function LineItemFormRow({
|
||||
icon={<InvenTreeIcon icon='destination' />}
|
||||
tooltip={t`Store at line item destination `}
|
||||
onClick={() =>
|
||||
props.changeFn(props.idx, 'location', record.destination)
|
||||
props.changeFn(
|
||||
props.rowId,
|
||||
'location',
|
||||
record.destination
|
||||
)
|
||||
}
|
||||
tooltipAlignment='top'
|
||||
/>
|
||||
@@ -746,7 +750,7 @@ function LineItemFormRow({
|
||||
tooltip={t`Store with already received stock`}
|
||||
onClick={() =>
|
||||
props.changeFn(
|
||||
props.idx,
|
||||
props.rowId,
|
||||
'location',
|
||||
record.destination_detail.pk
|
||||
)
|
||||
@@ -762,7 +766,7 @@ function LineItemFormRow({
|
||||
<TableFieldExtraRow
|
||||
visible={batchOpen}
|
||||
onValueChange={(value) => {
|
||||
props.changeFn(props.idx, 'batch_code', value);
|
||||
props.changeFn(props.rowId, 'batch_code', value);
|
||||
}}
|
||||
fieldName='batch_code'
|
||||
fieldDefinition={{
|
||||
@@ -776,7 +780,7 @@ function LineItemFormRow({
|
||||
<TableFieldExtraRow
|
||||
visible={serialOpen}
|
||||
onValueChange={(value) =>
|
||||
props.changeFn(props.idx, 'serial_numbers', value)
|
||||
props.changeFn(props.rowId, 'serial_numbers', value)
|
||||
}
|
||||
fieldName='serial_numbers'
|
||||
fieldDefinition={{
|
||||
@@ -794,7 +798,7 @@ function LineItemFormRow({
|
||||
<TableFieldExtraRow
|
||||
visible={expiryDateOpen}
|
||||
onValueChange={(value) =>
|
||||
props.changeFn(props.idx, 'expiry_date', value)
|
||||
props.changeFn(props.rowId, 'expiry_date', value)
|
||||
}
|
||||
fieldName='expiry_date'
|
||||
fieldDefinition={{
|
||||
@@ -808,7 +812,9 @@ function LineItemFormRow({
|
||||
)}
|
||||
<TableFieldExtraRow
|
||||
visible={packagingOpen}
|
||||
onValueChange={(value) => props.changeFn(props.idx, 'packaging', value)}
|
||||
onValueChange={(value) =>
|
||||
props.changeFn(props.rowId, 'packaging', value)
|
||||
}
|
||||
fieldName='packaging'
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
@@ -821,7 +827,7 @@ function LineItemFormRow({
|
||||
visible={statusOpen}
|
||||
defaultValue={10}
|
||||
fieldName='status'
|
||||
onValueChange={(value) => props.changeFn(props.idx, 'status', value)}
|
||||
onValueChange={(value) => props.changeFn(props.rowId, 'status', value)}
|
||||
fieldDefinition={{
|
||||
field_type: 'choice',
|
||||
api_url: apiUrl(ApiEndpoints.stock_status),
|
||||
@@ -833,7 +839,7 @@ function LineItemFormRow({
|
||||
<TableFieldExtraRow
|
||||
visible={noteOpen}
|
||||
fieldName='note'
|
||||
onValueChange={(value) => props.changeFn(props.idx, 'note', value)}
|
||||
onValueChange={(value) => props.changeFn(props.rowId, 'note', value)}
|
||||
fieldDefinition={{
|
||||
field_type: 'string',
|
||||
label: t`Note`
|
||||
@@ -862,13 +868,20 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
[]
|
||||
);
|
||||
|
||||
const records = Object.fromEntries(
|
||||
props.items.map((item) => [item.pk, item])
|
||||
);
|
||||
const records = useMemo(() => {
|
||||
return Object.fromEntries(props.items.map((item) => [item.pk, item]));
|
||||
}, [props.items]);
|
||||
|
||||
const filteredItems = props.items.filter(
|
||||
(elem) => elem.quantity !== elem.received
|
||||
);
|
||||
const filteredItems = useMemo(() => {
|
||||
return props.items
|
||||
.filter((elem) => elem.quantity !== elem.received)
|
||||
.map((elem) => {
|
||||
return {
|
||||
id: elem.pk,
|
||||
...elem
|
||||
};
|
||||
});
|
||||
}, [props.items]);
|
||||
|
||||
const fields: ApiFormFieldSet = useMemo(() => {
|
||||
return {
|
||||
@@ -878,8 +891,9 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
},
|
||||
items: {
|
||||
field_type: 'table',
|
||||
value: filteredItems.map((elem, idx) => {
|
||||
value: filteredItems.map((elem) => {
|
||||
return {
|
||||
id: elem.pk,
|
||||
line_item: elem.pk,
|
||||
location: elem.destination ?? elem.destination_detail?.pk ?? null,
|
||||
quantity: elem.quantity - elem.received,
|
||||
@@ -902,7 +916,7 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
props={row}
|
||||
record={record}
|
||||
statuses={stockStatusCodes}
|
||||
key={record.pk}
|
||||
key={row.rowId}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -921,7 +935,7 @@ export function useReceiveLineItems(props: LineItemsForm) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [filteredItems, props, stockStatusCodes]);
|
||||
}, [filteredItems, records, props, stockStatusCodes]);
|
||||
|
||||
return useCreateApiFormModal({
|
||||
...props.formProps,
|
||||
|
||||
Reference in New Issue
Block a user