import { useEffect, useState, useMemo } from 'react'; import { AlertCircle, CheckCircle, Info, AlertTriangle, RefreshCw } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas'; import { logger } from '@/lib/logger'; interface ValidationSummaryProps { item: { item_type: string; item_data: any; id?: string; }; onValidationChange?: (result: ValidationResult) => void; compact?: boolean; validationKey?: number; } export function ValidationSummary({ item, onValidationChange, compact = false, validationKey }: ValidationSummaryProps) { const [validationResult, setValidationResult] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isExpanded, setIsExpanded] = useState(false); const [manualTriggerCount, setManualTriggerCount] = useState(0); // Helper to extract the correct entity ID based on entity type const getEntityId = (itemType: string, itemData: any, fallbackId?: string): string | undefined => { // Try entity-specific ID fields first const entityIdField = `${itemType}_id`; if (itemData[entityIdField]) { return itemData[entityIdField]; } // For companies, check company_id if (['manufacturer', 'designer', 'operator', 'property_owner'].includes(itemType) && itemData.company_id) { return itemData.company_id; } // Fall back to generic id field or provided fallback return itemData.id || fallbackId; }; // Create stable reference for item_data to prevent unnecessary re-validations const itemDataString = useMemo( () => JSON.stringify(item.item_data), [item.item_data] ); useEffect(() => { async function validate() { setIsLoading(true); try { // Type guard for valid entity types type ValidEntityType = 'park' | 'ride' | 'manufacturer' | 'operator' | 'designer' | 'property_owner' | 'ride_model' | 'photo' | 'milestone' | 'timeline_event'; const validEntityTypes: ValidEntityType[] = ['park', 'ride', 'manufacturer', 'operator', 'designer', 'property_owner', 'ride_model', 'photo', 'milestone', 'timeline_event']; if (!validEntityTypes.includes(item.item_type as ValidEntityType)) { setValidationResult({ isValid: false, blockingErrors: [{ field: 'item_type', message: `Invalid entity type: ${item.item_type}`, severity: 'blocking' }], warnings: [], suggestions: [], allErrors: [{ field: 'item_type', message: `Invalid entity type: ${item.item_type}`, severity: 'blocking' }], }); setIsLoading(false); return; } const result = await validateEntityData( item.item_type as ValidEntityType, { ...item.item_data, id: getEntityId(item.item_type, item.item_data, item.id) } ); setValidationResult(result); onValidationChange?.(result); } catch (error: unknown) { logger.error('Entity validation failed', { action: 'validate_entity', entityType: item.item_type, error: error instanceof Error ? error.message : String(error) }); setValidationResult({ isValid: false, blockingErrors: [{ field: 'validation', message: 'Failed to validate', severity: 'blocking' }], warnings: [], suggestions: [], allErrors: [{ field: 'validation', message: 'Failed to validate', severity: 'blocking' }], }); } finally { setIsLoading(false); } } validate(); }, [item.item_type, itemDataString, item.id, validationKey, manualTriggerCount]); // Auto-expand when there are blocking errors or warnings useEffect(() => { if (validationResult && (validationResult.blockingErrors.length > 0 || validationResult.warnings.length > 0)) { setIsExpanded(true); } }, [validationResult]); if (isLoading) { return (
Validating...
); } if (!validationResult) { return null; } const hasBlockingErrors = validationResult.blockingErrors.length > 0; const hasWarnings = validationResult.warnings.length > 0; const hasSuggestions = validationResult.suggestions.length > 0; const hasAnyIssues = hasBlockingErrors || hasWarnings || hasSuggestions; // Compact view (for queue items) - NO HOVER, ALWAYS VISIBLE if (compact) { return (
{/* Status Badges */}
{validationResult.isValid && !hasWarnings && !hasSuggestions && ( Valid )} {hasBlockingErrors && ( {validationResult.blockingErrors.length} Error{validationResult.blockingErrors.length !== 1 ? 's' : ''} )} {hasWarnings && ( {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''} )}
{/* ALWAYS SHOW ERROR DETAILS - NO HOVER NEEDED */} {hasBlockingErrors && (

Blocking Errors:

    {validationResult.blockingErrors.map((error, i) => (
  • {error.field}: {error.message}
  • ))}
)} {hasWarnings && !hasBlockingErrors && (

Warnings:

    {validationResult.warnings.slice(0, 3).map((warning, i) => (
  • {warning.field}: {warning.message}
  • ))} {validationResult.warnings.length > 3 && (
  • ... and {validationResult.warnings.length - 3} more
  • )}
)}
); } // Detailed view (for review manager) return (
{/* Summary Badge */}
{validationResult.isValid && !hasWarnings && !hasSuggestions && ( All Valid )} {hasBlockingErrors && ( {validationResult.blockingErrors.length} Blocking Error{validationResult.blockingErrors.length !== 1 ? 's' : ''} )} {hasWarnings && ( {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''} )} {hasSuggestions && ( {validationResult.suggestions.length} Suggestion{validationResult.suggestions.length !== 1 ? 's' : ''} )}
{/* Detailed Issues */} {hasAnyIssues && ( {isExpanded ? 'Hide' : 'Show'} validation details {/* Blocking Errors */} {hasBlockingErrors && ( Blocking Errors {validationResult.blockingErrors.map((error, index) => (
{error.field}: {error.message}
))}
)} {/* Warnings */} {hasWarnings && ( Warnings {validationResult.warnings.map((warning, index) => (
{warning.field}: {warning.message}
))}
)} {/* Suggestions */} {hasSuggestions && ( Suggestions {validationResult.suggestions.map((suggestion, index) => (
{suggestion.field}: {suggestion.message}
))}
)}
)}
); }