Move UPC validation out of ValidationContainer, add code lines tracking
This commit is contained in:
@@ -15,6 +15,7 @@ import { SearchProductTemplateDialog } from '@/components/templates/SearchProduc
|
||||
import { TemplateForm } from '@/components/templates/TemplateForm'
|
||||
import axios from 'axios'
|
||||
import { RowSelectionState } from '@tanstack/react-table'
|
||||
import { useUpcValidation } from '../hooks/useUpcValidation'
|
||||
|
||||
/**
|
||||
* ValidationContainer component - the main wrapper for the validation step
|
||||
@@ -72,50 +73,17 @@ const ValidationContainer = <T extends string>({
|
||||
const [companyLinesCache, setCompanyLinesCache] = useState<Record<string, any[]>>({});
|
||||
const [lineSublineCache, setLineSublineCache] = useState<Record<string, any[]>>({});
|
||||
|
||||
// Add UPC validation state
|
||||
const [, setIsValidatingUpc] = useState(false);
|
||||
const [validatingUpcRows, setValidatingUpcRows] = useState<Set<number>>(new Set());
|
||||
|
||||
// Add state for tracking cells in loading state
|
||||
const [validatingCells, setValidatingCells] = useState<Set<string>>(new Set());
|
||||
|
||||
// Store item numbers in a separate state to avoid updating the main data
|
||||
const [itemNumbers, setItemNumbers] = useState<Record<number, string>>({});
|
||||
|
||||
// Cache for UPC validation results
|
||||
const processedUpcMapRef = useRef(new Map<string, string>());
|
||||
const initialUpcValidationDoneRef = useRef(false);
|
||||
// Use UPC validation hook
|
||||
const upcValidation = useUpcValidation<T>(data, setData);
|
||||
|
||||
// Function to check if a specific row is being validated - memoized
|
||||
const isRowValidatingUpc = useCallback((rowIndex: number): boolean => {
|
||||
return validatingUpcRows.has(rowIndex);
|
||||
}, [validatingUpcRows]);
|
||||
const isRowValidatingUpc = upcValidation.isRowValidatingUpc;
|
||||
|
||||
// Apply all pending updates to the data state
|
||||
const applyItemNumbersToData = useCallback(() => {
|
||||
if (Object.keys(itemNumbers).length === 0) return;
|
||||
|
||||
setData(prevData => {
|
||||
const newData = [...prevData];
|
||||
|
||||
// Apply all item numbers without changing other data
|
||||
Object.entries(itemNumbers).forEach(([indexStr, itemNumber]) => {
|
||||
const index = parseInt(indexStr);
|
||||
if (index >= 0 && index < newData.length) {
|
||||
// Only update the item_number field and leave everything else unchanged
|
||||
newData[index] = {
|
||||
...newData[index],
|
||||
item_number: itemNumber
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return newData;
|
||||
});
|
||||
|
||||
// Clear the item numbers state after applying
|
||||
setItemNumbers({});
|
||||
}, [setData, itemNumbers]);
|
||||
const applyItemNumbersToData = upcValidation.applyItemNumbersToData;
|
||||
|
||||
// Function to fetch product lines for a specific company - memoized
|
||||
const fetchProductLines = useCallback(async (rowIndex: string | number, companyId: string) => {
|
||||
@@ -223,170 +191,6 @@ const ValidationContainer = <T extends string>({
|
||||
}
|
||||
}, [lineSublineCache]);
|
||||
|
||||
// Function to validate UPC with the API - memoized
|
||||
const validateUpc = useCallback(async (rowIndex: number, supplierId: string, upcValue: string): Promise<{ success: boolean, itemNumber?: string }> => {
|
||||
try {
|
||||
// Skip if either value is missing
|
||||
if (!supplierId || !upcValue) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Add logging to help debug
|
||||
console.log(`Validating UPC for row ${rowIndex}. Supplier ID: ${supplierId}, UPC: ${upcValue}`);
|
||||
|
||||
// Check if we've already validated this UPC/supplier combination
|
||||
const cacheKey = `${supplierId}-${upcValue}`;
|
||||
if (processedUpcMapRef.current.has(cacheKey)) {
|
||||
const cachedItemNumber = processedUpcMapRef.current.get(cacheKey);
|
||||
|
||||
if (cachedItemNumber) {
|
||||
// Just update the item numbers state, not the main data
|
||||
setItemNumbers(prev => ({
|
||||
...prev,
|
||||
[rowIndex]: cachedItemNumber
|
||||
}));
|
||||
|
||||
return { success: true, itemNumber: cachedItemNumber };
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Make API call to validate UPC - ensure we're using supplierId (not company)
|
||||
const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`);
|
||||
|
||||
// Process the response
|
||||
if (response.status === 409) {
|
||||
// UPC already exists - show validation error
|
||||
|
||||
// We need to trigger validation for this row to update the validation errors
|
||||
// This will update the validationErrors Map in useValidationState
|
||||
const row = data[rowIndex];
|
||||
if (row) {
|
||||
// Update the UPC field to trigger validation
|
||||
updateRow(rowIndex, 'upc' as T, row.upc);
|
||||
|
||||
// We also need to manually add the error to the validation errors
|
||||
// But we don't have direct access to setValidationErrors
|
||||
// So we'll use a workaround by updating the row data
|
||||
setData(prevData => {
|
||||
const newData = [...prevData];
|
||||
// We're only updating the row to trigger validation
|
||||
// The actual error will be handled by the validation system
|
||||
return newData;
|
||||
});
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
} else if (response.ok) {
|
||||
// Successful validation - update item number
|
||||
const responseData = await response.json();
|
||||
|
||||
if (responseData.success && responseData.itemNumber) {
|
||||
// Store in cache
|
||||
processedUpcMapRef.current.set(cacheKey, responseData.itemNumber);
|
||||
|
||||
// Update the item numbers state, not the main data
|
||||
setItemNumbers(prev => ({
|
||||
...prev,
|
||||
[rowIndex]: responseData.itemNumber
|
||||
}));
|
||||
|
||||
// Clear any UPC errors by triggering validation
|
||||
const row = data[rowIndex];
|
||||
if (row) {
|
||||
// Update the UPC field to trigger validation
|
||||
updateRow(rowIndex, 'upc' as T, row.upc);
|
||||
}
|
||||
|
||||
return { success: true, itemNumber: responseData.itemNumber };
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
} catch (error) {
|
||||
console.error(`Error validating UPC for row ${rowIndex}:`, error);
|
||||
return { success: false };
|
||||
}
|
||||
}, [data, updateRow, setData]);
|
||||
|
||||
// Apply item numbers when they're available
|
||||
useEffect(() => {
|
||||
// Apply item numbers immediately if there are any
|
||||
if (Object.keys(itemNumbers).length > 0) {
|
||||
applyItemNumbersToData();
|
||||
}
|
||||
}, [itemNumbers, applyItemNumbersToData]);
|
||||
|
||||
// Optimized batch validation function - memoized
|
||||
const validateAllUPCs = useCallback(async () => {
|
||||
// Skip if we've already done the initial validation
|
||||
if (initialUpcValidationDoneRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark that we've done the initial validation
|
||||
initialUpcValidationDoneRef.current = true;
|
||||
|
||||
console.log('Starting UPC validation...');
|
||||
|
||||
// Set validation state
|
||||
setIsValidatingUpc(true);
|
||||
|
||||
// Find all rows that have both supplier and UPC/barcode
|
||||
const rowsToValidate = data
|
||||
.map((row, index) => ({ row, index }))
|
||||
.filter(({ row }) => {
|
||||
const rowAny = row as Record<string, any>;
|
||||
const hasSupplier = rowAny.supplier;
|
||||
const hasUpc = rowAny.upc || rowAny.barcode;
|
||||
return hasSupplier && hasUpc;
|
||||
});
|
||||
|
||||
const totalRows = rowsToValidate.length;
|
||||
console.log(`Found ${totalRows} rows with both supplier and UPC`);
|
||||
|
||||
if (totalRows === 0) {
|
||||
setIsValidatingUpc(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all rows as being validated
|
||||
setValidatingUpcRows(new Set(rowsToValidate.map(({ index }) => index)));
|
||||
|
||||
try {
|
||||
// Process all rows in parallel without batching
|
||||
await Promise.all(
|
||||
rowsToValidate.map(async ({ row, index }) => {
|
||||
try {
|
||||
const rowAny = row as Record<string, any>;
|
||||
const supplierId = rowAny.supplier.toString();
|
||||
const upcValue = (rowAny.upc || rowAny.barcode).toString();
|
||||
|
||||
// Validate the UPC
|
||||
await validateUpc(index, supplierId, upcValue);
|
||||
|
||||
// Remove this row from the validating set
|
||||
setValidatingUpcRows(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(index);
|
||||
return newSet;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error processing row ${index}:`, error);
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error in validation:', error);
|
||||
} finally {
|
||||
// Reset validation state
|
||||
setIsValidatingUpc(false);
|
||||
setValidatingUpcRows(new Set());
|
||||
console.log('Completed UPC validation');
|
||||
}
|
||||
}, [data, validateUpc]);
|
||||
|
||||
// Enhanced updateRow function - memoized
|
||||
const enhancedUpdateRow = useCallback(async (rowIndex: number, fieldKey: T, value: any) => {
|
||||
// Process value before updating data
|
||||
@@ -478,30 +282,23 @@ const ValidationContainer = <T extends string>({
|
||||
if (rowDataAny.upc || rowDataAny.barcode) {
|
||||
const upcValue = rowDataAny.upc || rowDataAny.barcode;
|
||||
|
||||
// Run UPC validation immediately without timeout
|
||||
try {
|
||||
// Mark this row as being validated
|
||||
setValidatingUpcRows(prev => {
|
||||
// Mark the item_number cell as being validated
|
||||
setValidatingCells(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(rowIndex);
|
||||
newSet.add(`${rowIndex}-item_number`);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// Set global validation state
|
||||
setIsValidatingUpc(true);
|
||||
|
||||
// Use supplier ID (the value being set) to validate UPC
|
||||
await validateUpc(rowIndex, value.toString(), upcValue.toString());
|
||||
await upcValidation.validateUpc(rowIndex, value.toString(), upcValue.toString());
|
||||
} catch (error) {
|
||||
console.error('Error validating UPC:', error);
|
||||
} finally {
|
||||
// Always clean up validation state, even if there was an error
|
||||
setValidatingUpcRows(prev => {
|
||||
// Clear validation state for the item_number cell
|
||||
setValidatingCells(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(rowIndex);
|
||||
if (newSet.size === 0) {
|
||||
setIsValidatingUpc(false);
|
||||
}
|
||||
newSet.delete(`${rowIndex}-item_number`);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
@@ -547,36 +344,29 @@ const ValidationContainer = <T extends string>({
|
||||
if ((fieldKey === 'upc' || fieldKey === 'barcode') && value && rowData) {
|
||||
const rowDataAny = rowData as Record<string, any>;
|
||||
if (rowDataAny.supplier) {
|
||||
// Run UPC validation immediately without timeout
|
||||
try {
|
||||
// Mark this row as being validated
|
||||
setValidatingUpcRows(prev => {
|
||||
// Mark the item_number cell as being validated
|
||||
setValidatingCells(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(rowIndex);
|
||||
newSet.add(`${rowIndex}-item_number`);
|
||||
return newSet;
|
||||
});
|
||||
|
||||
// Set global validation state
|
||||
setIsValidatingUpc(true);
|
||||
|
||||
// Use supplier ID from the row data (NOT company ID) to validate UPC
|
||||
await validateUpc(rowIndex, rowDataAny.supplier.toString(), value.toString());
|
||||
// Use supplier ID from the row data to validate UPC
|
||||
await upcValidation.validateUpc(rowIndex, rowDataAny.supplier.toString(), value.toString());
|
||||
} catch (error) {
|
||||
console.error('Error validating UPC:', error);
|
||||
} finally {
|
||||
// Always clean up validation state, even if there was an error
|
||||
setValidatingUpcRows(prev => {
|
||||
// Clear validation state for the item_number cell
|
||||
setValidatingCells(prev => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(rowIndex);
|
||||
if (newSet.size === 0) {
|
||||
setIsValidatingUpc(false);
|
||||
}
|
||||
newSet.delete(`${rowIndex}-item_number`);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [data, filteredData, updateRow, fetchProductLines, fetchSublines, validateUpc, setData, companyLinesCache, lineSublineCache]);
|
||||
}, [data, filteredData, updateRow, fetchProductLines, fetchSublines, setData, companyLinesCache, lineSublineCache, upcValidation]);
|
||||
|
||||
// When data changes, fetch product lines and sublines for rows that have company/line values
|
||||
useEffect(() => {
|
||||
@@ -834,16 +624,16 @@ const ValidationContainer = <T extends string>({
|
||||
|
||||
}, [data, rowProductLines, rowSublines, companyLinesCache, lineSublineCache]);
|
||||
|
||||
// Validate UPCs on initial data load
|
||||
// Use UPC validation when data changes
|
||||
useEffect(() => {
|
||||
// Skip if there's no data or we've already done the validation
|
||||
if (data.length === 0 || initialUpcValidationDoneRef.current) return;
|
||||
// Skip if there's no data or already validated
|
||||
if (data.length === 0 || upcValidation.initialValidationDone) return;
|
||||
|
||||
// Run validation immediately without timeout
|
||||
validateAllUPCs();
|
||||
upcValidation.validateAllUPCs();
|
||||
|
||||
// No cleanup needed since we're not using a timer
|
||||
}, [data, validateAllUPCs]);
|
||||
}, [data, upcValidation]);
|
||||
|
||||
// Use AI validation hook
|
||||
const aiValidation = useAiValidation<T>(
|
||||
@@ -1013,11 +803,11 @@ const ValidationContainer = <T extends string>({
|
||||
// Handle next button click - memoized
|
||||
const handleNext = useCallback(() => {
|
||||
// Make sure any pending item numbers are applied
|
||||
applyItemNumbersToData();
|
||||
upcValidation.applyItemNumbersToData();
|
||||
|
||||
// Call the onNext callback with the validated data
|
||||
onNext?.(data)
|
||||
}, [onNext, data, applyItemNumbersToData]);
|
||||
}, [onNext, data, upcValidation.applyItemNumbersToData]);
|
||||
|
||||
const deleteSelectedRows = useCallback(() => {
|
||||
// Get selected row keys (which may be UUIDs)
|
||||
@@ -1146,12 +936,12 @@ const ValidationContainer = <T extends string>({
|
||||
|
||||
// Memoize the enhanced validation table component
|
||||
const EnhancedValidationTable = useMemo(() => React.memo((props: React.ComponentProps<typeof ValidationTable>) => {
|
||||
// Create validatingCells set from validatingUpcRows, but only for item_number fields
|
||||
// Create validatingCells set from validating rows, but only for item_number fields
|
||||
// This ensures only the item_number column shows loading state during UPC validation
|
||||
const combinedValidatingCells = new Set<string>();
|
||||
|
||||
// Add UPC validation cells
|
||||
validatingUpcRows.forEach(rowIndex => {
|
||||
upcValidation.validatingRows.forEach(rowIndex => {
|
||||
// Only mark the item_number cells as validating, NOT the UPC or supplier
|
||||
combinedValidatingCells.add(`${rowIndex}-item_number`);
|
||||
});
|
||||
@@ -1161,15 +951,20 @@ const ValidationContainer = <T extends string>({
|
||||
combinedValidatingCells.add(cellKey);
|
||||
});
|
||||
|
||||
// Convert itemNumbers to Map
|
||||
const itemNumbersMap = new Map(Object.entries(itemNumbers).map(([key, value]) => [parseInt(key), value]));
|
||||
|
||||
// Convert the Map to the expected format for the ValidationTable
|
||||
// Create a new Map from the item numbers to ensure proper typing
|
||||
const itemNumbersMap = new Map<number, string>();
|
||||
|
||||
// Merge the item numbers with the data for display purposes only
|
||||
const enhancedData = props.data.map((row: any, index: number) => {
|
||||
if (itemNumbers[index]) {
|
||||
const itemNumber = upcValidation.getItemNumber(index);
|
||||
if (itemNumber) {
|
||||
// Add to our map for proper prop passing
|
||||
itemNumbersMap.set(index, itemNumber);
|
||||
|
||||
return {
|
||||
...row,
|
||||
item_number: itemNumbers[index]
|
||||
item_number: itemNumber
|
||||
};
|
||||
}
|
||||
return row;
|
||||
@@ -1189,7 +984,7 @@ const ValidationContainer = <T extends string>({
|
||||
isLoadingSublines={isLoadingSublines}
|
||||
/>
|
||||
);
|
||||
}), [validatingUpcRows, itemNumbers, isLoadingTemplates, handleCopyDown, validatingCells, rowProductLines, rowSublines, isLoadingLines, isLoadingSublines]);
|
||||
}), [upcValidation.validatingRows, upcValidation.getItemNumber, isLoadingTemplates, handleCopyDown, validatingCells, rowProductLines, rowSublines, isLoadingLines, isLoadingSublines]);
|
||||
|
||||
// Memoize the rendered validation table
|
||||
const renderValidationTable = useMemo(() => (
|
||||
@@ -1201,7 +996,7 @@ const ValidationContainer = <T extends string>({
|
||||
updateRow={handleUpdateRow as (rowIndex: number, key: string, value: any) => void}
|
||||
validationErrors={validationErrors}
|
||||
isValidatingUpc={isRowValidatingUpc}
|
||||
validatingUpcRows={Array.from(validatingUpcRows)}
|
||||
validatingUpcRows={Array.from(upcValidation.validatingRows)}
|
||||
filters={filters}
|
||||
templates={templates}
|
||||
applyTemplate={applyTemplate}
|
||||
@@ -1225,7 +1020,7 @@ const ValidationContainer = <T extends string>({
|
||||
handleUpdateRow,
|
||||
validationErrors,
|
||||
isRowValidatingUpc,
|
||||
validatingUpcRows,
|
||||
upcValidation.validatingRows,
|
||||
filters,
|
||||
templates,
|
||||
applyTemplate,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback, useRef } from 'react'
|
||||
import { useState, useCallback, useRef, useEffect } from 'react'
|
||||
import config from '@/config'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface UpcValidationResult {
|
||||
error?: boolean
|
||||
@@ -10,18 +11,29 @@ interface UpcValidationResult {
|
||||
interface ValidationState {
|
||||
validatingCells: Set<string>; // Using rowIndex-fieldKey as identifier
|
||||
itemNumbers: Map<number, string>; // Using rowIndex as key
|
||||
validatingRows: Set<number>; // Rows currently being validated
|
||||
}
|
||||
|
||||
export const useUpcValidation = () => {
|
||||
export const useUpcValidation = <T extends string>(
|
||||
data: any[],
|
||||
setData: (updater: any[] | ((prevData: any[]) => any[])) => void
|
||||
) => {
|
||||
// Use a ref for validation state to avoid triggering re-renders
|
||||
const validationStateRef = useRef<ValidationState>({
|
||||
validatingCells: new Set(),
|
||||
itemNumbers: new Map()
|
||||
itemNumbers: new Map(),
|
||||
validatingRows: new Set()
|
||||
});
|
||||
|
||||
// Use state only for forcing re-renders of specific cells
|
||||
const [validatingCellKeys, setValidatingCellKeys] = useState<Set<string>>(new Set());
|
||||
const [itemNumberUpdates, setItemNumberUpdates] = useState<Map<number, string>>(new Map());
|
||||
const [validatingRows, setValidatingRows] = useState<Set<number>>(new Set());
|
||||
const [isValidatingUpc, setIsValidatingUpc] = useState(false);
|
||||
|
||||
// Cache for UPC validation results
|
||||
const processedUpcMapRef = useRef(new Map<string, string>());
|
||||
const initialUpcValidationDoneRef = useRef(false);
|
||||
|
||||
// Helper to create cell key
|
||||
const getCellKey = (rowIndex: number, fieldKey: string) => `${rowIndex}-${fieldKey}`;
|
||||
@@ -46,79 +58,251 @@ export const useUpcValidation = () => {
|
||||
setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers));
|
||||
}, []);
|
||||
|
||||
// Mark a row as being validated
|
||||
const startValidatingRow = useCallback((rowIndex: number) => {
|
||||
validationStateRef.current.validatingRows.add(rowIndex);
|
||||
setValidatingRows(new Set(validationStateRef.current.validatingRows));
|
||||
}, []);
|
||||
|
||||
// Mark a row as no longer being validated
|
||||
const stopValidatingRow = useCallback((rowIndex: number) => {
|
||||
validationStateRef.current.validatingRows.delete(rowIndex);
|
||||
setValidatingRows(new Set(validationStateRef.current.validatingRows));
|
||||
|
||||
// If no more rows are being validated, set global validation state to false
|
||||
if (validationStateRef.current.validatingRows.size === 0) {
|
||||
setIsValidatingUpc(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Check if a specific cell is being validated
|
||||
const isValidatingCell = useCallback((rowIndex: number, fieldKey: string): boolean => {
|
||||
return validationStateRef.current.validatingCells.has(getCellKey(rowIndex, fieldKey));
|
||||
}, []);
|
||||
|
||||
// Check if a specific row is being validated
|
||||
const isRowValidatingUpc = useCallback((rowIndex: number): boolean => {
|
||||
return validationStateRef.current.validatingRows.has(rowIndex);
|
||||
}, []);
|
||||
|
||||
// Get item number for a row
|
||||
const getItemNumber = useCallback((rowIndex: number): string | undefined => {
|
||||
return validationStateRef.current.itemNumbers.get(rowIndex);
|
||||
}, []);
|
||||
|
||||
// Apply all pending updates to the data state
|
||||
const applyItemNumbersToData = useCallback(() => {
|
||||
if (validationStateRef.current.itemNumbers.size === 0) return;
|
||||
|
||||
setData((prevData: any[]) => {
|
||||
const newData = [...prevData];
|
||||
|
||||
// Apply all item numbers without changing other data
|
||||
Array.from(validationStateRef.current.itemNumbers.entries()).forEach(([index, itemNumber]) => {
|
||||
if (index >= 0 && index < newData.length) {
|
||||
// Only update the item_number field and leave everything else unchanged
|
||||
newData[index] = {
|
||||
...newData[index],
|
||||
item_number: itemNumber
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return newData;
|
||||
});
|
||||
|
||||
// Clear the item numbers state after applying
|
||||
validationStateRef.current.itemNumbers.clear();
|
||||
setItemNumberUpdates(new Map());
|
||||
}, [setData]);
|
||||
|
||||
// Validate a UPC value
|
||||
const validateUpc = useCallback(async (
|
||||
upcValue: string,
|
||||
rowIndex: number,
|
||||
supplier: string
|
||||
): Promise<UpcValidationResult> => {
|
||||
// Start validating UPC and item number cells
|
||||
startValidatingCell(rowIndex, 'upc');
|
||||
startValidatingCell(rowIndex, 'item_number');
|
||||
|
||||
supplierId: string,
|
||||
upcValue: string
|
||||
): Promise<{success: boolean, itemNumber?: string}> => {
|
||||
try {
|
||||
// Call the UPC validation API
|
||||
const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplier)}`);
|
||||
// Skip if either value is missing
|
||||
if (!supplierId || !upcValue) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Add logging to help debug
|
||||
console.log(`Validating UPC for row ${rowIndex}. Supplier ID: ${supplierId}, UPC: ${upcValue}`);
|
||||
|
||||
// Start validating both UPC and item_number cells
|
||||
startValidatingCell(rowIndex, 'upc');
|
||||
startValidatingCell(rowIndex, 'item_number');
|
||||
|
||||
// Also mark the row as being validated
|
||||
startValidatingRow(rowIndex);
|
||||
|
||||
// Check if we've already validated this UPC/supplier combination
|
||||
const cacheKey = `${supplierId}-${upcValue}`;
|
||||
if (processedUpcMapRef.current.has(cacheKey)) {
|
||||
const cachedItemNumber = processedUpcMapRef.current.get(cacheKey);
|
||||
|
||||
if (cachedItemNumber) {
|
||||
// Use cached item number
|
||||
updateItemNumber(rowIndex, cachedItemNumber);
|
||||
return { success: true, itemNumber: cachedItemNumber };
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// Make API call to validate UPC
|
||||
const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`);
|
||||
|
||||
// Process the response
|
||||
if (response.status === 409) {
|
||||
// UPC already exists - show validation error
|
||||
const errorData = await response.json();
|
||||
return {
|
||||
error: true,
|
||||
message: `UPC already exists (${errorData.existingItemNumber})`,
|
||||
data: errorData
|
||||
};
|
||||
return { success: false };
|
||||
} else if (response.ok) {
|
||||
// Successful validation - update item number
|
||||
const responseData = await response.json();
|
||||
|
||||
if (responseData.success && responseData.itemNumber) {
|
||||
// Store in cache
|
||||
processedUpcMapRef.current.set(cacheKey, responseData.itemNumber);
|
||||
|
||||
// Update the item numbers state
|
||||
updateItemNumber(rowIndex, responseData.itemNumber);
|
||||
|
||||
return { success: true, itemNumber: responseData.itemNumber };
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error (${response.status})`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.itemNumber) {
|
||||
// Update item number in our state
|
||||
updateItemNumber(rowIndex, result.itemNumber);
|
||||
return {
|
||||
error: false,
|
||||
data: {
|
||||
itemNumber: result.itemNumber,
|
||||
...result
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
error: true,
|
||||
message: 'Invalid response from server'
|
||||
};
|
||||
return { success: false };
|
||||
} catch (error) {
|
||||
console.error('Error validating UPC:', error);
|
||||
return {
|
||||
error: true,
|
||||
message: 'Failed to validate UPC'
|
||||
};
|
||||
console.error(`Error validating UPC for row ${rowIndex}:`, error);
|
||||
return { success: false };
|
||||
} finally {
|
||||
// Stop validating both cells
|
||||
// Clear validation state
|
||||
stopValidatingCell(rowIndex, 'upc');
|
||||
stopValidatingCell(rowIndex, 'item_number');
|
||||
stopValidatingRow(rowIndex);
|
||||
}
|
||||
}, [startValidatingCell, stopValidatingCell, updateItemNumber]);
|
||||
}, [startValidatingCell, stopValidatingCell, updateItemNumber, startValidatingRow, stopValidatingRow]);
|
||||
|
||||
// Batch validate all UPCs in the data
|
||||
const validateAllUPCs = useCallback(async () => {
|
||||
// Skip if we've already done the initial validation
|
||||
if (initialUpcValidationDoneRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark that we've done the initial validation
|
||||
initialUpcValidationDoneRef.current = true;
|
||||
|
||||
console.log('Starting UPC validation...');
|
||||
|
||||
// Set validation state
|
||||
setIsValidatingUpc(true);
|
||||
|
||||
// Find all rows that have both supplier and UPC/barcode
|
||||
const rowsToValidate = data
|
||||
.map((row, index) => ({ row, index }))
|
||||
.filter(({ row }) => {
|
||||
const rowAny = row as Record<string, any>;
|
||||
const hasSupplier = rowAny.supplier;
|
||||
const hasUpc = rowAny.upc || rowAny.barcode;
|
||||
return hasSupplier && hasUpc;
|
||||
});
|
||||
|
||||
const totalRows = rowsToValidate.length;
|
||||
console.log(`Found ${totalRows} rows with both supplier and UPC`);
|
||||
|
||||
if (totalRows === 0) {
|
||||
setIsValidatingUpc(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all rows as being validated
|
||||
const newValidatingRows = new Set(rowsToValidate.map(({ index }) => index));
|
||||
validationStateRef.current.validatingRows = newValidatingRows;
|
||||
setValidatingRows(newValidatingRows);
|
||||
|
||||
try {
|
||||
// Process all rows in parallel
|
||||
await Promise.all(
|
||||
rowsToValidate.map(async ({ row, index }) => {
|
||||
try {
|
||||
const rowAny = row as Record<string, any>;
|
||||
const supplierId = rowAny.supplier.toString();
|
||||
const upcValue = (rowAny.upc || rowAny.barcode).toString();
|
||||
|
||||
// Validate the UPC
|
||||
await validateUpc(index, supplierId, upcValue);
|
||||
|
||||
// Remove this row from the validating set (handled in validateUpc)
|
||||
} catch (error) {
|
||||
console.error(`Error processing row ${index}:`, error);
|
||||
stopValidatingRow(index);
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error in batch validation:', error);
|
||||
} finally {
|
||||
// Reset validation state
|
||||
setIsValidatingUpc(false);
|
||||
validationStateRef.current.validatingRows.clear();
|
||||
setValidatingRows(new Set());
|
||||
console.log('Completed UPC validation');
|
||||
|
||||
// Apply item numbers to data
|
||||
applyItemNumbersToData();
|
||||
}
|
||||
}, [data, validateUpc, stopValidatingRow, applyItemNumbersToData]);
|
||||
|
||||
// Run initial UPC validation when data changes
|
||||
useEffect(() => {
|
||||
// Skip if there's no data or we've already done the validation
|
||||
if (data.length === 0 || initialUpcValidationDoneRef.current) return;
|
||||
|
||||
// Run validation
|
||||
validateAllUPCs();
|
||||
}, [data, validateAllUPCs]);
|
||||
|
||||
// Apply item numbers when they change
|
||||
useEffect(() => {
|
||||
// Apply item numbers if there are any
|
||||
if (validationStateRef.current.itemNumbers.size > 0) {
|
||||
applyItemNumbersToData();
|
||||
}
|
||||
}, [itemNumberUpdates, applyItemNumbersToData]);
|
||||
|
||||
// Reset validation state when hook is unmounted
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
initialUpcValidationDoneRef.current = false;
|
||||
processedUpcMapRef.current.clear();
|
||||
validationStateRef.current.validatingCells.clear();
|
||||
validationStateRef.current.itemNumbers.clear();
|
||||
validationStateRef.current.validatingRows.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
validateUpc,
|
||||
validateAllUPCs,
|
||||
isValidatingCell,
|
||||
isRowValidatingUpc,
|
||||
isValidatingUpc,
|
||||
getItemNumber,
|
||||
applyItemNumbersToData,
|
||||
itemNumbers: itemNumberUpdates,
|
||||
validatingCells: validatingCellKeys
|
||||
validatingCells: validatingCellKeys,
|
||||
validatingRows,
|
||||
resetInitialValidation: () => {
|
||||
initialUpcValidationDoneRef.current = false;
|
||||
},
|
||||
// Export the ref for direct access
|
||||
get initialValidationDone() {
|
||||
return initialUpcValidationDoneRef.current;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user