From 083773f1ea299d478d2b53ff06f9d9e001fd23cb Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:43:27 +0000 Subject: [PATCH] Fix partially approved submissions --- src/components/moderation/ModerationQueue.tsx | 116 +++++++++++++++++- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index fe78f545..8f909f13 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useImperativeHandle, forwardRef } from 'react'; -import { CheckCircle, XCircle, Eye, Calendar, User, Filter, MessageSquare, FileText, Image, X, Trash2, ListTree } from 'lucide-react'; +import { CheckCircle, XCircle, Eye, Calendar, User, Filter, MessageSquare, FileText, Image, X, Trash2, ListTree, RefreshCw, AlertCircle } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; @@ -44,7 +44,7 @@ interface ModerationItem { } type EntityFilter = 'all' | 'reviews' | 'submissions' | 'photos'; -type StatusFilter = 'all' | 'pending' | 'flagged' | 'approved' | 'rejected'; +type StatusFilter = 'all' | 'pending' | 'partially_approved' | 'flagged' | 'approved' | 'rejected'; export interface ModerationQueueRef { refresh: () => void; @@ -90,11 +90,15 @@ export const ModerationQueue = forwardRef((props, ref) => { switch (statusFilter) { case 'all': reviewStatuses = ['pending', 'flagged', 'approved', 'rejected']; - submissionStatuses = ['pending', 'approved', 'rejected']; + submissionStatuses = ['pending', 'partially_approved', 'approved', 'rejected']; break; case 'pending': reviewStatuses = ['pending']; - submissionStatuses = ['pending']; + submissionStatuses = ['pending', 'partially_approved']; + break; + case 'partially_approved': + reviewStatuses = []; + submissionStatuses = ['partially_approved']; break; case 'flagged': reviewStatuses = ['flagged']; @@ -110,7 +114,7 @@ export const ModerationQueue = forwardRef((props, ref) => { break; default: reviewStatuses = ['pending', 'flagged']; - submissionStatuses = ['pending']; + submissionStatuses = ['pending', 'partially_approved']; } // Fetch reviews with entity data @@ -379,6 +383,58 @@ export const ModerationQueue = forwardRef((props, ref) => { } }, [activeEntityFilter, activeStatusFilter, user]); + const handleRetryFailedItems = async (item: ModerationItem) => { + setActionLoading(item.id); + try { + // Fetch failed/rejected submission items + const { data: failedItems, error: fetchError } = await supabase + .from('submission_items') + .select('id') + .eq('submission_id', item.id) + .eq('status', 'rejected'); + + if (fetchError) throw fetchError; + + if (!failedItems || failedItems.length === 0) { + toast({ + title: "No Failed Items", + description: "All items have been processed successfully", + }); + return; + } + + // Call edge function to retry failed items + const { data, error } = await supabase.functions.invoke( + 'process-selective-approval', + { + body: { + itemIds: failedItems.map(i => i.id), + userId: user?.id, + submissionId: item.id + } + } + ); + + if (error) throw error; + + toast({ + title: "Retry Complete", + description: `Processed ${failedItems.length} failed item(s)`, + }); + + fetchItems(activeEntityFilter, activeStatusFilter); + } catch (error: any) { + console.error('Error retrying failed items:', error); + toast({ + title: "Retry Failed", + description: error.message, + variant: "destructive", + }); + } finally { + setActionLoading(null); + } + }; + const handleModerationAction = async ( item: ModerationItem, action: 'approved' | 'rejected', @@ -939,6 +995,8 @@ export const ModerationQueue = forwardRef((props, ref) => { switch (status) { case 'pending': return 'secondary'; + case 'partially_approved': + return 'secondary'; case 'flagged': return 'destructive'; case 'approved': @@ -958,6 +1016,8 @@ export const ModerationQueue = forwardRef((props, ref) => { switch (statusFilter) { case 'pending': return `No pending ${entityLabel} require moderation at this time.`; + case 'partially_approved': + return `No partially approved ${entityLabel} found.`; case 'flagged': return `No flagged ${entityLabel} found.`; case 'approved': @@ -999,6 +1059,7 @@ export const ModerationQueue = forwardRef((props, ref) => { 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' }`}> @@ -1023,8 +1084,15 @@ export const ModerationQueue = forwardRef((props, ref) => { )} - {item.status.charAt(0).toUpperCase() + item.status.slice(1)} + {item.status === 'partially_approved' ? 'Partially Approved' : + item.status.charAt(0).toUpperCase() + item.status.slice(1)} + {item.status === 'partially_approved' && ( + + + Needs Retry + + )}
@@ -1467,6 +1535,41 @@ export const ModerationQueue = forwardRef((props, ref) => {
)} + + {/* Retry button for partially approved items */} + {item.status === 'partially_approved' && item.type === 'content_submission' && ( +
+
+ +
+

This submission was partially approved

+

Some items failed to process. Click "Retry Failed Items" to process them again.

+
+
+
+ + +
+
+ )} {/* Reviewer Information for approved/rejected items */} {(item.status === 'approved' || item.status === 'rejected') && (item.reviewed_at || item.reviewer_notes) && ( @@ -1629,6 +1732,7 @@ export const ModerationQueue = forwardRef((props, ref) => { All Status Pending + Partially Approved {activeEntityFilter !== 'submissions' && activeEntityFilter !== 'photos' && ( Flagged )}