Fix validation timing issues with templates
This commit is contained in:
@@ -141,13 +141,26 @@ 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
|
||||||
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
||||||
const isRequiredButEmpty = errors.some(error => error.level === 'required' && (!value || value.trim() === ''));
|
|
||||||
const nonRequiredErrors = errors.filter(error => error.level !== 'required');
|
// Determine if the field is required but empty
|
||||||
|
const isRequiredButEmpty = errors.some(error =>
|
||||||
|
error.message?.toLowerCase().includes('required') &&
|
||||||
|
(!value || (typeof value === 'string' && value.trim() === ''))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only show error icons for non-empty fields
|
||||||
|
const shouldShowErrorIcon = hasError && value && (typeof value !== 'string' || value.trim() !== '');
|
||||||
|
|
||||||
|
// Get error messages for the tooltip
|
||||||
|
const errorMessages = shouldShowErrorIcon
|
||||||
|
? errors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n')
|
||||||
|
: '';
|
||||||
|
|
||||||
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 ? 'border-red-500' : (isRequiredButEmpty ? 'border-red-500' : '')}`}>
|
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||||
{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" />
|
||||||
@@ -164,10 +177,10 @@ const ItemNumberCell = React.memo(({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{nonRequiredErrors.length > 0 && (
|
{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={{
|
||||||
message: nonRequiredErrors.map(e => e.message).join('\n'),
|
message: errorMessages,
|
||||||
level: 'error'
|
level: 'error'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
@@ -209,21 +222,27 @@ const ValidationCell = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error states
|
// Determine if the field has an error
|
||||||
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
|
||||||
const isRequiredButEmpty = errors.some(error => {
|
|
||||||
if (error.level !== 'required') return false;
|
// Determine if the field is required but empty
|
||||||
|
const isRequiredButEmpty = errors.some(error =>
|
||||||
// Handle different value types
|
error.message?.toLowerCase().includes('required') &&
|
||||||
if (Array.isArray(value)) {
|
(value === undefined || value === null ||
|
||||||
return value.length === 0;
|
(typeof value === 'string' && value.trim() === '') ||
|
||||||
}
|
(Array.isArray(value) && value.length === 0))
|
||||||
if (typeof value === 'string') {
|
);
|
||||||
return !value || value.trim() === '';
|
|
||||||
}
|
// Only show error icons for non-empty fields
|
||||||
return value === undefined || value === null;
|
const shouldShowErrorIcon = hasError &&
|
||||||
});
|
!(value === undefined || value === null ||
|
||||||
const nonRequiredErrors = errors.filter(error => error.level !== 'required');
|
(typeof value === 'string' && value.trim() === '') ||
|
||||||
|
(Array.isArray(value) && value.length === 0));
|
||||||
|
|
||||||
|
// Get error messages for the tooltip
|
||||||
|
const errorMessages = shouldShowErrorIcon
|
||||||
|
? errors.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
|
||||||
const isMultiline = typeof field.fieldType === 'object' &&
|
const isMultiline = typeof field.fieldType === 'object' &&
|
||||||
@@ -235,7 +254,7 @@ const ValidationCell = ({
|
|||||||
|
|
||||||
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 ? 'border-red-500' : (isRequiredButEmpty ? 'border-red-500' : '')} ${cellHeight}`}>
|
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''} ${cellHeight}`}>
|
||||||
|
|
||||||
|
|
||||||
<div className={`truncate overflow-hidden ${isMultiline ? 'h-full' : ''}`}>
|
<div className={`truncate overflow-hidden ${isMultiline ? 'h-full' : ''}`}>
|
||||||
@@ -248,10 +267,10 @@ const ValidationCell = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{nonRequiredErrors.length > 0 && (
|
{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={{
|
||||||
message: nonRequiredErrors.map(e => e.message).join('\n'),
|
message: errorMessages,
|
||||||
level: 'error'
|
level: 'error'
|
||||||
}} />
|
}} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -562,17 +562,51 @@ const ValidationContainer = <T extends string>({
|
|||||||
onNext?.(data)
|
onNext?.(data)
|
||||||
}, [onNext, data, applyItemNumbersToData]);
|
}, [onNext, data, applyItemNumbersToData]);
|
||||||
|
|
||||||
// Delete selected rows - memoized
|
|
||||||
const deleteSelectedRows = useCallback(() => {
|
const deleteSelectedRows = useCallback(() => {
|
||||||
|
// Get selected row indices
|
||||||
const selectedRowIndexes = Object.keys(rowSelection).map(Number);
|
const selectedRowIndexes = Object.keys(rowSelection).map(Number);
|
||||||
const newData = data.filter((_, index) => !selectedRowIndexes.includes(index));
|
|
||||||
|
if (selectedRowIndexes.length === 0) {
|
||||||
|
toast.error("No rows selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort indices in descending order to avoid index shifting during removal
|
||||||
|
const sortedIndices = [...selectedRowIndexes].sort((a, b) => b - a);
|
||||||
|
|
||||||
|
// Create a new array without the selected rows
|
||||||
|
const newData = [...data];
|
||||||
|
|
||||||
|
// Remove rows from bottom up to avoid index issues
|
||||||
|
sortedIndices.forEach(index => {
|
||||||
|
if (index >= 0 && index < newData.length) {
|
||||||
|
newData.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the data with rows removed
|
||||||
setData(newData);
|
setData(newData);
|
||||||
|
|
||||||
|
// Clear row selection
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
|
|
||||||
|
// Show success message
|
||||||
toast.success(
|
toast.success(
|
||||||
selectedRowIndexes.length === 1
|
selectedRowIndexes.length === 1
|
||||||
? "Row deleted"
|
? "Row deleted"
|
||||||
: `${selectedRowIndexes.length} rows deleted`
|
: `${selectedRowIndexes.length} rows deleted`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reindex the data in the next render cycle
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// Update indices to maintain consistency
|
||||||
|
setData(current =>
|
||||||
|
current.map((row, newIndex) => ({
|
||||||
|
...row,
|
||||||
|
__index: String(newIndex)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
}, [data, rowSelection, setData, setRowSelection]);
|
}, [data, rowSelection, setData, setRowSelection]);
|
||||||
|
|
||||||
// Enhanced ValidationTable component that's aware of item numbers
|
// Enhanced ValidationTable component that's aware of item numbers
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -137,7 +137,7 @@ export const validateSpecialFields = <T extends string>(row: Data<T>): Record<st
|
|||||||
message: 'Supplier is required',
|
message: 'Supplier is required',
|
||||||
level: 'error',
|
level: 'error',
|
||||||
source: ErrorSources.Row
|
source: ErrorSources.Row
|
||||||
} as ErrorType]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate company field
|
// Validate company field
|
||||||
@@ -146,7 +146,7 @@ export const validateSpecialFields = <T extends string>(row: Data<T>): Record<st
|
|||||||
message: 'Company is required',
|
message: 'Company is required',
|
||||||
level: 'error',
|
level: 'error',
|
||||||
source: ErrorSources.Row
|
source: ErrorSources.Row
|
||||||
} as ErrorType]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|||||||
Reference in New Issue
Block a user