diff --git a/docs/validation-hook-refactor.md b/docs/validation-hook-refactor.md
new file mode 100644
index 0000000..6895166
--- /dev/null
+++ b/docs/validation-hook-refactor.md
@@ -0,0 +1,131 @@
+
+
+# Refactoring Plan for Validation Code
+
+## Current Structure Analysis
+- **useValidationState.tsx**: ~1650 lines - Core validation state management
+- **useValidation.tsx**: ~425 lines - Field/data validation utility
+- **useUpcValidation.tsx**: ~410 lines - UPC-specific validation
+
+## Proposed New Structure
+
+### 1. Core Types & Utilities (150-200 lines)
+**File: `validation/types.ts`**
+- All interfaces and types (RowData, ValidationError, FilterState, Template, etc.)
+- Shared utility functions (isEmpty, getCellKey, etc.)
+
+**File: `validation/utils.ts`**
+- Generic validation utility functions
+- Caching mechanism and cache clearing helpers
+- API URL helpers
+
+### 2. Field Validation (300-350 lines)
+**File: `validation/hooks/useFieldValidation.ts`**
+- `validateField` function
+- Field-level validation logic
+- Required, regex, and other field validations
+
+### 3. Uniqueness Validation (250-300 lines)
+**File: `validation/hooks/useUniquenessValidation.ts`**
+- `validateUniqueField` function
+- `validateUniqueItemNumbers` function
+- All uniqueness checking logic
+
+### 4. UPC Validation (300-350 lines)
+**File: `validation/hooks/useUpcValidation.ts`**
+- `fetchProductByUpc` function
+- `validateUpc` function
+- `applyItemNumbersToData` function
+- UPC validation state management
+
+### 5. Validation Status Management (300-350 lines)
+**File: `validation/hooks/useValidationStatus.ts`**
+- Error state management
+- Row validation status tracking
+- Validation indicators and refs
+- Batch validation processing
+
+### 6. Data Management (300-350 lines)
+**File: `validation/hooks/useValidationData.ts`**
+- Data state management
+- Row updates
+- Data filtering
+- Initial data processing
+
+### 7. Template Management (250-300 lines)
+**File: `validation/hooks/useTemplateManagement.ts`**
+- Template saving
+- Template application
+- Template loading
+- Template display helpers
+
+### 8. Main Validation Hook (300-350 lines)
+**File: `validation/hooks/useValidation.ts`**
+- Main hook that composes all other hooks
+- Public API export
+- Initialization logic
+- Core validation flow
+
+## Function Distribution
+
+### Core Types & Utilities
+- All interfaces (InfoWithSource, ValidationState, etc.)
+- `isEmpty` utility
+- `getApiUrl` helper
+
+### Field Validation
+- `validateField`
+- `validateRow`
+- `validateData` (partial)
+- All validation result caching
+
+### Uniqueness Validation
+- `validateUniqueField`
+- `validateUniqueItemNumbers`
+- Uniqueness caching mechanisms
+
+### UPC Validation
+- `fetchProductByUpc`
+- `validateUpc`
+- `validateAllUPCs`
+- `applyItemNumbersToData`
+- UPC validation state tracking (cells, rows)
+
+### Validation Status Management
+- `startValidatingCell`/`stopValidatingCell`
+- `startValidatingRow`/`stopValidatingRow`
+- `isValidatingCell`/`isRowValidatingUpc`
+- Error state management
+- `revalidateRows`
+
+### Data Management
+- Initial data cleaning/processing
+- `updateRow`
+- `copyDown`
+- Search/filter functionality
+- `filteredData` calculation
+
+### Template Management
+- `saveTemplate`
+- `applyTemplate`
+- `applyTemplateToSelected`
+- `getTemplateDisplayText`
+- `loadTemplates`/`refreshTemplates`
+
+### Main Validation Hook
+- Composition of all other hooks
+- Initialization logic
+- Button/navigation handling
+- Field options management
+
+## Implementation Approach
+
+1. **Start with Types**: Create the types file first, as all other files will depend on it
+2. **Create Utility Functions**: Move shared utilities next
+3. **Build Core Validation**: Extract the field validation and uniqueness validation
+4. **Separate UPC Logic**: Move all UPC-specific code to its own module
+5. **Extract State Management**: Move data and status management to separate files
+6. **Move Template Logic**: Extract template functionality
+7. **Create Composition Hook**: Build the main hook that uses all other hooks
+
+This approach will give you more maintainable code with clearer separation of concerns, making it easier to understand, test, and modify each component independently.
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationTable.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationTable.tsx
index 9f0be32..e1a1bff 100644
--- a/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationTable.tsx
+++ b/inventory/src/components/product-import/steps/ValidationStepNew/components/ValidationTable.tsx
@@ -15,7 +15,7 @@ import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table'
import { Checkbox } from '@/components/ui/checkbox'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
-import { Loader2 } from 'lucide-react'
+import { Skeleton } from '@/components/ui/skeleton'
// Define a simple Error type locally to avoid import issues
type ErrorType = {
@@ -67,10 +67,9 @@ const MemoizedTemplateSelect = React.memo(({
}) => {
if (isLoading) {
return (
-
+
+
+
);
}
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useTemplates.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useTemplates.tsx
deleted file mode 100644
index 5197d51..0000000
--- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useTemplates.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import { useState, useEffect, useCallback } from 'react'
-import { RowSelectionState } from '@tanstack/react-table'
-import { Template, RowData, getApiUrl } from './useValidationState'
-
-interface TemplateState {
- selectedTemplateId: string | null
- showSaveTemplateDialog: boolean
- newTemplateName: string
- newTemplateType: string
-}
-
-export const useTemplates = (
- data: RowData[],
- setData: React.Dispatch[]>>,
- toast: any,
- rowSelection: RowSelectionState
-) => {
- const [templates, setTemplates] = useState([])
- const [templateState, setTemplateState] = useState({
- selectedTemplateId: null,
- showSaveTemplateDialog: false,
- newTemplateName: '',
- newTemplateType: '',
- })
-
- // Load templates from API
- const loadTemplates = useCallback(async () => {
- try {
- console.log('Fetching templates...');
- const response = await fetch(`${getApiUrl()}/templates`)
- console.log('Templates response status:', response.status);
-
- if (!response.ok) throw new Error('Failed to fetch templates')
-
- const templateData = await response.json()
- console.log('Templates fetched successfully:', templateData);
-
- // Validate template data
- const validTemplates = templateData.filter((t: any) =>
- t && typeof t === 'object' && t.id && t.company && t.product_type
- );
-
- if (validTemplates.length !== templateData.length) {
- console.warn('Some templates were filtered out due to invalid data', {
- original: templateData.length,
- valid: validTemplates.length
- });
- }
-
- setTemplates(validTemplates)
- } catch (error) {
- console.error('Error loading templates:', error)
- toast({
- title: 'Error',
- description: 'Failed to load templates',
- })
- }
- }, [toast])
-
- // Save a new template based on selected rows
- const saveTemplate = useCallback(async (name: string, type: string) => {
- try {
- // Get selected rows
- const selectedRows = Object.keys(rowSelection)
- .map(index => data[parseInt(index)])
- .filter(Boolean)
-
- if (selectedRows.length === 0) {
- toast({
- title: 'Error',
- description: 'Please select at least one row to create a template',
- })
- return
- }
-
- // Create template based on selected rows
- const template: Template = {
- id: Date.now(), // Temporary ID, will be replaced by server
- company: selectedRows[0].company as string || '',
- product_type: type,
- ...selectedRows[0], // Copy all fields from the first selected row
- }
-
- // Remove metadata fields
- delete (template as any).__meta
- delete (template as any).__template
- delete (template as any).__original
- delete (template as any).__corrected
- delete (template as any).__changes
-
- // Send to API
- const response = await fetch(`${getApiUrl()}/templates`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- company: template.company,
- product_type: type,
- ...Object.fromEntries(
- Object.entries(template).filter(([key]) =>
- !['company', 'product_type'].includes(key)
- )
- )
- }),
- })
-
- if (!response.ok) {
- throw new Error('Failed to save template')
- }
-
- // Reload templates to get the server-generated ID
- await loadTemplates()
-
- toast({
- title: 'Success',
- description: `Template "${name}" saved successfully`,
- })
-
- // Reset dialog state
- setTemplateState(prev => ({
- ...prev,
- showSaveTemplateDialog: false,
- newTemplateName: '',
- newTemplateType: '',
- }))
- } catch (error) {
- console.error('Error saving template:', error)
- toast({
- title: 'Error',
- description: 'Failed to save template',
- })
- }
- }, [data, rowSelection, toast, loadTemplates])
-
- // Apply a template to selected rows
- const applyTemplate = useCallback((templateId: string, rowIndexes: number[]) => {
- const template = templates.find(t => t.id.toString() === templateId)
-
- if (!template) {
- toast({
- title: 'Error',
- description: 'Template not found',
- })
- return
- }
-
- setData(prevData => {
- const newData = [...prevData]
-
- rowIndexes.forEach(index => {
- if (index >= 0 && index < newData.length) {
- // Create a new row with template values
- const updatedRow = { ...newData[index] }
-
- // Apply template fields (excluding metadata and ID fields)
- Object.entries(template).forEach(([key, value]) => {
- if (!['id', 'company', 'product_type', 'created_at', 'updated_at'].includes(key)) {
- // Handle numeric values that might be stored as strings
- if (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value)) {
- // If it's a price field, add the dollar sign
- if (['msrp', 'cost_each'].includes(key)) {
- updatedRow[key as keyof typeof updatedRow] = `$${value}` as any;
- } else {
- updatedRow[key as keyof typeof updatedRow] = value as any;
- }
- }
- // Special handling for array fields like categories and ship_restrictions
- else if (key === 'categories' || key === 'ship_restrictions') {
- if (Array.isArray(value)) {
- updatedRow[key as keyof typeof updatedRow] = value as any;
- } else if (typeof value === 'string') {
- try {
- // Try to parse as JSON if it's a JSON string
- if (value.startsWith('[') && value.endsWith(']')) {
- const parsed = JSON.parse(value);
- updatedRow[key as keyof typeof updatedRow] = parsed as any;
- }
- // Otherwise, it might be a PostgreSQL array format like {val1,val2}
- else if (value.startsWith('{') && value.endsWith('}')) {
- const parsed = value.slice(1, -1).split(',');
- updatedRow[key as keyof typeof updatedRow] = parsed as any;
- }
- // If it's a single value, wrap it in an array
- else {
- updatedRow[key as keyof typeof updatedRow] = [value] as any;
- }
- } catch (error) {
- console.error(`Error parsing ${key}:`, error);
- // If parsing fails, use as-is
- updatedRow[key as keyof typeof updatedRow] = value as any;
- }
- } else {
- updatedRow[key as keyof typeof updatedRow] = value as any;
- }
- } else {
- updatedRow[key as keyof typeof updatedRow] = value as any;
- }
- }
- })
-
- // Mark the row as using this template
- updatedRow.__template = templateId
-
- // Update the row in the data array
- newData[index] = updatedRow
- }
- })
-
- return newData
- })
-
- toast({
- title: 'Success',
- description: `Template applied to ${rowIndexes.length} row(s)`,
- })
- }, [templates, setData, toast])
-
- // Get display text for a template
- const getTemplateDisplayText = useCallback((templateId: string | null) => {
- if (!templateId) return 'Select a template'
-
- const template = templates.find(t => t.id.toString() === templateId)
- return template
- ? `${template.company} - ${template.product_type}`
- : 'Unknown template'
- }, [templates])
-
- // Load templates on component mount and set up refresh event listener
- useEffect(() => {
- loadTemplates()
-
- // Add event listener for template refresh
- const handleRefreshTemplates = () => {
- loadTemplates()
- }
-
- window.addEventListener('refresh-templates', handleRefreshTemplates)
-
- // Clean up event listener
- return () => {
- window.removeEventListener('refresh-templates', handleRefreshTemplates)
- }
- }, [loadTemplates])
-
- return {
- templates,
- selectedTemplateId: templateState.selectedTemplateId,
- showSaveTemplateDialog: templateState.showSaveTemplateDialog,
- newTemplateName: templateState.newTemplateName,
- newTemplateType: templateState.newTemplateType,
- setTemplateState,
- loadTemplates,
- saveTemplate,
- applyTemplate,
- getTemplateDisplayText,
- // Helper method to apply to selected rows
- applyTemplateToSelected: (templateId: string) => {
- const selectedIndexes = Object.keys(rowSelection).map(i => parseInt(i))
- applyTemplate(templateId, selectedIndexes)
- }
- }
-}
\ No newline at end of file
diff --git a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useUpcValidation.tsx b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useUpcValidation.tsx
index a6866f9..f72cd7a 100644
--- a/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useUpcValidation.tsx
+++ b/inventory/src/components/product-import/steps/ValidationStepNew/hooks/useUpcValidation.tsx
@@ -30,13 +30,6 @@ export const useUpcValidation = (
const processedUpcMapRef = useRef(new Map());
const initialUpcValidationDoneRef = useRef(false);
- // For batch validation
- const validationQueueRef = useRef>([]);
- const isProcessingBatchRef = useRef(false);
-
- // For validation results
- const [upcValidationResults] = useState