Fix a few validation loading state issues

This commit is contained in:
2025-10-01 12:09:32 -04:00
parent 945e4a8cc3
commit e10df632d8
5 changed files with 123 additions and 85 deletions

View File

@@ -1,58 +0,0 @@
# ValidationStepNew Component
This is a refactored version of the original ValidationStep component with improved architecture, performance, and maintainability.
## Overview
The ValidationStepNew component is designed to replace the original ValidationStep component in the React Spreadsheet Import library. It provides the same functionality but with a more modular and maintainable codebase.
## Features
- Field-level validation (required, regex, unique)
- Row-level validation (supplier, company fields)
- UPC validation with API integration
- Template management (saving, loading, applying)
- Filtering and sorting capabilities
- Error display and management
- Special field handling (input, multi-input, select, checkbox)
## Usage
To use the new ValidationStepNew component, select the "Use new validation component" checkbox in the MatchColumnsStep. This will route you to the new implementation instead of the original one.
## Architecture
The component is structured as follows:
- **ValidationContainer**: Main container component that coordinates all subcomponents
- **ValidationTable**: Handles data display, filtering, and column configuration
- **ValidationCell**: Cell-level component with specialized rendering based on field type
- **ValidationToolbar**: Top toolbar with actions and statistics
- **ValidationSidebar**: Contains filters, actions, and other UI controls
## State Management
State is managed through custom hooks:
- **useValidationState**: Main state management hook
- **useValidation**: Validation logic
- **useTemplates**: Template management
- **useFilters**: Filtering logic
- **useUpcValidation**: UPC validation
## Development
This component is still under development. The goal is to eventually replace the original ValidationStep component completely once all functionality is implemented and tested.
## Known Issues
- Some TypeScript errors in the UploadFlow component when integrating with the new component
- Not all features from the original component are fully implemented yet
## Roadmap
1. Complete implementation of all features from the original component
2. Add comprehensive tests
3. Improve performance with virtualization for large datasets
4. Add more customization options
5. Replace the original component completely

View File

@@ -0,0 +1,73 @@
import React from 'react'
import { Loader2, Check, X } from 'lucide-react'
import { cn } from '@/lib/utils'
interface InitializationTask {
label: string
status: 'pending' | 'in_progress' | 'completed' | 'failed'
}
interface InitializingValidationProps {
totalRows: number
tasks: InitializationTask[]
}
export const InitializingValidation: React.FC<InitializingValidationProps> = ({
totalRows,
tasks
}) => {
return (
<div className="flex flex-col items-center justify-center h-[calc(100vh-10rem)]">
<Loader2 className="h-12 w-12 animate-spin text-primary mb-4" />
<h3 className="text-lg font-medium text-foreground mb-2">Initializing Validation</h3>
<p className="text-sm text-muted-foreground mb-6">Processing {totalRows} rows...</p>
{/* Task checklist */}
<div className="w-80 space-y-2">
{tasks.map((task, index) => (
<div
key={index}
className={cn(
"flex items-center gap-3 px-4 py-2 rounded-md transition-colors",
task.status === 'completed' && "bg-green-50 border border-green-200",
task.status === 'failed' && "bg-red-50 border border-red-200",
task.status === 'in_progress' && "bg-blue-50 border border-blue-200",
task.status === 'pending' && "bg-blue-50 border border-blue-200"
)}
>
{/* Status icon */}
<div className="flex-shrink-0">
{task.status === 'completed' && (
<Check className="h-4 w-4 text-green-600" />
)}
{task.status === 'failed' && (
<X className="h-4 w-4 text-red-600" />
)}
{task.status === 'in_progress' && (
<Loader2 className="h-4 w-4 text-blue-600 animate-spin" />
)}
{task.status === 'pending' && (
<Loader2 className="h-4 w-4 text-blue-600 animate-spin" />
)}
</div>
{/* Task label */}
<span
className={cn(
"text-sm font-medium",
task.status === 'completed' && "text-green-700",
task.status === 'failed' && "text-red-700",
task.status === 'in_progress' && "text-blue-700",
task.status === 'pending' && "text-blue-700"
)}
>
{task.label}
</span>
</div>
))}
</div>
</div>
)
}
export default InitializingValidation

View File

