import { memo, useState, useCallback } from 'react'; import { CheckCircle, XCircle, Eye, Calendar, MessageSquare, FileText, Image, ListTree, RefreshCw, AlertCircle, Lock, Trash2, AlertTriangle, Edit } from 'lucide-react'; import { usePhotoSubmissionItems } from '@/hooks/usePhotoSubmissionItems'; import { PhotoGrid } from '@/components/common/PhotoGrid'; import { normalizePhotoData } from '@/lib/photoHelpers'; import type { PhotoItem } from '@/types/photos'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { format } from 'date-fns'; import { SubmissionItemsList } from './SubmissionItemsList'; import { MeasurementDisplay } from '@/components/ui/measurement-display'; import { ValidationSummary } from './ValidationSummary'; import type { ValidationResult } from '@/lib/entityValidationSchemas'; import type { LockStatus } from '@/lib/moderation/lockHelpers'; import type { ModerationItem } from '@/types/moderation'; interface QueueItemProps { item: ModerationItem; isMobile: boolean; actionLoading: string | null; isLockedByMe: boolean; isLockedByOther: boolean; lockStatus: LockStatus; currentLockSubmissionId?: string; notes: Record; isAdmin: boolean; isSuperuser: boolean; queueIsLoading: boolean; isInitialRender?: boolean; onNoteChange: (id: string, value: string) => void; onApprove: (item: ModerationItem, action: 'approved' | 'rejected', notes?: string) => void; onResetToPending: (item: ModerationItem) => void; onRetryFailed: (item: ModerationItem) => void; onOpenPhotos: (photos: any[], index: number) => void; onOpenReviewManager: (submissionId: string) => void; onClaimSubmission: (submissionId: string) => void; onDeleteSubmission: (item: ModerationItem) => void; onInteractionFocus: (id: string) => void; onInteractionBlur: (id: string) => void; } const getStatusBadgeVariant = (status: string): "default" | "secondary" | "destructive" | "outline" => { switch (status) { case 'pending': return 'default'; case 'approved': return 'secondary'; case 'rejected': return 'destructive'; case 'flagged': return 'destructive'; case 'partially_approved': return 'outline'; default: return 'outline'; } }; export const QueueItem = memo(({ item, isMobile, actionLoading, isLockedByMe, isLockedByOther, lockStatus, currentLockSubmissionId, notes, isAdmin, isSuperuser, queueIsLoading, isInitialRender = false, onNoteChange, onApprove, onResetToPending, onRetryFailed, onOpenPhotos, onOpenReviewManager, onClaimSubmission, onDeleteSubmission, onInteractionFocus, onInteractionBlur }: QueueItemProps) => { const [validationResult, setValidationResult] = useState(null); const [isClaiming, setIsClaiming] = useState(false); // Fetch relational photo data for photo submissions const { photos: photoItems, loading: photosLoading } = usePhotoSubmissionItems( item.submission_type === 'photo' ? item.id : undefined ); // Check if submission has any moderator-edited items const hasModeratorEdits = item.submission_items?.some( si => si.original_data && Object.keys(si.original_data).length > 0 ); const handleValidationChange = useCallback((result: ValidationResult) => { setValidationResult(result); }, []); const handleClaim = useCallback(async () => { setIsClaiming(true); await onClaimSubmission(item.id); setIsClaiming(false); }, [onClaimSubmission, item.id]); return ( 0 ? 'border-l-red-600' : item.status === 'flagged' ? 'border-l-red-500' : item.status === 'approved' ? 'border-l-green-500' : item.status === 'rejected' ? 'border-l-red-400' : item.status === 'partially_approved' ? 'border-l-yellow-500' : 'border-l-amber-500' }`} style={{ opacity: actionLoading === item.id ? 0.5 : (item._removing ? 0 : 1), pointerEvents: actionLoading === item.id ? 'none' : 'auto', transition: isInitialRender ? 'none' : 'all 300ms ease-in-out' }} >
{item.type === 'review' ? ( <> Review ) : item.submission_type === 'photo' ? ( <> Photo ) : ( <> Submission )} {item.status === 'partially_approved' ? 'Partially Approved' : item.status.charAt(0).toUpperCase() + item.status.slice(1)} {hasModeratorEdits && ( Edited

This submission has been modified by a moderator

)} {item.status === 'partially_approved' && ( Needs Retry )} {isLockedByOther && item.type === 'content_submission' && ( Locked by Another Moderator )} {currentLockSubmissionId === item.id && item.type === 'content_submission' && ( Claimed by You )} {item.submission_items && item.submission_items.length > 0 && ( )}
{format(new Date(item.created_at), isMobile ? 'MMM d, HH:mm:ss' : 'MMM d, yyyy HH:mm:ss.SSS')}

Full timestamp:

{item.created_at}

{item.user_profile && (
{(item.user_profile.display_name || item.user_profile.username)?.slice(0, 2).toUpperCase()}
{item.user_profile.display_name || item.user_profile.username} {item.user_profile.display_name && ( @{item.user_profile.username} )}
)}
{item.type === 'review' ? (
{item.content.title && (

{item.content.title}

)} {item.content.content && (

{item.content.content}

)}
Rating: {item.content.rating}/5
{/* Entity Names for Reviews */} {(item.entity_name || item.park_name) && (
{item.entity_name && (
{item.park_name ? 'Ride:' : 'Park:'} {item.entity_name}
)} {item.park_name && (
Park: {item.park_name}
)}
)} {item.content.photos && item.content.photos.length > 0 && (() => { const reviewPhotos: PhotoItem[] = normalizePhotoData({ type: 'review', photos: item.content.photos }); return (
Attached Photos:
{item.content.photos[0]?.caption && (

{item.content.photos[0].caption}

)}
); })()}
) : item.submission_type === 'photo' ? (
Photo Submission
{/* Submission Title */} {item.content.title && (
Title:

{item.content.title}

)} {/* Photos from relational table */} {photosLoading ? (
Loading photos...
) : photoItems.length > 0 ? (
Photos ({photoItems.length}): {import.meta.env.DEV && photoItems[0] && ( URL: {photoItems[0].cloudflare_image_url?.slice(0, 30)}... )}
({ id: photo.id, url: photo.cloudflare_image_url, filename: photo.filename || `Photo ${photo.order_index + 1}`, caption: photo.caption, title: photo.title, date_taken: photo.date_taken, }))} onPhotoClick={onOpenPhotos} />
) : ( No Photos Found This photo submission has no photos attached. This may be a data integrity issue. )} {/* Context Information */} {item.entity_name && (
For: {item.entity_name} {item.park_name && ( <> at {item.park_name} )}
)}
) : (
{/* Composite Submissions or Standard Submissions */}
)}
{/* Action buttons based on status */} {(item.status === 'pending' || item.status === 'flagged') && ( <> {/* Claim button for unclaimed submissions */} {!isLockedByOther && currentLockSubmissionId !== item.id && (
Unclaimed Submission
Claim this submission to lock it for 15 minutes while you review
)}