More validation fixes and enhancements

This commit is contained in:
2025-09-06 16:15:00 -04:00
parent 4935cfe3bb
commit 54f55b06a1
4 changed files with 46 additions and 55 deletions

View File

@@ -123,9 +123,23 @@ 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;
});
@@ -133,16 +147,16 @@ const ValidationContainer = <T extends string>({
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(() => {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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];
}