@@ -20,6 +20,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { Protected } from '@/components/auth/Protected' import { Protected } from '@/components/auth/Protected'
import { normalizeCountryCode } from '../utils/countryUtils' import { normalizeCountryCode } from '../utils/countryUtils'
import { cleanPriceField } from '../utils/priceUtils' import { cleanPriceField } from '../utils/priceUtils'
import InitializingValidation from './InitializingValidation'
/** /**
* ValidationContainer component - the main wrapper for the validation step * ValidationContainer component - the main wrapper for the validation step
* *
@@ -62,8 +63,8 @@ const ValidationContainer = <T extends string>({
fields, fields,
upcValidation, upcValidation,
isLoadingTemplates, isLoadingTemplates,
isValidating,
isInitializing, isInitializing,
initializationTasks,
validatingCells, validatingCells,
setValidatingCells, setValidatingCells,
editingCells, editingCells,
@@ -130,10 +131,6 @@ const ValidationContainer = <T extends string>({
const [isTemplateFormOpen, setIsTemplateFormOpen] = useState(false) const [isTemplateFormOpen, setIsTemplateFormOpen] = useState(false)
const [templateFormInitialData, setTemplateFormInitialData] = useState<any>(null) const [templateFormInitialData, setTemplateFormInitialData] = useState<any>(null)
const pendingInitializationTasks: string[] = []
if (isValidating) pendingInitializationTasks.push('validating rows')
if (upcValidation.validatingRows.size > 0) pendingInitializationTasks.push('checking UPCs')
if (isLoadingTemplates) pendingInitializationTasks.push('loading templates')
const [fieldOptions, setFieldOptions] = useState<any>(null) const [fieldOptions, setFieldOptions] = useState<any>(null)
// Track fields that need revalidation due to value changes // Track fields that need revalidation due to value changes
@@ -699,15 +696,17 @@ const ValidationContainer = <T extends string>({
// Show loading state during initialization // Show loading state during initialization
if (isInitializing) { if (isInitializing) {
// Convert initializationTasks object to array format
const tasksArray = Object.values(initializationTasks).map(task => ({
label: task.label,
status: task.status as 'pending' | 'in_progress' | 'completed' | 'failed'
}));
return ( return (
<div className="flex flex-col items-center justify-center h-[calc(100vh-10rem)]"> <InitializingValidation
<Loader2 className="h-12 w-12 animate-spin text-primary mb-4" /> totalRows={data.length}
<h3 className="text-lg font-medium text-foreground mb-2">Initializing Validation</h3> tasks={tasksArray}
<p className="text-sm text-muted-foreground">Processing {data.length} rows...</p> />
{pendingInitializationTasks.length > 0 && (
<p className="text-xs text-muted-foreground">Still {pendingInitializationTasks.join(' | ')}</p>
)}
</div>
); );
} }

View File

@@ -355,21 +355,22 @@ const ValidationTable = <T extends string>({
? row.original[field.key] ? row.original[field.key]
: row.original[field.key as keyof typeof row.original]; : row.original[field.key as keyof typeof row.original];
// Determine if this cell is in loading state - only show loading for empty fields // Determine if this cell is in loading state
let isLoading = false; let isLoading = false;
// Only show loading if the field is currently empty const cellLoadingKey = `${row.index}-${fieldKey}`;
const isEmpty = currentValue === undefined || currentValue === null || currentValue === '' || const isEmpty = currentValue === undefined || currentValue === null || currentValue === '' ||
(Array.isArray(currentValue) && currentValue.length === 0); (Array.isArray(currentValue) && currentValue.length === 0);
if (isEmpty) { // CRITICAL: Check validatingCells FIRST - this shows loading for item_number during UPC validation
// Check the validatingCells Set first (for item_number and other fields) // even if the field already has a value (because we're fetching a new one)
const cellLoadingKey = `${row.index}-${fieldKey}`;
if (validatingCells.has(cellLoadingKey)) { if (validatingCells.has(cellLoadingKey)) {
isLoading = true; isLoading = true;
} }
// Only show loading for empty fields for these other cases
else if (isEmpty) {
// Check if UPC is validating for this row and field is item_number // Check if UPC is validating for this row and field is item_number
else if (fieldKey === 'item_number' && isRowValidatingUpc(row.index)) { if (fieldKey === 'item_number' && isRowValidatingUpc(row.index)) {
isLoading = true; isLoading = true;
} }
// Add loading state for line/subline fields // Add loading state for line/subline fields

View File

@@ -280,14 +280,36 @@ export const useValidationState = <T extends string>({
}, [data, initialValidationComplete, upcValidation.initialValidationDone]); }, [data, initialValidationComplete, upcValidation.initialValidationDone]);
const hasPendingUpcValidation = upcValidation.validatingRows.size > 0; const hasPendingUpcValidation = upcValidation.validatingRows.size > 0;
// Separate initial validation from subsequent validations
// isInitializing should ONLY be true during the first load, never again
const isInitializing = const isInitializing =
!initialValidationComplete || !initialValidationComplete ||
isInitialValidationRunning || isInitialValidationRunning ||
templateManagement.isLoadingTemplates || templateManagement.isLoadingTemplates ||
hasPendingUpcValidation; (hasPendingUpcValidation && !upcValidation.initialValidationDone);
const isValidating = isInitialValidationRunning; const isValidating = isInitialValidationRunning;
// Track initialization task statuses for the progress UI
const initializationTasks = {
upcValidation: {
label: 'Validating UPCs and generating item numbers',
status: upcValidation.initialValidationDone ? 'completed' :
hasPendingUpcValidation ? 'in_progress' : 'pending'
},
fieldValidation: {
label: 'Validating field requirements and formats',
status: initialValidationComplete ? 'completed' :
isInitialValidationRunning ? 'in_progress' : 'pending'
},
templateLoading: {
label: 'Loading product templates',
status: !templateManagement.isLoadingTemplates ? 'completed' :
'in_progress'
}
};
// Targeted uniqueness revalidation: run only when item_number values change // Targeted uniqueness revalidation: run only when item_number values change
useEffect(() => { useEffect(() => {
if (!data || data.length === 0) return; if (!data || data.length === 0) return;
@@ -433,6 +455,7 @@ export const useValidationState = <T extends string>({
// Validation // Validation
isValidating, isValidating,
isInitializing, isInitializing,
initializationTasks,
validationErrors, validationErrors,
rowValidationStatus, rowValidationStatus,
validateRow, validateRow,