import { useState, useEffect, memo } from 'react'; import { supabase } from '@/lib/supabaseClient'; import { SubmissionChangesDisplay } from './SubmissionChangesDisplay'; import { PhotoSubmissionDisplay } from './PhotoSubmissionDisplay'; import { RichParkDisplay } from './displays/RichParkDisplay'; import { RichRideDisplay } from './displays/RichRideDisplay'; import { RichCompanyDisplay } from './displays/RichCompanyDisplay'; import { RichRideModelDisplay } from './displays/RichRideModelDisplay'; import { RichTimelineEventDisplay } from './displays/RichTimelineEventDisplay'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { AlertCircle, Loader2 } from 'lucide-react'; import { format } from 'date-fns'; import type { SubmissionItemData } from '@/types/submissions'; import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData, RideModelSubmissionData } from '@/types/submission-data'; import type { TimelineSubmissionData } from '@/types/timeline'; import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler'; import { ModerationErrorBoundary } from '@/components/error/ModerationErrorBoundary'; interface SubmissionItemsListProps { submissionId: string; view?: 'summary' | 'detailed'; showImages?: boolean; } export const SubmissionItemsList = memo(function SubmissionItemsList({ submissionId, view = 'summary', showImages = true }: SubmissionItemsListProps) { const [items, setItems] = useState([]); const [hasPhotos, setHasPhotos] = useState(false); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); useEffect(() => { fetchSubmissionItems(); }, [submissionId]); const fetchSubmissionItems = async () => { try { // Only show skeleton on initial load, show refreshing indicator on refresh if (loading) { setLoading(true); } else { setRefreshing(true); } setError(null); // Use database function to fetch submission items with entity data in one query // This eliminates N+1 query problem and properly handles RLS/AAL2 checks const { data: itemsData, error: itemsError } = await supabase .rpc('get_submission_items_with_entities', { p_submission_id: submissionId }); if (itemsError) throw itemsError; // Transform to expected format with better null handling const transformedItems = (itemsData || []).map((item: any) => { // Ensure entity_data is at least an empty object, never null const safeEntityData = item.entity_data && typeof item.entity_data === 'object' ? item.entity_data : {}; return { ...item, item_data: safeEntityData, entity_data: item.entity_data // Keep original for debugging }; }); // Check for photo submissions (using array query to avoid 406) const { data: photoData, error: photoError } = await supabase .from('photo_submissions') .select('id') .eq('submission_id', submissionId); if (photoError) { handleNonCriticalError(photoError, { action: 'Check photo submissions', metadata: { submissionId } }); } setItems(transformedItems as SubmissionItemData[]); setHasPhotos(!!(photoData && photoData.length > 0)); } catch (err) { handleNonCriticalError(err, { action: 'Fetch submission items', metadata: { submissionId } }); setError('Failed to load submission details'); } finally { setLoading(false); setRefreshing(false); } }; if (loading) { return (
{view === 'detailed' && }
); } if (error) { return ( {error} ); } if (items.length === 0 && !hasPhotos) { return (
No items found for this submission
); } // Render item with appropriate display component const renderItem = (item: SubmissionItemData) => { // SubmissionItemData from submissions.ts has item_data property const entityData = item.item_data; const actionType = item.action_type || 'create'; // Show item metadata (order_index, depends_on, timestamps, test data flag) const itemMetadata = (
#{item.order_index ?? 0} {item.depends_on && ( Depends on: {item.depends_on.slice(0, 8)}... )} {(item as any).is_test_data && ( Test Data )} {item.created_at && ( Created: {format(new Date(item.created_at), 'MMM d, HH:mm:ss')} )} {item.updated_at && item.updated_at !== item.created_at && ( Updated: {format(new Date(item.updated_at), 'MMM d, HH:mm:ss')} )}
); // Use summary view for compact display if (view === 'summary') { return ( <> {itemMetadata} ); } // Use rich displays for detailed view - show BOTH rich display AND field-by-field changes if (item.item_type === 'park' && entityData) { return ( <> {itemMetadata}
All Fields (Detailed View)
); } if (item.item_type === 'ride' && entityData) { return ( <> {itemMetadata}
All Fields (Detailed View)
); } if ((['manufacturer', 'operator', 'designer', 'property_owner'] as const).some(type => type === item.item_type) && entityData) { return ( <> {itemMetadata}
All Fields (Detailed View)
); } if (item.item_type === 'ride_model' && entityData) { return ( <> {itemMetadata}
All Fields (Detailed View)
); } if ((item.item_type === 'milestone' || item.item_type === 'timeline_event') && entityData) { return ( <> {itemMetadata}
All Fields (Detailed View)
); } // Fallback to SubmissionChangesDisplay return ( <> {itemMetadata} ); }; return (
{refreshing && (
Refreshing...
)} {/* Show regular submission items */} {items.map((item) => (
{renderItem(item)}
))} {/* Show photo submission if exists */} {hasPhotos && (
)}
); });