import { useEffect, useState, useMemo } from 'react'; import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { RefreshButton } from '@/components/ui/refresh-button'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas'; import { handleNonCriticalError } from '@/lib/errorHandler'; import type { SubmissionItemData } from '@/types/moderation'; interface ValidationSummaryProps { item: { item_type: string; item_data: SubmissionItemData; 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); const [isRevalidating, setIsRevalidating] = useState(false); // Helper to extract the correct entity ID based on entity type const getEntityId = ( itemType: string, itemData: SubmissionItemData | null | undefined, fallbackId?: string ): string | undefined => { // Guard against null/undefined itemData if (!itemData) return fallbackId; // Try entity-specific ID fields first const entityIdField = `${itemType}_id`; const typedData = itemData as unknown as Record; if (typeof typedData[entityIdField] === 'string') { return typedData[entityIdField] as string; } // For companies, check company_id if (['manufacturer', 'designer', 'operator', 'property_owner'].includes(itemType) && typeof typedData.company_id === 'string') { return typedData.company_id; } // Fall back to generic id field or provided fallback if (typeof typedData.id === 'string') { return typedData.id; } return 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 || {}), // Add null coalescing id: getEntityId(item.item_type, item.item_data, item.id) } ); setValidationResult(result); onValidationChange?.(result); } catch (error: unknown) { handleNonCriticalError(error, { action: 'Validate entity', metadata: { entityType: item.item_type } }); 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' : ''} )} { setIsRevalidating(true); try { setManualTriggerCount(prev => prev + 1); // Short delay to show feedback await new Promise(resolve => setTimeout(resolve, 500)); } finally { setIsRevalidating(false); } }} isLoading={isRevalidating} size="sm" variant="outline" className="text-xs h-7" > Re-validate
{/* 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}
))}
)}
)}
); }