mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 13:51:12 -05:00
Fix partially approved submissions
This commit is contained in:
@@ -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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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'
|
||||
}`}>
|
||||
<CardHeader className={isMobile ? "pb-3 p-4" : "pb-4"}>
|
||||
@@ -1023,8 +1084,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
||||
)}
|
||||
</Badge>
|
||||
<Badge variant={getStatusBadgeVariant(item.status)} className={isMobile ? "text-xs" : ""}>
|
||||
{item.status.charAt(0).toUpperCase() + item.status.slice(1)}
|
||||
{item.status === 'partially_approved' ? 'Partially Approved' :
|
||||
item.status.charAt(0).toUpperCase() + item.status.slice(1)}
|
||||
</Badge>
|
||||
{item.status === 'partially_approved' && (
|
||||
<Badge variant="outline" className="bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300 border-yellow-300 dark:border-yellow-700">
|
||||
<AlertCircle className="w-3 h-3 mr-1" />
|
||||
Needs Retry
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className={`flex items-center gap-2 text-muted-foreground ${isMobile ? 'text-xs' : 'text-sm'}`}>
|
||||
<Calendar className={isMobile ? "w-3 h-3" : "w-4 h-4"} />
|
||||
@@ -1467,6 +1535,41 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Retry button for partially approved items */}
|
||||
{item.status === 'partially_approved' && item.type === 'content_submission' && (
|
||||
<div className="space-y-3 pt-4 border-t bg-yellow-50 dark:bg-yellow-950/20 -mx-4 px-4 py-3 rounded-b-lg">
|
||||
<div className="flex items-start gap-2 text-sm text-yellow-800 dark:text-yellow-300">
|
||||
<AlertCircle className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium">This submission was partially approved</p>
|
||||
<p className="text-xs mt-1">Some items failed to process. Click "Retry Failed Items" to process them again.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setSelectedSubmissionId(item.id);
|
||||
setReviewManagerOpen(true);
|
||||
}}
|
||||
disabled={actionLoading === item.id}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
<ListTree className="w-4 h-4 mr-2" />
|
||||
Review Items
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleRetryFailedItems(item)}
|
||||
disabled={actionLoading === item.id}
|
||||
className="flex-1 bg-yellow-600 hover:bg-yellow-700"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
Retry Failed Items
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 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<ModerationQueueRef>((props, ref) => {
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Status</SelectItem>
|
||||
<SelectItem value="pending">Pending</SelectItem>
|
||||
<SelectItem value="partially_approved">Partially Approved</SelectItem>
|
||||
{activeEntityFilter !== 'submissions' && activeEntityFilter !== 'photos' && (
|
||||
<SelectItem value="flagged">Flagged</SelectItem>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user