Fix item number not getting updated when applying template

This commit is contained in:
2025-03-22 20:55:34 -04:00
parent aacb3a2fd0
commit a51a48ce89
7 changed files with 428 additions and 155 deletions

View File

@@ -28,6 +28,7 @@ interface UpcValidationTableAdapterProps<T extends string> {
validatingRows: Set<number> validatingRows: Set<number>
getItemNumber: (rowIndex: number) => string | undefined getItemNumber: (rowIndex: number) => string | undefined
} }
itemNumbers?: Map<number, string>
} }
/** /**
@@ -56,75 +57,79 @@ function UpcValidationTableAdapter<T extends string>({
rowSublines, rowSublines,
isLoadingLines, isLoadingLines,
isLoadingSublines, isLoadingSublines,
upcValidation upcValidation,
itemNumbers
}: UpcValidationTableAdapterProps<T>) { }: UpcValidationTableAdapterProps<T>) {
// Prepare the validation table with UPC data // Prepare the validation table with UPC data
const AdaptedTable = useMemo(() => React.memo((props: React.ComponentProps<typeof ValidationTable>) => {
// Create validatingCells set from validating rows, but only for item_number fields // Create combined validatingCells set from validating rows and external cells
// This ensures only the item_number column shows loading state during UPC validation const combinedValidatingCells = useMemo(() => {
const combinedValidatingCells = new Set<string>(); const combined = new Set<string>();
// Add UPC validation cells // Add UPC validation cells
upcValidation.validatingRows.forEach(rowIndex => { upcValidation.validatingRows.forEach(rowIndex => {
// Only mark the item_number cells as validating, NOT the UPC or supplier // Only mark the item_number cells as validating, NOT the UPC or supplier
combinedValidatingCells.add(`${rowIndex}-item_number`); combined.add(`${rowIndex}-item_number`);
}); });
// Add any other validating cells from state // Add any other validating cells from state
externalValidatingCells.forEach(cellKey => { externalValidatingCells.forEach(cellKey => {
combinedValidatingCells.add(cellKey); combined.add(cellKey);
}); });
// Convert the Map to the expected format for the ValidationTable return combined;
// Create a new Map from the item numbers to ensure proper typing }, [upcValidation.validatingRows, externalValidatingCells]);
const itemNumbersMap = new Map<number, string>();
// Merge the item numbers with the data for display purposes only // Create a consolidated item numbers map from all sources
const enhancedData = props.data.map((row: any, index: number) => { const consolidatedItemNumbers = useMemo(() => {
const itemNumber = upcValidation.getItemNumber(index); const result = new Map<number, string>();
if (itemNumber) {
// Add to our map for proper prop passing
itemNumbersMap.set(index, itemNumber);
return { // First add from itemNumbers directly - this is the source of truth for template applications
...row, if (itemNumbers) {
item_number: itemNumber // Log all numbers for debugging
}; console.log(`[ADAPTER-DEBUG] Received itemNumbers map with ${itemNumbers.size} entries`);
itemNumbers.forEach((itemNumber, rowIndex) => {
console.log(`[ADAPTER-DEBUG] Adding item number for row ${rowIndex} from itemNumbers: ${itemNumber}`);
result.set(rowIndex, itemNumber);
});
} }
return row;
});
// Create a Map for upcValidationResults with the same structure expected by ValidationTable // For each row, ensure we have the most up-to-date item number
const upcValidationResultsMap = new Map<number, { itemNumber: string }>();
// Populate with any item numbers we have from validation
data.forEach((_, index) => { data.forEach((_, index) => {
// Check if upcValidation has an item number for this row
const itemNumber = upcValidation.getItemNumber(index); const itemNumber = upcValidation.getItemNumber(index);
if (itemNumber) { if (itemNumber) {
upcValidationResultsMap.set(index, { itemNumber }); console.log(`[ADAPTER-DEBUG] Adding item number for row ${index} from upcValidation: ${itemNumber}`);
result.set(index, itemNumber);
}
// Also check if it's directly in the data
const dataItemNumber = data[index].item_number;
if (dataItemNumber && !result.has(index)) {
console.log(`[ADAPTER-DEBUG] Adding item number for row ${index} from data: ${dataItemNumber}`);
result.set(index, dataItemNumber);
} }
}); });
return ( return result;
<ValidationTable }, [data, itemNumbers, upcValidation]);
{...props}
data={enhancedData} // Create upcValidationResults map using the consolidated item numbers
validatingCells={combinedValidatingCells} const upcValidationResults = useMemo(() => {
itemNumbers={itemNumbersMap} const results = new Map<number, { itemNumber: string }>();
isLoadingTemplates={isLoadingTemplates}
copyDown={copyDown} // Populate with our consolidated item numbers
rowProductLines={rowProductLines} consolidatedItemNumbers.forEach((itemNumber, rowIndex) => {
rowSublines={rowSublines} results.set(rowIndex, { itemNumber });
isLoadingLines={isLoadingLines} });
isLoadingSublines={isLoadingSublines}
upcValidationResults={upcValidationResultsMap} return results;
/> }, [consolidatedItemNumbers]);
);
}), [upcValidation.validatingRows, upcValidation.getItemNumber, isLoadingTemplates, copyDown, externalValidatingCells, rowProductLines, rowSublines, isLoadingLines, isLoadingSublines]);
// Render the validation table with the provided props and UPC data // Render the validation table with the provided props and UPC data
return ( return (
<AdaptedTable <ValidationTable
data={data} data={data}
fields={fields} fields={fields}
rowSelection={rowSelection} rowSelection={rowSelection}
@@ -137,11 +142,11 @@ function UpcValidationTableAdapter<T extends string>({
templates={templates} templates={templates}
applyTemplate={applyTemplate} applyTemplate={applyTemplate}
getTemplateDisplayText={getTemplateDisplayText} getTemplateDisplayText={getTemplateDisplayText}
validatingCells={new Set()} validatingCells={combinedValidatingCells}
itemNumbers={new Map()} itemNumbers={consolidatedItemNumbers}
isLoadingTemplates={isLoadingTemplates} isLoadingTemplates={isLoadingTemplates}
copyDown={copyDown} copyDown={copyDown}
upcValidationResults={new Map<number, { itemNumber: string }>()} upcValidationResults={upcValidationResults}
rowProductLines={rowProductLines} rowProductLines={rowProductLines}
rowSublines={rowSublines} rowSublines={rowSublines}
isLoadingLines={isLoadingLines} isLoadingLines={isLoadingLines}

View File

@@ -293,8 +293,18 @@ const ValidationCell = React.memo(({
// Use the CopyDown context // Use the CopyDown context
const copyDownContext = React.useContext(CopyDownContext); const copyDownContext = React.useContext(CopyDownContext);
// Display value prioritizes itemNumber if available (for item_number fields) // CRITICAL FIX: For item_number fields, always prioritize the itemNumber prop over the value
const displayValue = fieldKey === 'item_number' && itemNumber ? itemNumber : value; // This ensures that when the itemNumber changes, the display value changes
let displayValue;
if (fieldKey === 'item_number' && itemNumber) {
// Always log when an item_number field is rendered to help debug
console.log(`[VC-DEBUG] ValidationCell rendering item_number for row=${rowIndex} with itemNumber=${itemNumber}, value=${value}`);
// Prioritize itemNumber prop for item_number fields
displayValue = itemNumber;
} else {
displayValue = value;
}
// Use the optimized processErrors function to avoid redundant filtering // Use the optimized processErrors function to avoid redundant filtering
const { const {

View File

@@ -58,7 +58,10 @@ const ValidationContainer = <T extends string>({
loadTemplates, loadTemplates,
setData, setData,
fields, fields,
isLoadingTemplates } = validationState isLoadingTemplates,
validatingCells,
setValidatingCells
} = validationState
// Use product lines fetching hook // Use product lines fetching hook
const { const {
@@ -70,9 +73,6 @@ const ValidationContainer = <T extends string>({
fetchSublines fetchSublines
} = useProductLinesFetching(data); } = useProductLinesFetching(data);
// Add state for tracking cells in loading state
const [validatingCells, setValidatingCells] = useState<Set<string>>(new Set());
// Use UPC validation hook // Use UPC validation hook
const upcValidation = useUpcValidation(data, setData); const upcValidation = useUpcValidation(data, setData);
@@ -959,6 +959,7 @@ const ValidationContainer = <T extends string>({
isLoadingLines={isLoadingLines} isLoadingLines={isLoadingLines}
isLoadingSublines={isLoadingSublines} isLoadingSublines={isLoadingSublines}
upcValidation={upcValidation} upcValidation={upcValidation}
itemNumbers={upcValidation.itemNumbers}
/> />
); );
}, [ }, [

View File

@@ -138,11 +138,15 @@ const MemoizedCell = React.memo(({
/> />
); );
}, (prev, next) => { }, (prev, next) => {
// CRITICAL FIX: Never memoize item_number cells - always re-render them
if (prev.fieldKey === 'item_number') {
return false; // Never skip re-renders for item_number cells
}
// Optimize the memo comparison function for better performance // Optimize the memo comparison function for better performance
// Only re-render if these essential props change // Only re-render if these essential props change
const valueEqual = prev.value === next.value; const valueEqual = prev.value === next.value;
const isValidatingEqual = prev.isValidating === next.isValidating; const isValidatingEqual = prev.isValidating === next.isValidating;
const itemNumberEqual = prev.itemNumber === next.itemNumber;
// Shallow equality check for errors array // Shallow equality check for errors array
const errorsEqual = prev.errors === next.errors || ( const errorsEqual = prev.errors === next.errors || (
@@ -161,7 +165,7 @@ const MemoizedCell = React.memo(({
); );
// Skip checking for props that rarely change // Skip checking for props that rarely change
return valueEqual && isValidatingEqual && errorsEqual && optionsEqual && itemNumberEqual; return valueEqual && isValidatingEqual && errorsEqual && optionsEqual;
}); });
MemoizedCell.displayName = 'MemoizedCell'; MemoizedCell.displayName = 'MemoizedCell';
@@ -335,10 +339,28 @@ const ValidationTable = <T extends string>({
return isValidatingUpc?.(rowIndex) || validatingUpcRows.includes(rowIndex); return isValidatingUpc?.(rowIndex) || validatingUpcRows.includes(rowIndex);
}, [isValidatingUpc, validatingUpcRows]); }, [isValidatingUpc, validatingUpcRows]);
// Use upcValidationResults for display // Use upcValidationResults for display, prioritizing the most recent values
const getRowUpcResult = useCallback((rowIndex: number) => { const getRowUpcResult = useCallback((rowIndex: number) => {
return upcValidationResults?.get(rowIndex)?.itemNumber; // ALWAYS get from the data array directly - most authoritative source
}, [upcValidationResults]); const rowData = data[rowIndex];
if (rowData && rowData.item_number) {
return rowData.item_number;
}
// Maps are only backup sources when data doesn't have a value
const itemNumberFromMap = itemNumbers.get(rowIndex);
if (itemNumberFromMap) {
return itemNumberFromMap;
}
// Last resort - upcValidationResults
const upcResult = upcValidationResults?.get(rowIndex)?.itemNumber;
if (upcResult) {
return upcResult;
}
return undefined;
}, [data, itemNumbers, upcValidationResults]);
// Memoize field columns with stable handlers // Memoize field columns with stable handlers
const fieldColumns = useMemo(() => fields.map((field): ColumnDef<RowData<T>, any> | null => { const fieldColumns = useMemo(() => fields.map((field): ColumnDef<RowData<T>, any> | null => {
@@ -413,16 +435,32 @@ const ValidationTable = <T extends string>({
} }
// Get item number from UPC validation results if available // CRITICAL CHANGE: Get item number directly from row data first for item_number fields
let itemNumber = itemNumbers.get(row.index); let itemNumber;
if (!itemNumber && fieldKey === 'item_number') { if (fieldKey === 'item_number') {
// Check directly in row data first - this is the most accurate source
const directValue = row.original[fieldKey];
if (directValue) {
itemNumber = directValue;
} else {
// Fall back to centralized getter that checks all sources
itemNumber = getRowUpcResult(row.index); itemNumber = getRowUpcResult(row.index);
} }
}
// CRITICAL: For item_number fields, create a unique key that includes the itemNumber value
// This forces a complete re-render when the itemNumber changes
const cellKey = fieldKey === 'item_number'
? `cell-${row.index}-${fieldKey}-${itemNumber || 'empty'}-${Date.now()}` // Force re-render on every render cycle for item_number
: `cell-${row.index}-${fieldKey}`;
return ( return (
<MemoizedCell <MemoizedCell
key={cellKey} // CRITICAL: Add key to force re-render when itemNumber changes
field={fieldWithType as Field<string>} field={fieldWithType as Field<string>}
value={row.original[field.key as keyof typeof row.original]} value={fieldKey === 'item_number' && row.original[field.key]
? row.original[field.key] // Use direct value from row data
: row.original[field.key as keyof typeof row.original]}
onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)} onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)}
errors={cellErrors} errors={cellErrors}
isValidating={isLoading} isValidating={isLoading}

View File

@@ -15,7 +15,8 @@ export const useTemplateManagement = <T extends string>(
upcValidation: { upcValidation: {
validateUpc: (rowIndex: number, supplierId: string, upcValue: string) => Promise<{success: boolean, itemNumber?: string}>, validateUpc: (rowIndex: number, supplierId: string, upcValue: string) => Promise<{success: boolean, itemNumber?: string}>,
applyItemNumbersToData: (onApplied?: (updatedRowIds: number[]) => void) => void applyItemNumbersToData: (onApplied?: (updatedRowIds: number[]) => void) => void
} },
setValidatingCells?: React.Dispatch<React.SetStateAction<Set<string>>>
) => { ) => {
// Template state // Template state
const [templates, setTemplates] = useState<Template[]>([]); const [templates, setTemplates] = useState<Template[]>([]);
@@ -227,6 +228,12 @@ export const useTemplateManagement = <T extends string>(
batchStatuses.set(index, "validated"); batchStatuses.set(index, "validated");
}); });
// Check which rows need UPC validation
const upcValidationRows = validRowIndexes.filter((rowIndex) => {
const row = newData[rowIndex];
return row && row.upc && row.supplier;
});
// Perform a single update for all rows // Perform a single update for all rows
setData(newData); setData(newData);
@@ -259,59 +266,180 @@ export const useTemplateManagement = <T extends string>(
toast.success(`Template applied to ${validRowIndexes.length} rows`); toast.success(`Template applied to ${validRowIndexes.length} rows`);
} }
// Check which rows need UPC validation // Reset template application flag to allow validation
const upcValidationRows = validRowIndexes.filter((rowIndex) => { isApplyingTemplateRef.current = false;
const row = newData[rowIndex];
return row && row.upc && row.supplier;
});
// Validate UPCs for rows that have both UPC and supplier // If there are rows with both UPC and supplier, validate them
if (upcValidationRows.length > 0) { if (upcValidationRows.length > 0) {
console.log( console.log(`Validating UPCs for ${upcValidationRows.length} rows after template application`);
`Validating UPCs for ${upcValidationRows.length} rows after template application`
);
// Schedule UPC validation for the next tick to allow UI to update first // Process each row sequentially - this mimics the exact manual edit behavior
setTimeout(() => { const processNextValidation = (index = 0) => {
// Track successful validations if (index >= upcValidationRows.length) {
const validationPromises: Promise<{success: boolean, itemNumber?: string}>[] = []; return; // All rows processed
upcValidationRows.forEach((rowIndex) => {
const row = newData[rowIndex];
if (row && row.upc && row.supplier) {
// FIXED: Directly call validateUpc instead of validateRow
console.log(`Directly calling validateUpc for row ${rowIndex} with UPC ${row.upc} and supplier ${row.supplier}`);
const validationPromise = upcValidation.validateUpc(rowIndex, row.supplier.toString(), row.upc.toString());
validationPromises.push(validationPromise);
} }
const rowIndex = upcValidationRows[index];
const row = newData[rowIndex];
if (row && row.supplier && row.upc) {
// The EXACT implementation from handleUpdateRow when supplier is edited manually:
// 1. Mark the item_number cell as being validated - THIS IS CRITICAL FOR LOADING STATE
const cellKey = `${rowIndex}-item_number`;
// Clear validation errors for this field
setValidationErrors(prev => {
const newErrors = new Map(prev);
if (newErrors.has(rowIndex)) {
const rowErrors = { ...newErrors.get(rowIndex) };
if (rowErrors.item_number) {
delete rowErrors.item_number;
}
newErrors.set(rowIndex, rowErrors);
}
return newErrors;
}); });
// After all validations complete, apply item numbers // Set loading state - using setValidatingCells from props
if (validationPromises.length > 0) { if (setValidatingCells) {
Promise.all(validationPromises) setValidatingCells(prev => {
.then(results => { const newSet = new Set(prev);
const successCount = results.filter(r => r.success).length; newSet.add(cellKey);
console.log(`${successCount}/${validationPromises.length} UPC validations succeeded`); return newSet;
});
}
// Apply item numbers to the data // Validate UPC for this row
upcValidation.applyItemNumbersToData(updatedRowIds => { upcValidation.validateUpc(rowIndex, row.supplier.toString(), row.upc.toString())
console.log(`Applied item numbers to ${updatedRowIds.length} rows`); .then(result => {
if (result.success && result.itemNumber) {
// CRITICAL FIX: Directly update data with the item number to ensure immediate UI update
setData(prevData => {
const newData = [...prevData];
// After applying item numbers, trigger validation for those rows // Update this specific row with the item number
updatedRowIds.forEach(rowIndex => { if (rowIndex >= 0 && rowIndex < newData.length) {
newData[rowIndex] = {
...newData[rowIndex],
item_number: result.itemNumber
};
}
return newData;
});
// Also trigger other relevant updates
upcValidation.applyItemNumbersToData();
// Mark for revalidation after item numbers are updated
setTimeout(() => {
// Validate the row EXACTLY like in manual edit
validateRow(rowIndex, 'item_number'); validateRow(rowIndex, 'item_number');
// CRITICAL FIX: Make one final check to ensure data is correct
setTimeout(() => {
// Get the current item number from the data
const currentItemNumber = (() => {
try {
const dataAtThisPointInTime = data[rowIndex];
return dataAtThisPointInTime?.item_number;
} catch (e) {
return undefined;
}
})();
// If the data is wrong at this point, fix it directly
if (currentItemNumber !== result.itemNumber) {
// Directly update the data to fix the issue
setData(dataRightNow => {
const fixedData = [...dataRightNow];
if (rowIndex >= 0 && rowIndex < fixedData.length) {
fixedData[rowIndex] = {
...fixedData[rowIndex],
item_number: result.itemNumber
};
}
return fixedData;
}); });
// Then do a force update after a brief delay
setTimeout(() => {
setData(currentData => {
// Critical fix: ensure the item number is correct
if (currentData[rowIndex] && currentData[rowIndex].item_number !== result.itemNumber) {
// Create a completely new array with the correct item number
const fixedData = [...currentData];
fixedData[rowIndex] = {
...fixedData[rowIndex],
item_number: result.itemNumber
};
return fixedData;
}
// Create a completely new array
return [...currentData];
}); });
}, 20);
} else {
// Item number is already correct, just do the force update
setData(currentData => {
// Create a completely new array
return [...currentData];
});
}
}, 50);
// Clear loading state
if (setValidatingCells) {
setValidatingCells(prev => {
const newSet = new Set(prev);
newSet.delete(cellKey);
return newSet;
});
}
// Continue to next row after validation is complete
setTimeout(() => processNextValidation(index + 1), 100);
}, 100);
} else {
// Clear loading state on failure
if (setValidatingCells) {
setValidatingCells(prev => {
const newSet = new Set(prev);
newSet.delete(cellKey);
return newSet;
});
}
// Continue to next row if validation fails
setTimeout(() => processNextValidation(index + 1), 100);
}
}) })
.catch(err => { .catch(err => {
console.error("Error in UPC validation batch:", err); console.error(`Error validating UPC for row ${rowIndex}:`, err);
// Clear loading state on error
if (setValidatingCells) {
setValidatingCells(prev => {
const newSet = new Set(prev);
newSet.delete(cellKey);
return newSet;
}); });
} }
}, 100);
}
// Reset the template application flag // Continue to next row despite error
isApplyingTemplateRef.current = false; setTimeout(() => processNextValidation(index + 1), 100);
});
} else {
// Skip this row and continue to the next
processNextValidation(index + 1);
}
};
// Start processing validations
processNextValidation();
}
}, },
[ [
data, data,
@@ -321,6 +449,7 @@ export const useTemplateManagement = <T extends string>(
setRowValidationStatus, setRowValidationStatus,
validateRow, validateRow,
upcValidation, upcValidation,
setValidatingCells
] ]
); );

View File

@@ -49,10 +49,40 @@ export const useUpcValidation = (
// Update item number // Update item number
const updateItemNumber = useCallback((rowIndex: number, itemNumber: string) => { const updateItemNumber = useCallback((rowIndex: number, itemNumber: string) => {
console.log(`Setting item number for row ${rowIndex} to ${itemNumber}`); // CRITICAL: Update BOTH the data state and the ref
// First, update the data directly to ensure UI consistency
setData(prevData => {
// Create a new copy of the data
const newData = [...prevData];
// Only update if the row exists
if (rowIndex >= 0 && rowIndex < newData.length) {
// First, we need a new object reference for the row to force a re-render
newData[rowIndex] = {
...newData[rowIndex],
item_number: itemNumber
};
}
return newData;
});
// Also update the itemNumbers map AFTER the data is updated
// This ensures the map reflects the current state of the data
setTimeout(() => {
// Update the ref with the same value
validationStateRef.current.itemNumbers.set(rowIndex, itemNumber); validationStateRef.current.itemNumbers.set(rowIndex, itemNumber);
setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers));
}, []); // CRITICAL: Force a React state update to ensure all components re-render
// Created a brand new Map object to ensure React detects the change
const newItemNumbersMap = new Map(validationStateRef.current.itemNumbers);
setItemNumberUpdates(newItemNumbersMap);
// Force an immediate React render cycle by triggering state updates
setValidatingCellKeys(new Set(validationStateRef.current.validatingCells));
setValidatingRows(new Set(validationStateRef.current.validatingRows));
}, 0);
}, [setData]);
// Mark a row as being validated // Mark a row as being validated
const startValidatingRow = useCallback((rowIndex: number) => { const startValidatingRow = useCallback((rowIndex: number) => {
@@ -132,11 +162,22 @@ export const useUpcValidation = (
); );
previousKeys.forEach(key => validationStateRef.current.activeValidations.delete(key)); previousKeys.forEach(key => validationStateRef.current.activeValidations.delete(key));
// Start validation - track this with the ref to avoid race conditions // Log validation start to help debug template issues
startValidatingRow(rowIndex); console.log(`[UPC-DEBUG] Starting UPC validation for row ${rowIndex} with supplier ${supplierId}, upc ${upcValue}`);
startValidatingCell(rowIndex, 'item_number');
console.log(`Validating UPC: rowIndex=${rowIndex}, supplierId=${supplierId}, upc=${upcValue}`); // IMPORTANT: Set validation state using setState to FORCE UI updates
validationStateRef.current.validatingRows.add(rowIndex);
setValidatingRows(new Set(validationStateRef.current.validatingRows));
setIsValidatingUpc(true);
// Start cell validation and explicitly update UI via setState
const cellKey = getCellKey(rowIndex, 'item_number');
validationStateRef.current.validatingCells.add(cellKey);
setValidatingCellKeys(new Set(validationStateRef.current.validatingCells));
console.log(`[UPC-DEBUG] Set loading state for row ${rowIndex}, cell key ${cellKey}`);
console.log(`[UPC-DEBUG] Current validating rows: ${Array.from(validationStateRef.current.validatingRows).join(', ')}`);
console.log(`[UPC-DEBUG] Current validating cells: ${Array.from(validationStateRef.current.validatingCells).join(', ')}`);
try { try {
// Create a unique key for this validation to track it // Create a unique key for this validation to track it
@@ -157,18 +198,43 @@ export const useUpcValidation = (
}); });
// Fetch the product by UPC // Fetch the product by UPC
console.log(`[UPC-DEBUG] Fetching product data for UPC ${upcValue} with supplier ${supplierId}`);
const product = await fetchProductByUpc(supplierId, upcValue); const product = await fetchProductByUpc(supplierId, upcValue);
console.log(`[UPC-DEBUG] Fetch complete for row ${rowIndex}, success: ${!product.error}`);
// Check if this validation is still relevant (hasn't been superseded by another) // Check if this validation is still relevant (hasn't been superseded by another)
if (!validationStateRef.current.activeValidations.has(validationKey)) { if (!validationStateRef.current.activeValidations.has(validationKey)) {
console.log(`Validation ${validationKey} was cancelled`); console.log(`[UPC-DEBUG] Validation ${validationKey} was cancelled`);
return { success: false }; return { success: false };
} }
// Extract the item number from the API response - check for !error since API returns { error: boolean, data: any } // Extract the item number from the API response - check for !error since API returns { error: boolean, data: any }
if (product && !product.error && product.data?.itemNumber) { if (product && !product.error && product.data?.itemNumber) {
// Store this validation result console.log(`[UPC-DEBUG] Got item number for row ${rowIndex}: ${product.data.itemNumber}`);
updateItemNumber(rowIndex, product.data.itemNumber);
// CRITICAL FIX: Directly update the data with the new item number first
setData(prevData => {
const newData = [...prevData];
if (rowIndex >= 0 && rowIndex < newData.length) {
// This should happen before updating the map
newData[rowIndex] = {
...newData[rowIndex],
item_number: product.data.itemNumber
};
}
return newData;
});
// Then, update the map to match what's now in the data
validationStateRef.current.itemNumbers.set(rowIndex, product.data.itemNumber);
// CRITICAL: Force a React state update to ensure all components re-render
// Created a brand new Map object to ensure React detects the change
const newItemNumbersMap = new Map(validationStateRef.current.itemNumbers);
setItemNumberUpdates(newItemNumbersMap);
// Force a shallow copy of the itemNumbers map to trigger useEffect dependencies
validationStateRef.current.itemNumbers = new Map(validationStateRef.current.itemNumbers);
return { return {
success: true, success: true,
@@ -176,7 +242,7 @@ export const useUpcValidation = (
}; };
} else { } else {
// No item number found but validation was still attempted // No item number found but validation was still attempted
console.log(`No item number found for UPC ${upcValue}`); console.log(`[UPC-DEBUG] No item number found for UPC ${upcValue}`);
// Clear any existing item number to show validation was attempted and failed // Clear any existing item number to show validation was attempted and failed
if (validationStateRef.current.itemNumbers.has(rowIndex)) { if (validationStateRef.current.itemNumbers.has(rowIndex)) {
@@ -187,58 +253,71 @@ export const useUpcValidation = (
return { success: false }; return { success: false };
} }
} catch (error) { } catch (error) {
console.error('Error validating UPC:', error); console.error('[UPC-DEBUG] Error validating UPC:', error);
return { success: false }; return { success: false };
} finally { } finally {
// End validation // End validation - FORCE UI update by using setState directly
stopValidatingRow(rowIndex); console.log(`[UPC-DEBUG] Ending validation for row ${rowIndex}`);
stopValidatingCell(rowIndex, 'item_number');
validationStateRef.current.validatingRows.delete(rowIndex);
setValidatingRows(new Set(validationStateRef.current.validatingRows));
if (validationStateRef.current.validatingRows.size === 0) {
setIsValidatingUpc(false);
} }
}, [fetchProductByUpc, updateItemNumber, startValidatingCell, stopValidatingCell, startValidatingRow, stopValidatingRow, setData]);
// Apply item numbers to data validationStateRef.current.validatingCells.delete(cellKey);
const applyItemNumbersToData = useCallback((onApplied?: (updatedRowIds: number[]) => void) => { setValidatingCellKeys(new Set(validationStateRef.current.validatingCells));
// Create a copy of the current item numbers map to avoid race conditions
const currentItemNumbers = new Map(validationStateRef.current.itemNumbers);
// Only apply if we have any item numbers console.log(`[UPC-DEBUG] Cleared loading state for row ${rowIndex}`);
if (currentItemNumbers.size === 0) return; console.log(`[UPC-DEBUG] Updated validating rows: ${Array.from(validationStateRef.current.validatingRows).join(', ')}`);
console.log(`[UPC-DEBUG] Updated validating cells: ${Array.from(validationStateRef.current.validatingCells).join(', ')}`);
}
}, [fetchProductByUpc, updateItemNumber, setData]);
// Track updated row indices to pass to callback // Apply all pending item numbers to the data state
const updatedRowIndices: number[] = []; const applyItemNumbersToData = useCallback((callback?: (updatedRows: number[]) => void) => {
// Skip if we have nothing to apply
if (validationStateRef.current.itemNumbers.size === 0) {
if (callback) callback([]);
return;
}
// Log for debugging // Gather all row IDs that will be updated
console.log(`Applying ${currentItemNumbers.size} item numbers to data`); const rowIds: number[] = [];
// Update the data state with all item numbers
setData(prevData => { setData(prevData => {
// Create a new copy of the data
const newData = [...prevData]; const newData = [...prevData];
// Update each row with its item number without affecting other fields // Apply each item number to the data
currentItemNumbers.forEach((itemNumber, rowIndex) => { validationStateRef.current.itemNumbers.forEach((itemNumber, rowIndex) => {
if (rowIndex < newData.length) { // Ensure row exists and value has actually changed
console.log(`Setting item_number for row ${rowIndex} to ${itemNumber}`); if (rowIndex >= 0 && rowIndex < newData.length &&
newData[rowIndex]?.item_number !== itemNumber) {
// Only update the item_number field, leaving other fields unchanged // Create a new row object to force re-rendering
newData[rowIndex] = { newData[rowIndex] = {
...newData[rowIndex], ...newData[rowIndex],
item_number: itemNumber item_number: itemNumber
}; };
// Track which rows were updated // Track which row was updated for the callback
updatedRowIndices.push(rowIndex); rowIds.push(rowIndex);
} }
}); });
return newData; return newData;
}); });
// Call the callback if provided, after state updates are processed // Force a re-render by updating React state
if (onApplied && updatedRowIndices.length > 0) {
// Use setTimeout to ensure this happens after the state update
setTimeout(() => { setTimeout(() => {
onApplied(updatedRowIndices); setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers));
}, 100); // Use 100ms to ensure the data update is fully processed }, 0);
// Call the callback with the updated row IDs
if (callback) {
callback(rowIds);
} }
}, [setData]); }, [setData]);
@@ -405,6 +484,9 @@ export const useUpcValidation = (
getItemNumber, getItemNumber,
applyItemNumbersToData, applyItemNumbersToData,
// CRITICAL: Expose the itemNumbers map directly
itemNumbers: validationStateRef.current.itemNumbers,
// Initialization state // Initialization state
initialValidationDone: initialUpcValidationDoneRef.current initialValidationDone: initialUpcValidationDoneRef.current
}; };

View File

@@ -88,6 +88,9 @@ export const useValidationState = <T extends string>({
Map<number, "pending" | "validating" | "validated" | "error"> Map<number, "pending" | "validating" | "validated" | "error">
>(new Map()); >(new Map());
// Add state for tracking cells in loading state
const [validatingCells, setValidatingCells] = useState<Set<string>>(new Set());
const initialValidationDoneRef = useRef(false); const initialValidationDoneRef = useRef(false);
const isValidatingRef = useRef(false); const isValidatingRef = useRef(false);
@@ -119,7 +122,8 @@ export const useValidationState = <T extends string>({
setRowValidationStatus, setRowValidationStatus,
validateRow, validateRow,
isApplyingTemplateRef, isApplyingTemplateRef,
upcValidation upcValidation,
setValidatingCells
); );
// Use filter management hook // Use filter management hook
@@ -673,6 +677,10 @@ export const useValidationState = <T extends string>({
validateRow, validateRow,
hasErrors, hasErrors,
// CRITICAL: Export validatingCells to make it available to ValidationContainer
validatingCells,
setValidatingCells,
// Row selection // Row selection
rowSelection, rowSelection,
setRowSelection, setRowSelection,