mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 02:11:14 -05:00
Fix partially approved submissions
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useImperativeHandle, forwardRef } from 'react';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
@@ -44,7 +44,7 @@ interface ModerationItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EntityFilter = 'all' | 'reviews' | 'submissions' | 'photos';
|
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 {
|
export interface ModerationQueueRef {
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
@@ -90,11 +90,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
switch (statusFilter) {
|
switch (statusFilter) {
|
||||||
case 'all':
|
case 'all':
|
||||||
reviewStatuses = ['pending', 'flagged', 'approved', 'rejected'];
|
reviewStatuses = ['pending', 'flagged', 'approved', 'rejected'];
|
||||||
submissionStatuses = ['pending', 'approved', 'rejected'];
|
submissionStatuses = ['pending', 'partially_approved', 'approved', 'rejected'];
|
||||||
break;
|
break;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
reviewStatuses = ['pending'];
|
reviewStatuses = ['pending'];
|
||||||
submissionStatuses = ['pending'];
|
submissionStatuses = ['pending', 'partially_approved'];
|
||||||
|
break;
|
||||||
|
case 'partially_approved':
|
||||||
|
reviewStatuses = [];
|
||||||
|
submissionStatuses = ['partially_approved'];
|
||||||
break;
|
break;
|
||||||
case 'flagged':
|
case 'flagged':
|
||||||
reviewStatuses = ['flagged'];
|
reviewStatuses = ['flagged'];
|
||||||
@@ -110,7 +114,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
reviewStatuses = ['pending', 'flagged'];
|
reviewStatuses = ['pending', 'flagged'];
|
||||||
submissionStatuses = ['pending'];
|
submissionStatuses = ['pending', 'partially_approved'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch reviews with entity data
|
// Fetch reviews with entity data
|
||||||
@@ -379,6 +383,58 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
}
|
}
|
||||||
}, [activeEntityFilter, activeStatusFilter, user]);
|
}, [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 (
|
const handleModerationAction = async (
|
||||||
item: ModerationItem,
|
item: ModerationItem,
|
||||||
action: 'approved' | 'rejected',
|
action: 'approved' | 'rejected',
|
||||||
@@ -939,6 +995,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return 'secondary';
|
return 'secondary';
|
||||||
|
case 'partially_approved':
|
||||||
|
return 'secondary';
|
||||||
case 'flagged':
|
case 'flagged':
|
||||||
return 'destructive';
|
return 'destructive';
|
||||||
case 'approved':
|
case 'approved':
|
||||||
@@ -958,6 +1016,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
switch (statusFilter) {
|
switch (statusFilter) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return `No pending ${entityLabel} require moderation at this time.`;
|
return `No pending ${entityLabel} require moderation at this time.`;
|
||||||
|
case 'partially_approved':
|
||||||
|
return `No partially approved ${entityLabel} found.`;
|
||||||
case 'flagged':
|
case 'flagged':
|
||||||
return `No flagged ${entityLabel} found.`;
|
return `No flagged ${entityLabel} found.`;
|
||||||
case 'approved':
|
case 'approved':
|
||||||
@@ -999,6 +1059,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
item.status === 'flagged' ? 'border-l-red-500' :
|
item.status === 'flagged' ? 'border-l-red-500' :
|
||||||
item.status === 'approved' ? 'border-l-green-500' :
|
item.status === 'approved' ? 'border-l-green-500' :
|
||||||
item.status === 'rejected' ? 'border-l-red-400' :
|
item.status === 'rejected' ? 'border-l-red-400' :
|
||||||
|
item.status === 'partially_approved' ? 'border-l-yellow-500' :
|
||||||
'border-l-amber-500'
|
'border-l-amber-500'
|
||||||
}`}>
|
}`}>
|
||||||
<CardHeader className={isMobile ? "pb-3 p-4" : "pb-4"}>
|
<CardHeader className={isMobile ? "pb-3 p-4" : "pb-4"}>
|
||||||
@@ -1023,8 +1084,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
)}
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant={getStatusBadgeVariant(item.status)} className={isMobile ? "text-xs" : ""}>
|
<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>
|
</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>
|
||||||
<div className={`flex items-center gap-2 text-muted-foreground ${isMobile ? 'text-xs' : 'text-sm'}`}>
|
<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"} />
|
<Calendar className={isMobile ? "w-3 h-3" : "w-4 h-4"} />
|
||||||
@@ -1468,6 +1536,41 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 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 */}
|
{/* Reviewer Information for approved/rejected items */}
|
||||||
{(item.status === 'approved' || item.status === 'rejected') && (item.reviewed_at || item.reviewer_notes) && (
|
{(item.status === 'approved' || item.status === 'rejected') && (item.reviewed_at || item.reviewer_notes) && (
|
||||||
<div className="space-y-3 pt-4 border-t">
|
<div className="space-y-3 pt-4 border-t">
|
||||||
@@ -1629,6 +1732,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Status</SelectItem>
|
<SelectItem value="all">All Status</SelectItem>
|
||||||
<SelectItem value="pending">Pending</SelectItem>
|
<SelectItem value="pending">Pending</SelectItem>
|
||||||
|
<SelectItem value="partially_approved">Partially Approved</SelectItem>
|
||||||
{activeEntityFilter !== 'submissions' && activeEntityFilter !== 'photos' && (
|
{activeEntityFilter !== 'submissions' && activeEntityFilter !== 'photos' && (
|
||||||
<SelectItem value="flagged">Flagged</SelectItem>
|
<SelectItem value="flagged">Flagged</SelectItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user