Fix a few validation loading state issues
This commit is contained in:
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -355,27 +355,28 @@ 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
|
||||||
else if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) {
|
else if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
}
|
}
|
||||||
else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) {
|
else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user