More validation fixes and enhancements
This commit is contained in:
@@ -123,26 +123,40 @@ const ValidationContainer = <T extends string>({
|
||||
|
||||
// Function to mark a row for revalidation
|
||||
const markRowForRevalidation = useCallback((rowIndex: number, fieldKey?: string) => {
|
||||
// Map filtered rowIndex to original data index via __index
|
||||
const originalIndex = (() => {
|
||||
try {
|
||||
const row = filteredData[rowIndex];
|
||||
if (!row) return rowIndex;
|
||||
const id = row.__index;
|
||||
if (!id) return rowIndex;
|
||||
const idx = data.findIndex(r => r.__index === id);
|
||||
return idx >= 0 ? idx : rowIndex;
|
||||
} catch {
|
||||
return rowIndex;
|
||||
}
|
||||
})();
|
||||
|
||||
setFieldsToRevalidate(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(rowIndex);
|
||||
newSet.add(originalIndex);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
|
||||
// Also track which specific field needs to be revalidated
|
||||
if (fieldKey) {
|
||||
setFieldsToRevalidateMap(prev => {
|
||||
const newMap = { ...prev };
|
||||
if (!newMap[rowIndex]) {
|
||||
newMap[rowIndex] = [];
|
||||
if (!newMap[originalIndex]) {
|
||||
newMap[originalIndex] = [];
|
||||
}
|
||||
if (!newMap[rowIndex].includes(fieldKey)) {
|
||||
newMap[rowIndex] = [...newMap[rowIndex], fieldKey];
|
||||
if (!newMap[originalIndex].includes(fieldKey)) {
|
||||
newMap[originalIndex] = [...newMap[originalIndex], fieldKey];
|
||||
}
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [data, filteredData]);
|
||||
|
||||
// Add a ref to track the last validation time
|
||||
|
||||
@@ -488,52 +502,16 @@ const ValidationContainer = <T extends string>({
|
||||
// Detect if this is a direct item_number edit
|
||||
const isItemNumberEdit = key === 'item_number' as T;
|
||||
|
||||
// For item_number edits, we need special handling to ensure they persist
|
||||
// For item_number edits, use core updateRow to atomically update + validate
|
||||
if (isItemNumberEdit) {
|
||||
console.log(`Manual edit to item_number: ${value}`);
|
||||
|
||||
// First, update data immediately to ensure edit takes effect
|
||||
setData(prevData => {
|
||||
const newData = [...prevData];
|
||||
if (originalIndex >= 0 && originalIndex < newData.length) {
|
||||
newData[originalIndex] = {
|
||||
...newData[originalIndex],
|
||||
[key]: processedValue
|
||||
};
|
||||
}
|
||||
return newData;
|
||||
});
|
||||
|
||||
// Mark for revalidation after a delay to ensure data update completes first
|
||||
setTimeout(() => {
|
||||
markRowForRevalidation(rowIndex, key as string);
|
||||
}, 0);
|
||||
|
||||
// Return early to prevent double-updating
|
||||
const idx = originalIndex >= 0 ? originalIndex : rowIndex;
|
||||
validationState.updateRow(idx, key as unknown as any, processedValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// For all other fields, use standard approach
|
||||
// Always use setData for updating - immediate update for better UX
|
||||
const updatedRow = { ...rowData, [key]: processedValue };
|
||||
|
||||
// Mark this row for revalidation to clear any existing errors
|
||||
markRowForRevalidation(rowIndex, key as string);
|
||||
|
||||
// Update the data immediately to show the change
|
||||
setData(prevData => {
|
||||
const newData = [...prevData];
|
||||
if (originalIndex >= 0 && originalIndex < newData.length) {
|
||||
// Create a new row object with the updated field
|
||||
newData[originalIndex] = {
|
||||
...newData[originalIndex],
|
||||
[key]: processedValue
|
||||
};
|
||||
} else {
|
||||
console.error(`Invalid originalIndex: ${originalIndex}, data length: ${newData.length}`);
|
||||
}
|
||||
return newData;
|
||||
});
|
||||
// For all other fields, use core updateRow for atomic update + validation
|
||||
const idx = originalIndex >= 0 ? originalIndex : rowIndex;
|
||||
validationState.updateRow(idx, key as unknown as any, processedValue);
|
||||
|
||||
// Secondary effects - using requestAnimationFrame for better performance
|
||||
requestAnimationFrame(() => {
|
||||
@@ -1225,4 +1203,4 @@ const ValidationContainer = <T extends string>({
|
||||
)
|
||||
}
|
||||
|
||||
export default ValidationContainer
|
||||
export default ValidationContainer
|
||||
|
||||
@@ -24,6 +24,9 @@ type ErrorType = {
|
||||
source?: string;
|
||||
}
|
||||
|
||||
// Stable empty errors array to prevent unnecessary re-renders
|
||||
const EMPTY_ERRORS: ErrorType[] = Object.freeze([]);
|
||||
|
||||
interface ValidationTableProps<T extends string> {
|
||||
data: RowData<T>[]
|
||||
fields: Fields<T>
|
||||
@@ -421,7 +424,8 @@ const ValidationTable = <T extends string>({
|
||||
}
|
||||
|
||||
// Get validation errors for this cell
|
||||
const cellErrors = validationErrors.get(row.index)?.[fieldKey] || [];
|
||||
// Use stable EMPTY_ERRORS to avoid new array creation on every render
|
||||
const cellErrors = validationErrors.get(row.index)?.[fieldKey] || EMPTY_ERRORS;
|
||||
|
||||
// Create a copy of the field with guaranteed field type for line and subline fields
|
||||
let fieldWithType = field;
|
||||
@@ -702,4 +706,4 @@ const areEqual = (prev: ValidationTableProps<any>, next: ValidationTableProps<an
|
||||
return true;
|
||||
};
|
||||
|
||||
export default React.memo(ValidationTable, areEqual);
|
||||
export default React.memo(ValidationTable, areEqual);
|
||||
|
||||
@@ -18,6 +18,15 @@ export const useFilterManagement = <T extends string>(
|
||||
|
||||
// Filter data based on current filter state
|
||||
const filteredData = useMemo(() => {
|
||||
// Fast path: no filters active, return original data reference to avoid re-renders
|
||||
const noSearch = !filters.searchText || filters.searchText.trim() === '';
|
||||
const noErrorsOnly = !filters.showErrorsOnly;
|
||||
const noFieldFilter = !filters.filterField || !filters.filterValue || filters.filterValue.trim() === '';
|
||||
|
||||
if (noSearch && noErrorsOnly && noFieldFilter) {
|
||||
return data; // preserve reference; prevents full table rerender on error map changes
|
||||
}
|
||||
|
||||
return data.filter((row, index) => {
|
||||
// Filter by search text
|
||||
if (filters.searchText) {
|
||||
@@ -107,4 +116,4 @@ export const useFilterManagement = <T extends string>(
|
||||
updateFilters,
|
||||
resetFilters
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -301,8 +301,8 @@ export const useRowOperations = <T extends string>(
|
||||
// Update with new validation results
|
||||
if (errors.length > 0) {
|
||||
newRowErrors[key as string] = errors;
|
||||
} else if (!newRowErrors[key as string]) {
|
||||
// If no errors found and no existing errors, ensure field is removed from errors
|
||||
} else {
|
||||
// Clear any existing errors for this field
|
||||
delete newRowErrors[key as string];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user