Fix validation again I hope?
This commit is contained in:
@@ -141,21 +141,33 @@ const ItemNumberCell = React.memo(({
|
||||
field: Field<string>,
|
||||
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 ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
|
||||
<span>{itemNumber || value || ''}</span>
|
||||
<span>{displayValue || ''}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="truncate overflow-hidden">
|
||||
<BaseCellContent
|
||||
field={field}
|
||||
value={itemNumber || value}
|
||||
value={displayValue}
|
||||
onChange={onChange}
|
||||
hasErrors={hasError || isRequiredButEmpty}
|
||||
options={[]}
|
||||
@@ -222,26 +234,32 @@ const ValidationCell = ({
|
||||
);
|
||||
}
|
||||
|
||||
// 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, 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,15 +267,21 @@ 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 (
|
||||
<TableCell className="p-1" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''} ${cellHeight}`}>
|
||||
|
||||
|
||||
<div className={`truncate overflow-hidden ${isMultiline ? 'h-full' : ''}`}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||
{isValidating ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="truncate overflow-hidden">
|
||||
<BaseCellContent
|
||||
field={field}
|
||||
value={value}
|
||||
@@ -266,7 +290,7 @@ const ValidationCell = ({
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)}
|
||||
{shouldShowErrorIcon && (
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-20">
|
||||
<ValidationIcon error={{
|
||||
|
||||
@@ -170,6 +170,9 @@ const ValidationContainer = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
top: window.scrollY
|
||||
};
|
||||
|
||||
// Update the main data state
|
||||
// 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 = <T extends string>({
|
||||
}, 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,9 +429,10 @@ const ValidationContainer = <T extends string>({
|
||||
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) {
|
||||
// If updating supplier field AND there's a UPC value, validate UPC
|
||||
if (fieldKey === 'supplier' && value && rowData) {
|
||||
const rowDataAny = rowData as Record<string, any>;
|
||||
if (rowDataAny.upc || rowDataAny.barcode) {
|
||||
const upcValue = rowDataAny.upc || rowDataAny.barcode;
|
||||
@@ -422,6 +447,7 @@ const ValidationContainer = <T extends string>({
|
||||
// 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
|
||||
@@ -435,15 +461,11 @@ const ValidationContainer = <T extends string>({
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = <T extends string>({
|
||||
// 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
|
||||
|
||||
@@ -32,7 +32,15 @@ export const useValidation = <T extends string>(
|
||||
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 = <T extends string>(
|
||||
// Run field-level validations
|
||||
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 => {
|
||||
const value = row[String(field.key) as keyof typeof row]
|
||||
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
|
||||
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 = <T extends string>(
|
||||
|
||||
// Run complete validation
|
||||
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
|
||||
const rowValidations = await Promise.all(
|
||||
data.map((row, index) => validateRow(row, index, data))
|
||||
@@ -276,7 +300,25 @@ export const useValidation = <T extends string>(
|
||||
...(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
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user