diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx index a9496d3..4ff2275 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx @@ -141,21 +141,33 @@ const ItemNumberCell = React.memo(({ field: Field, onChange: (value: any) => void }) => { - // Determine if the field has an error - const hasError = errors.some(error => error.level === 'error' || error.level === 'warning'); + // Helper function to check if a value is empty + const isEmpty = (val: any): boolean => + val === undefined || + val === null || + val === '' || + (Array.isArray(val) && val.length === 0) || + (typeof val === 'object' && !Array.isArray(val) && Object.keys(val).length === 0); + + // If we have a value or itemNumber, ignore "required" errors + const displayValue = itemNumber || value; + const filteredErrors = !isEmpty(displayValue) + ? errors.filter(error => !error.message?.toLowerCase().includes('required')) + : errors; + + // Determine if the field has an error after filtering + const hasError = filteredErrors.some(error => error.level === 'error' || error.level === 'warning'); // Determine if the field is required but empty - const isRequiredButEmpty = errors.some(error => - error.message?.toLowerCase().includes('required') && - (!value || (typeof value === 'string' && value.trim() === '')) - ); + const isRequiredButEmpty = isEmpty(displayValue) && + errors.some(error => error.message?.toLowerCase().includes('required')); - // Only show error icons for non-empty fields - const shouldShowErrorIcon = hasError && value && (typeof value !== 'string' || value.trim() !== ''); + // Only show error icons for non-empty fields with actual errors (not just required errors) + const shouldShowErrorIcon = hasError && !isEmpty(displayValue); // Get error messages for the tooltip const errorMessages = shouldShowErrorIcon - ? errors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n') + ? filteredErrors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n') : ''; return ( @@ -164,13 +176,13 @@ const ItemNumberCell = React.memo(({ {isValidating ? (
- {itemNumber || value || ''} + {displayValue || ''}
) : (
error.level === 'error' || error.level === 'warning'); + // Helper function to check if a value is empty + const isEmpty = (val: any): boolean => + val === undefined || + val === null || + val === '' || + (Array.isArray(val) && val.length === 0) || + (typeof val === 'object' && !Array.isArray(val) && Object.keys(val).length === 0); + + // If we have a value, ignore "required" errors + const filteredErrors = !isEmpty(value) + ? errors.filter(error => !error.message?.toLowerCase().includes('required')) + : errors; + + // Determine if the field has an error after filtering + const hasError = filteredErrors.some(error => error.level === 'error' || error.level === 'warning'); // Determine if the field is required but empty - const isRequiredButEmpty = errors.some(error => - error.message?.toLowerCase().includes('required') && - (value === undefined || value === null || - (typeof value === 'string' && value.trim() === '') || - (Array.isArray(value) && value.length === 0)) - ); + const isRequiredButEmpty = isEmpty(value) && + errors.some(error => error.message?.toLowerCase().includes('required')); - // Only show error icons for non-empty fields - const shouldShowErrorIcon = hasError && - !(value === undefined || value === null || - (typeof value === 'string' && value.trim() === '') || - (Array.isArray(value) && value.length === 0)); + // Only show error icons for non-empty fields with actual errors (not just required errors) + const shouldShowErrorIcon = hasError && !isEmpty(value); // Get error messages for the tooltip const errorMessages = shouldShowErrorIcon - ? errors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n') + ? filteredErrors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n') : ''; // Check if this is a multiline field @@ -249,24 +267,30 @@ const ValidationCell = ({ (field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') && field.fieldType.multiline === true; - // Adjust cell height for multiline fields - const cellHeight = isMultiline ? 'min-h-[80px]' : 'h-10'; + // Check for price field + const isPrice = typeof field.fieldType === 'object' && + (field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') && + field.fieldType.price === true; return ( -
- - -
- -
- +
+ {isValidating ? ( +
+ + Loading... +
+ ) : ( +
+ +
+ )} {shouldShowErrorIcon && (
({ 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)) { @@ -188,7 +191,7 @@ const ValidationContainer = ({ return { success: false }; } - // Make API call to validate UPC + // 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 @@ -372,8 +375,35 @@ const ValidationContainer = ({ top: window.scrollY }; - // Update the main data state - updateRow(rowIndex, fieldKey, processedValue); + // Find the original index in the data array + const rowData = filteredData[rowIndex]; + const originalIndex = data.findIndex(item => item.__index === rowData?.__index); + + if (originalIndex === -1) { + // If we can't find the original row, just do a simple update + updateRow(rowIndex, fieldKey, processedValue); + } else { + // Create a new row with the updated field + const updatedRow = { + ...data[originalIndex], + [fieldKey]: processedValue + }; + + // Clear any validation errors for this field + if (updatedRow.__errors && updatedRow.__errors[String(fieldKey)]) { + const updatedErrors = { ...updatedRow.__errors }; + delete updatedErrors[String(fieldKey)]; + + updatedRow.__errors = Object.keys(updatedErrors).length > 0 ? updatedErrors : undefined; + } + + // Update the data directly + setData(prevData => { + const newData = [...prevData]; + newData[originalIndex] = updatedRow; + return newData; + }); + } // Restore scroll position after update setTimeout(() => { @@ -381,15 +411,9 @@ const ValidationContainer = ({ }, 0); // Now handle any additional logic for specific fields - const rowData = filteredData[rowIndex]; - - // If updating company field, fetch product lines if (fieldKey === 'company' && value) { // Clear any existing line/subline values for this row if company changes - const originalIndex = data.findIndex(item => item.__index === rowData?.__index); - if (originalIndex !== -1) { - // Update the data to clear line and subline setData(prevData => { const newData = [...prevData]; newData[originalIndex] = { @@ -405,45 +429,43 @@ const ValidationContainer = ({ if (rowData && rowData.__index) { await fetchProductLines(rowData.__index, value.toString()); } - - // If company field is being updated AND there's a UPC value, validate UPC - if (rowData) { - const rowDataAny = rowData as Record; - if (rowDataAny.upc || rowDataAny.barcode) { - const upcValue = rowDataAny.upc || rowDataAny.barcode; - - // Mark this row as being validated - setValidatingUpcRows(prev => { - const newSet = new Set(prev); - newSet.add(rowIndex); - return newSet; - }); - - // Set global validation state - setIsValidatingUpc(true); - - await validateUpc(rowIndex, value.toString(), upcValue.toString()); - - // Update validation state - setValidatingUpcRows(prev => { - const newSet = new Set(prev); - newSet.delete(rowIndex); - if (newSet.size === 0) { - setIsValidatingUpc(false); - } - return newSet; - }); - } + } + + // If updating supplier field AND there's a UPC value, validate UPC + if (fieldKey === 'supplier' && value && rowData) { + const rowDataAny = rowData as Record; + if (rowDataAny.upc || rowDataAny.barcode) { + const upcValue = rowDataAny.upc || rowDataAny.barcode; + + // Mark this row as being validated + setValidatingUpcRows(prev => { + const newSet = new Set(prev); + newSet.add(rowIndex); + 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()); + + // Update validation state + setValidatingUpcRows(prev => { + const newSet = new Set(prev); + newSet.delete(rowIndex); + if (newSet.size === 0) { + setIsValidatingUpc(false); + } + return newSet; + }); } } // If updating line field, fetch sublines if (fieldKey === 'line' && value) { // Clear any existing subline value for this row - const originalIndex = data.findIndex(item => item.__index === rowData?.__index); - if (originalIndex !== -1) { - // Update the data to clear subline only setData(prevData => { const newData = [...prevData]; newData[originalIndex] = { @@ -474,6 +496,7 @@ const ValidationContainer = ({ // 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()); // Update validation state diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx index 2e3567f..50db338 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx @@ -32,7 +32,15 @@ export const useValidation = ( field.validations.forEach(validation => { switch (validation.rule) { case 'required': - if (value === undefined || value === null || value === '') { + // More granular check for empty values + const isEmpty = + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) || + (typeof value === 'object' && Object.keys(value).length === 0); + + if (isEmpty) { errors.push({ message: validation.errorMessage || 'This field is required', level: validation.level || 'error' @@ -74,6 +82,14 @@ export const useValidation = ( // Run field-level validations const fieldErrors: Record = {} + // Helper function to check if a value is empty + const isEmpty = (value: any): boolean => + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) || + (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0); + fields.forEach(field => { const value = row[String(field.key) as keyof typeof row] const errors = validateField(value, field as Field) @@ -83,15 +99,15 @@ export const useValidation = ( } }) - // Special validation for supplier and company fields - if (fields.some(field => String(field.key) === 'supplier') && !row.supplier) { + // Special validation for supplier and company fields - only apply if the field exists in fields + if (fields.some(field => String(field.key) === 'supplier') && isEmpty(row.supplier)) { fieldErrors['supplier'] = [{ message: 'Supplier is required', level: 'error' }] } - if (fields.some(field => String(field.key) === 'company') && !row.company) { + if (fields.some(field => String(field.key) === 'company') && isEmpty(row.company)) { fieldErrors['company'] = [{ message: 'Company is required', level: 'error' @@ -249,6 +265,14 @@ export const useValidation = ( // Run complete validation const validateData = useCallback(async (data: RowData[]) => { + // Helper function to check if a value is empty + const isEmpty = (value: any): boolean => + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) || + (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0); + // Step 1: Run field and row validation const rowValidations = await Promise.all( data.map((row, index) => validateRow(row, index, data)) @@ -276,7 +300,25 @@ export const useValidation = ( ...(tableValidation.__errors || {}) } - newRow.__errors = combinedErrors + // Filter out "required" errors for fields that have values + const filteredErrors: Record = {} + + Object.entries(combinedErrors).forEach(([key, error]) => { + const fieldValue = row[key as keyof typeof row] + + // If the field has a value and the only error is "required", skip it + if (!isEmpty(fieldValue) && + error && + typeof error === 'object' && + 'message' in error && + error.message?.toLowerCase().includes('required')) { + return + } + + filteredErrors[key] = error as InfoWithSource + }) + + newRow.__errors = Object.keys(filteredErrors).length > 0 ? filteredErrors : undefined return newRow })