import { useState, useEffect } from 'react'; import { useToast } from '@/hooks/use-toast'; import { useUserRole } from '@/hooks/useUserRole'; import { useAuth } from '@/hooks/useAuth'; import { useRealtimeSubmissionItems } from '@/hooks/useRealtimeSubmissionItems'; import { fetchSubmissionItems, buildDependencyTree, detectDependencyConflicts, approveSubmissionItems, rejectSubmissionItems, escalateSubmission, type SubmissionItemWithDeps, type DependencyConflict } from '@/lib/submissionItemsService'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { AlertCircle, CheckCircle2, XCircle, Edit, Network, ArrowUp } from 'lucide-react'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useIsMobile } from '@/hooks/use-mobile'; import { ItemReviewCard } from './ItemReviewCard'; import { DependencyVisualizer } from './DependencyVisualizer'; import { ConflictResolutionDialog } from './ConflictResolutionDialog'; import { EscalationDialog } from './EscalationDialog'; import { RejectionDialog } from './RejectionDialog'; import { ItemEditDialog } from './ItemEditDialog'; interface SubmissionReviewManagerProps { submissionId: string; open: boolean; onOpenChange: (open: boolean) => void; onComplete: () => void; } export function SubmissionReviewManager({ submissionId, open, onOpenChange, onComplete }: SubmissionReviewManagerProps) { const [items, setItems] = useState([]); const [selectedItemIds, setSelectedItemIds] = useState>(new Set()); const [conflicts, setConflicts] = useState([]); const [loading, setLoading] = useState(false); const [showConflictDialog, setShowConflictDialog] = useState(false); const [showEscalationDialog, setShowEscalationDialog] = useState(false); const [showRejectionDialog, setShowRejectionDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); const [editingItem, setEditingItem] = useState(null); const [activeTab, setActiveTab] = useState<'items' | 'dependencies'>('items'); const { toast } = useToast(); const { isAdmin, isSuperuser } = useUserRole(); const { user } = useAuth(); const isMobile = useIsMobile(); const Container = isMobile ? Sheet : Dialog; // Set up realtime subscription for submission items useRealtimeSubmissionItems({ submissionId, onUpdate: (payload) => { console.log('Submission item updated in real-time:', payload); toast({ title: 'Item Updated', description: 'A submission item was updated by another moderator', }); loadSubmissionItems(); }, enabled: open && !!submissionId, }); useEffect(() => { if (open && submissionId) { loadSubmissionItems(); } }, [open, submissionId]); const loadSubmissionItems = async () => { setLoading(true); try { const fetchedItems = await fetchSubmissionItems(submissionId); const itemsWithDeps = buildDependencyTree(fetchedItems); setItems(itemsWithDeps); // Auto-select pending items const pendingIds = fetchedItems .filter(item => item.status === 'pending') .map(item => item.id); setSelectedItemIds(new Set(pendingIds)); } catch (error: any) { toast({ title: 'Error', description: error.message || 'Failed to load submission items', variant: 'destructive', }); } finally { setLoading(false); } }; const toggleItemSelection = (itemId: string) => { setSelectedItemIds(prev => { const next = new Set(prev); if (next.has(itemId)) { next.delete(itemId); } else { next.add(itemId); } return next; }); }; const handleCheckConflicts = async () => { setLoading(true); try { const detectedConflicts = await detectDependencyConflicts(items, Array.from(selectedItemIds)); setConflicts(detectedConflicts); if (detectedConflicts.length > 0) { setShowConflictDialog(true); } else { // No conflicts, proceed with approval handleApprove(); } } catch (error: any) { toast({ title: 'Error', description: error.message || 'Failed to check dependencies', variant: 'destructive', }); } finally { setLoading(false); } }; const handleApprove = async () => { if (!user?.id) { toast({ title: 'Authentication Required', description: 'You must be logged in to approve items', variant: 'destructive', }); return; } setLoading(true); try { const selectedItems = items.filter(item => selectedItemIds.has(item.id)); await approveSubmissionItems(selectedItems, user.id); toast({ title: 'Success', description: `Approved ${selectedItems.length} item(s)`, }); onComplete(); onOpenChange(false); } catch (error: any) { toast({ title: 'Error', description: error.message || 'Failed to approve items', variant: 'destructive', }); } finally { setLoading(false); } }; const handleRejectSelected = async () => { if (selectedItemIds.size === 0) { toast({ title: 'No Items Selected', description: 'Please select items to reject', variant: 'destructive', }); return; } if (!user?.id) { toast({ title: 'Authentication Required', description: 'You must be logged in to reject items', variant: 'destructive', }); return; } // Check if any selected items have dependents const selectedItems = items.filter(item => selectedItemIds.has(item.id)); const hasDependents = selectedItems.some(item => item.dependents && item.dependents.length > 0 ); setShowRejectionDialog(true); }; const handleReject = async (reason: string, cascade: boolean) => { if (!user?.id) return; setLoading(true); try { const selectedItems = items.filter(item => selectedItemIds.has(item.id)); await rejectSubmissionItems(selectedItems, reason, user.id, cascade); toast({ title: 'Items Rejected', description: `Successfully rejected ${selectedItems.length} item${selectedItems.length !== 1 ? 's' : ''}`, }); onComplete(); onOpenChange(false); } catch (error: any) { console.error('Error rejecting items:', error); toast({ title: 'Error', description: 'Failed to reject items. Please try again.', variant: 'destructive', }); } finally { setLoading(false); } }; const handleEscalate = async (reason: string) => { if (!user?.id) { toast({ title: 'Authentication Required', description: 'You must be logged in to escalate submissions', variant: 'destructive', }); return; } setLoading(true); try { await escalateSubmission(submissionId, reason, user.id); toast({ title: 'Escalated', description: 'Submission escalated to admin for review', }); onComplete(); onOpenChange(false); } catch (error: any) { toast({ title: 'Error', description: error.message || 'Failed to escalate submission', variant: 'destructive', }); } finally { setLoading(false); } }; const handleEdit = (item: SubmissionItemWithDeps) => { setEditingItem(item); setShowEditDialog(true); }; const handleEditComplete = async () => { setShowEditDialog(false); setEditingItem(null); await loadSubmissionItems(); }; const pendingCount = items.filter(item => item.status === 'pending').length; const selectedCount = selectedItemIds.size; return ( <> {isMobile ? ( Review Submission {pendingCount} pending item(s) • {selectedCount} selected ) : ( Review Submission {pendingCount} pending item(s) • {selectedCount} selected )} { await loadSubmissionItems(); await handleApprove(); }} /> selectedItemIds.has(item.id)).some(item => item.dependents && item.dependents.length > 0 )} onReject={handleReject} /> ); function ReviewContent() { return (
setActiveTab(v as any)} className="flex-1 flex flex-col"> Items ({items.length}) Dependencies
{items.length === 0 && !loading && ( No items found in this submission )} {items.map((item) => (
toggleItemSelection(item.id)} disabled={item.status !== 'pending'} /> handleEdit(item)} onStatusChange={(status) => {/* TODO: Update status */}} />
))}
{/* Action buttons */}
); } }