More validation fixes and enhancements
This commit is contained in:
@@ -123,9 +123,23 @@ const ValidationContainer = <T extends string>({
|
|||||||
|
|
||||||
// Function to mark a row for revalidation
|
// Function to mark a row for revalidation
|
||||||
const markRowForRevalidation = useCallback((rowIndex: number, fieldKey?: string) => {
|
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 => {
|
setFieldsToRevalidate(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.add(rowIndex);
|
newSet.add(originalIndex);
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,16 +147,16 @@ const ValidationContainer = <T extends string>({
|
|||||||
if (fieldKey) {
|
if (fieldKey) {
|
||||||
setFieldsToRevalidateMap(prev => {
|
setFieldsToRevalidateMap(prev => {
|
||||||
const newMap = { ...prev };
|
const newMap = { ...prev };
|
||||||
if (!newMap[rowIndex]) {
|
if (!newMap[originalIndex]) {
|
||||||
newMap[rowIndex] = [];
|
newMap[originalIndex] = [];
|
||||||
}
|
}
|
||||||
if (!newMap[rowIndex].includes(fieldKey)) {
|
if (!newMap[originalIndex].includes(fieldKey)) {
|
||||||
newMap[rowIndex] = [...newMap[rowIndex], fieldKey];
|
newMap[originalIndex] = [...newMap[originalIndex], fieldKey];
|
||||||
}
|
}
|
||||||
return newMap;
|
return newMap;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, [data, filteredData]);
|
||||||
|
|
||||||
// Add a ref to track the last validation time
|
// 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
|
// Detect if this is a direct item_number edit
|
||||||
const isItemNumberEdit = key === 'item_number' as T;
|
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) {
|
if (isItemNumberEdit) {
|
||||||
console.log(`Manual edit to item_number: ${value}`);
|
const idx = originalIndex >= 0 ? originalIndex : rowIndex;
|
||||||
|
validationState.updateRow(idx, key as unknown as any, processedValue);
|
||||||
// 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
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For all other fields, use standard approach
|
// For all other fields, use core updateRow for atomic update + validation
|
||||||
// Always use setData for updating - immediate update for better UX
|
const idx = originalIndex >= 0 ? originalIndex : rowIndex;
|
||||||
const updatedRow = { ...rowData, [key]: processedValue };
|
validationState.updateRow(idx, key as unknown as any, 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;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Secondary effects - using requestAnimationFrame for better performance
|
// Secondary effects - using requestAnimationFrame for better performance
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ type ErrorType = {
|
|||||||
source?: string;
|
source?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stable empty errors array to prevent unnecessary re-renders
|
||||||
|
const EMPTY_ERRORS: ErrorType[] = Object.freeze([]);
|
||||||
|
|
||||||
interface ValidationTableProps<T extends string> {
|
interface ValidationTableProps<T extends string> {
|
||||||
data: RowData<T>[]
|
data: RowData<T>[]
|
||||||
fields: Fields<T>
|
fields: Fields<T>
|
||||||
@@ -421,7 +424,8 @@ const ValidationTable = <T extends string>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get validation errors for this cell
|
// 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
|
// Create a copy of the field with guaranteed field type for line and subline fields
|
||||||
let fieldWithType = field;
|
let fieldWithType = field;
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ export const useFilterManagement = <T extends string>(
|
|||||||
|
|
||||||
// Filter data based on current filter state
|
// Filter data based on current filter state
|
||||||
const filteredData = useMemo(() => {
|
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) => {
|
return data.filter((row, index) => {
|
||||||
// Filter by search text
|
// Filter by search text
|
||||||
if (filters.searchText) {
|
if (filters.searchText) {
|
||||||
|
|||||||
@@ -301,8 +301,8 @@ export const useRowOperations = <T extends string>(
|
|||||||
// Update with new validation results
|
// Update with new validation results
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
newRowErrors[key as string] = errors;
|
newRowErrors[key as string] = errors;
|
||||||
} else if (!newRowErrors[key as string]) {
|
} else {
|
||||||
// If no errors found and no existing errors, ensure field is removed from errors
|
// Clear any existing errors for this field
|
||||||
delete newRowErrors[key as string];
|
delete newRowErrors[key as string];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user