From 8feb01f1c38a32fd217f851b4f56f0d8f2b2a637 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 21:11:18 +0000 Subject: [PATCH] Implement Phases 5 & 6 --- src/components/moderation/QueueItem.tsx | 518 ++---------------- .../renderers/EntitySubmissionDisplay.tsx | 57 ++ .../renderers/PhotoSubmissionDisplay.tsx | 87 +++ .../moderation/renderers/QueueItemActions.tsx | 404 ++++++++++++++ .../moderation/renderers/QueueItemContext.tsx | 67 +++ .../moderation/renderers/QueueItemHeader.tsx | 155 ++++++ .../moderation/renderers/ReviewDisplay.tsx | 71 +++ .../suites/moderationDependencyTests.ts | 155 ++++++ .../suites/moderationLockTests.ts | 294 ++++++++++ src/lib/integrationTests/testRunner.ts | 13 + tests/e2e/moderation/lock-management.spec.ts | 88 +++ 11 files changed, 1439 insertions(+), 470 deletions(-) create mode 100644 src/components/moderation/renderers/EntitySubmissionDisplay.tsx create mode 100644 src/components/moderation/renderers/PhotoSubmissionDisplay.tsx create mode 100644 src/components/moderation/renderers/QueueItemActions.tsx create mode 100644 src/components/moderation/renderers/QueueItemContext.tsx create mode 100644 src/components/moderation/renderers/QueueItemHeader.tsx create mode 100644 src/components/moderation/renderers/ReviewDisplay.tsx create mode 100644 src/lib/integrationTests/suites/moderationDependencyTests.ts create mode 100644 src/lib/integrationTests/suites/moderationLockTests.ts create mode 100644 tests/e2e/moderation/lock-management.spec.ts diff --git a/src/components/moderation/QueueItem.tsx b/src/components/moderation/QueueItem.tsx index 3ab2f500..abc2a066 100644 --- a/src/components/moderation/QueueItem.tsx +++ b/src/components/moderation/QueueItem.tsx @@ -1,29 +1,24 @@ import { memo, useState, useCallback } from 'react'; -import { CheckCircle, XCircle, Eye, Calendar, MessageSquare, FileText, Image, ListTree, RefreshCw, AlertCircle, Lock, Trash2, AlertTriangle, Edit, Info, ExternalLink, ChevronDown } 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 type { PhotoForDisplay } from '@/types/moderation'; -import { getSubmissionTypeLabel } from '@/lib/moderation/entities'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; -import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; -import { UserAvatar } from '@/components/ui/user-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 { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -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'; +import type { ModerationItem, PhotoForDisplay } from '@/types/moderation'; +import type { PhotoItem } from '@/types/photos'; import { handleError } from '@/lib/errorHandler'; +import { PhotoGrid } from '@/components/common/PhotoGrid'; +import { normalizePhotoData } from '@/lib/photoHelpers'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { AlertTriangle } from 'lucide-react'; +import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; +import { SubmissionItemsList } from './SubmissionItemsList'; +import { getSubmissionTypeLabel } from '@/lib/moderation/entities'; +import { QueueItemHeader } from './renderers/QueueItemHeader'; +import { ReviewDisplay } from './renderers/ReviewDisplay'; +import { PhotoSubmissionDisplay } from './renderers/PhotoSubmissionDisplay'; +import { EntitySubmissionDisplay } from './renderers/EntitySubmissionDisplay'; +import { QueueItemContext } from './renderers/QueueItemContext'; +import { QueueItemActions } from './renderers/QueueItemActions'; interface QueueItemProps { item: ModerationItem; @@ -51,16 +46,6 @@ interface QueueItemProps { 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, @@ -140,112 +125,18 @@ export const QueueItem = memo(({ pointerEvents: actionLoading === item.id ? 'none' : 'auto', transition: isInitialRender ? 'none' : 'all 300ms ease-in-out' }} + data-testid="queue-item" > -
-
- - {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} - - {item.user_profile.display_name && ( - - @{item.user_profile.username} - - )} -
-
- )} +
@@ -470,342 +361,29 @@ export const QueueItem = memo(({ )} - {/* 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 - -
-
-
-
- )} - -
- {/* Submitter Context - shown before moderator can add their notes */} - {(item.submission_items?.[0]?.item_data?.source_url || item.submission_items?.[0]?.item_data?.submission_notes) && ( -
-
- -

- Submitter Context -

-
- - {item.submission_items?.[0]?.item_data?.source_url && ( -
- Source: - - {item.submission_items[0].item_data.source_url} - - -
- )} - - {item.submission_items?.[0]?.item_data?.submission_notes && ( -
- Submitter Notes: -

- {item.submission_items[0].item_data.submission_notes} -

-
- )} -
- )} - - {/* Left: Notes textarea */} -
- -