Fix validation again I hope?
This commit is contained in:
@@ -141,21 +141,33 @@ const ItemNumberCell = React.memo(({
|
|||||||
field: Field<string>,
|
field: Field<string>,
|
||||||
onChange: (value: any) => void
|
onChange: (value: any) => void
|
||||||
}) => {
|
}) => {
|
||||||
// Determine if the field has an error
|
// Helper function to check if a value is empty
|
||||||
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
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
|
// Determine if the field is required but empty
|
||||||
const isRequiredButEmpty = errors.some(error =>
|
const isRequiredButEmpty = isEmpty(displayValue) &&
|
||||||
error.message?.toLowerCase().includes('required') &&
|
errors.some(error => error.message?.toLowerCase().includes('required'));
|
||||||
(!value || (typeof value === 'string' && value.trim() === ''))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only show error icons for non-empty fields
|
// Only show error icons for non-empty fields with actual errors (not just required errors)
|
||||||
const shouldShowErrorIcon = hasError && value && (typeof value !== 'string' || value.trim() !== '');
|
const shouldShowErrorIcon = hasError && !isEmpty(displayValue);
|
||||||
|
|
||||||
// Get error messages for the tooltip
|
// Get error messages for the tooltip
|
||||||
const errorMessages = shouldShowErrorIcon
|
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 (
|
return (
|
||||||
@@ -164,13 +176,13 @@ const ItemNumberCell = React.memo(({
|
|||||||
{isValidating ? (
|
{isValidating ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
|
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
|
||||||
<span>{itemNumber || value || ''}</span>
|
<span>{displayValue || ''}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="truncate overflow-hidden">
|
<div className="truncate overflow-hidden">
|
||||||
<BaseCellContent
|
<BaseCellContent
|
||||||
field={field}
|
field={field}
|
||||||
value={itemNumber || value}
|
value={displayValue}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
hasErrors={hasError || isRequiredButEmpty}
|
hasErrors={hasError || isRequiredButEmpty}
|
||||||
options={[]}
|
options={[]}
|
||||||
@@ -222,26 +234,32 @@ const ValidationCell = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the field has an error
|
// Helper function to check if a value is empty
|
||||||
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
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
|
// Determine if the field is required but empty
|
||||||
const isRequiredButEmpty = errors.some(error =>
|
const isRequiredButEmpty = isEmpty(value) &&
|
||||||
error.message?.toLowerCase().includes('required') &&
|
errors.some(error => error.message?.toLowerCase().includes('required'));
|
||||||
(value === undefined || value === null ||
|
|
||||||
(typeof value === 'string' && value.trim() === '') ||
|
|
||||||
(Array.isArray(value) && value.length === 0))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only show error icons for non-empty fields
|
// Only show error icons for non-empty fields with actual errors (not just required errors)
|
||||||
const shouldShowErrorIcon = hasError &&
|
const shouldShowErrorIcon = hasError && !isEmpty(value);
|
||||||
!(value === undefined || value === null ||
|
|
||||||
(typeof value === 'string' && value.trim() === '') ||
|
|
||||||
(Array.isArray(value) && value.length === 0));
|
|
||||||
|
|
||||||
// Get error messages for the tooltip
|
// Get error messages for the tooltip
|
||||||
const errorMessages = shouldShowErrorIcon
|
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
|
// Check if this is a multiline field
|
||||||
@@ -249,24 +267,30 @@ const ValidationCell = ({
|
|||||||
(field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') &&
|
(field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') &&
|
||||||
field.fieldType.multiline === true;
|
field.fieldType.multiline === true;
|
||||||
|
|
||||||
// Adjust cell height for multiline fields
|
// Check for price field
|
||||||
const cellHeight = isMultiline ? 'min-h-[80px]' : 'h-10';
|
const isPrice = typeof field.fieldType === 'object' &&
|
||||||
|
(field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') &&
|
||||||
|
field.fieldType.price === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCell className="p-1" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
<TableCell className="p-1" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''} ${cellHeight}`}>
|
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||||
|
{isValidating ? (
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className={`truncate overflow-hidden ${isMultiline ? 'h-full' : ''}`}>
|
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
|
||||||
<BaseCellContent
|
<span>Loading...</span>
|
||||||
field={field}
|
</div>
|
||||||
value={value}
|
) : (
|
||||||
onChange={onChange}
|
<div className="truncate overflow-hidden">
|
||||||
hasErrors={hasError || isRequiredButEmpty}
|
<BaseCellContent
|
||||||
options={options}
|
field={field}
|
||||||
/>
|
value={value}
|
||||||
</div>
|
onChange={onChange}
|
||||||
|
hasErrors={hasError || isRequiredButEmpty}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{shouldShowErrorIcon && (
|
{shouldShowErrorIcon && (
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-20">
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-20">
|
||||||
<ValidationIcon error={{
|
<ValidationIcon error={{
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ const ValidationContainer = <T extends string>({
|
|||||||
return { success: false };
|
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
|
// Check if we've already validated this UPC/supplier combination
|
||||||
const cacheKey = `${supplierId}-${upcValue}`;
|
const cacheKey = `${supplierId}-${upcValue}`;
|
||||||
if (processedUpcMapRef.current.has(cacheKey)) {
|
if (processedUpcMapRef.current.has(cacheKey)) {
|
||||||
@@ -188,7 +191,7 @@ const ValidationContainer = <T extends string>({
|
|||||||
return { success: false };
|
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)}`);
|
const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`);
|
||||||
|
|
||||||
// Process the response
|
// Process the response
|
||||||
@@ -372,8 +375,35 @@ const ValidationContainer = <T extends string>({
|
|||||||
top: window.scrollY
|
top: window.scrollY
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the main data state
|
// Find the original index in the data array
|
||||||
updateRow(rowIndex, fieldKey, processedValue);
|
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
|
// Restore scroll position after update
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -381,15 +411,9 @@ const ValidationContainer = <T extends string>({
|
|||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Now handle any additional logic for specific fields
|
// Now handle any additional logic for specific fields
|
||||||
const rowData = filteredData[rowIndex];
|
|
||||||
|
|
||||||
// If updating company field, fetch product lines
|
|
||||||
if (fieldKey === 'company' && value) {
|
if (fieldKey === 'company' && value) {
|
||||||
// Clear any existing line/subline values for this row if company changes
|
// Clear any existing line/subline values for this row if company changes
|
||||||
const originalIndex = data.findIndex(item => item.__index === rowData?.__index);
|
|
||||||
|
|
||||||
if (originalIndex !== -1) {
|
if (originalIndex !== -1) {
|
||||||
// Update the data to clear line and subline
|
|
||||||
setData(prevData => {
|
setData(prevData => {
|
||||||
const newData = [...prevData];
|
const newData = [...prevData];
|
||||||
newData[originalIndex] = {
|
newData[originalIndex] = {
|
||||||
@@ -405,45 +429,43 @@ const ValidationContainer = <T extends string>({
|
|||||||
if (rowData && rowData.__index) {
|
if (rowData && rowData.__index) {
|
||||||
await fetchProductLines(rowData.__index, value.toString());
|
await fetchProductLines(rowData.__index, value.toString());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If company field is being updated AND there's a UPC value, validate UPC
|
// If updating supplier field AND there's a UPC value, validate UPC
|
||||||
if (rowData) {
|
if (fieldKey === 'supplier' && value && rowData) {
|
||||||
const rowDataAny = rowData as Record<string, any>;
|
const rowDataAny = rowData as Record<string, any>;
|
||||||
if (rowDataAny.upc || rowDataAny.barcode) {
|
if (rowDataAny.upc || rowDataAny.barcode) {
|
||||||
const upcValue = rowDataAny.upc || rowDataAny.barcode;
|
const upcValue = rowDataAny.upc || rowDataAny.barcode;
|
||||||
|
|
||||||
// Mark this row as being validated
|
// Mark this row as being validated
|
||||||
setValidatingUpcRows(prev => {
|
setValidatingUpcRows(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.add(rowIndex);
|
newSet.add(rowIndex);
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set global validation state
|
// Set global validation state
|
||||||
setIsValidatingUpc(true);
|
setIsValidatingUpc(true);
|
||||||
|
|
||||||
await validateUpc(rowIndex, value.toString(), upcValue.toString());
|
// Use supplier ID (the value being set) to validate UPC
|
||||||
|
await validateUpc(rowIndex, value.toString(), upcValue.toString());
|
||||||
|
|
||||||
// Update validation state
|
// Update validation state
|
||||||
setValidatingUpcRows(prev => {
|
setValidatingUpcRows(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
newSet.delete(rowIndex);
|
newSet.delete(rowIndex);
|
||||||
if (newSet.size === 0) {
|
if (newSet.size === 0) {
|
||||||
setIsValidatingUpc(false);
|
setIsValidatingUpc(false);
|
||||||
}
|
}
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If updating line field, fetch sublines
|
// If updating line field, fetch sublines
|
||||||
if (fieldKey === 'line' && value) {
|
if (fieldKey === 'line' && value) {
|
||||||
// Clear any existing subline value for this row
|
// Clear any existing subline value for this row
|
||||||
const originalIndex = data.findIndex(item => item.__index === rowData?.__index);
|
|
||||||
|
|
||||||
if (originalIndex !== -1) {
|
if (originalIndex !== -1) {
|
||||||
// Update the data to clear subline only
|
|
||||||
setData(prevData => {
|
setData(prevData => {
|
||||||
const newData = [...prevData];
|
const newData = [...prevData];
|
||||||
newData[originalIndex] = {
|
newData[originalIndex] = {
|
||||||
@@ -474,6 +496,7 @@ const ValidationContainer = <T extends string>({
|
|||||||
// Set global validation state
|
// Set global validation state
|
||||||
setIsValidatingUpc(true);
|
setIsValidatingUpc(true);
|
||||||
|
|
||||||
|
// Use supplier ID from the row data (NOT company ID) to validate UPC
|
||||||
await validateUpc(rowIndex, rowDataAny.supplier.toString(), value.toString());
|
await validateUpc(rowIndex, rowDataAny.supplier.toString(), value.toString());
|
||||||
|
|
||||||
// Update validation state
|
// Update validation state
|
||||||
|
|||||||
@@ -32,7 +32,15 @@ export const useValidation = <T extends string>(
|
|||||||
field.validations.forEach(validation => {
|
field.validations.forEach(validation => {
|
||||||
switch (validation.rule) {
|
switch (validation.rule) {
|
||||||
case 'required':
|
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({
|
errors.push({
|
||||||
message: validation.errorMessage || 'This field is required',
|
message: validation.errorMessage || 'This field is required',
|
||||||
level: validation.level || 'error'
|
level: validation.level || 'error'
|
||||||
@@ -74,6 +82,14 @@ export const useValidation = <T extends string>(
|
|||||||
// Run field-level validations
|
// Run field-level validations
|
||||||
const fieldErrors: Record<string, ValidationError[]> = {}
|
const fieldErrors: Record<string, ValidationError[]> = {}
|
||||||
|
|
||||||
|
// 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 => {
|
fields.forEach(field => {
|
||||||
const value = row[String(field.key) as keyof typeof row]
|
const value = row[String(field.key) as keyof typeof row]
|
||||||
const errors = validateField(value, field as Field<T>)
|
const errors = validateField(value, field as Field<T>)
|
||||||
@@ -83,15 +99,15 @@ export const useValidation = <T extends string>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Special validation for supplier and company fields
|
// Special validation for supplier and company fields - only apply if the field exists in fields
|
||||||
if (fields.some(field => String(field.key) === 'supplier') && !row.supplier) {
|
if (fields.some(field => String(field.key) === 'supplier') && isEmpty(row.supplier)) {
|
||||||
fieldErrors['supplier'] = [{
|
fieldErrors['supplier'] = [{
|
||||||
message: 'Supplier is required',
|
message: 'Supplier is required',
|
||||||
level: 'error'
|
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'] = [{
|
fieldErrors['company'] = [{
|
||||||
message: 'Company is required',
|
message: 'Company is required',
|
||||||
level: 'error'
|
level: 'error'
|
||||||
@@ -249,6 +265,14 @@ export const useValidation = <T extends string>(
|
|||||||
|
|
||||||
// Run complete validation
|
// Run complete validation
|
||||||
const validateData = useCallback(async (data: RowData<T>[]) => {
|
const validateData = useCallback(async (data: RowData<T>[]) => {
|
||||||
|
// 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
|
// Step 1: Run field and row validation
|
||||||
const rowValidations = await Promise.all(
|
const rowValidations = await Promise.all(
|
||||||
data.map((row, index) => validateRow(row, index, data))
|
data.map((row, index) => validateRow(row, index, data))
|
||||||
@@ -276,7 +300,25 @@ export const useValidation = <T extends string>(
|
|||||||
...(tableValidation.__errors || {})
|
...(tableValidation.__errors || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
newRow.__errors = combinedErrors
|
// Filter out "required" errors for fields that have values
|
||||||
|
const filteredErrors: Record<string, InfoWithSource> = {}
|
||||||
|
|
||||||
|
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
|
return newRow
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user