'use client' import { Suspense, use, useState } from 'react' import Link from 'next/link' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { Switch } from '@/components/ui/switch' import { Label } from '@/components/ui/label' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { ArrowLeft, ShieldAlert, CheckCircle2, AlertCircle, MoreHorizontal, ShieldCheck, UserX, StickyNote, Loader2, } from 'lucide-react' import { toast } from 'sonner' import { formatDistanceToNow } from 'date-fns' interface PageProps { params: Promise<{ id: string }> } function COIManagementContent({ roundId }: { roundId: string }) { const [conflictsOnly, setConflictsOnly] = useState(false) const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId }) const { data: coiList, isLoading: loadingCOI } = trpc.evaluation.listCOIByRound.useQuery({ roundId, hasConflictOnly: conflictsOnly || undefined, }) const utils = trpc.useUtils() const reviewCOI = trpc.evaluation.reviewCOI.useMutation({ onSuccess: (data) => { utils.evaluation.listCOIByRound.invalidate({ roundId }) toast.success(`COI marked as "${data.reviewAction}"`) }, onError: (error) => { toast.error(error.message || 'Failed to review COI') }, }) if (loadingRound || loadingCOI) { return } if (!round) { return (

Round Not Found

) } const conflictCount = coiList?.filter((c) => c.hasConflict).length ?? 0 const totalCount = coiList?.length ?? 0 const reviewedCount = coiList?.filter((c) => c.reviewAction).length ?? 0 const getReviewBadge = (reviewAction: string | null) => { switch (reviewAction) { case 'cleared': return ( Cleared ) case 'reassigned': return ( Reassigned ) case 'noted': return ( Noted ) default: return ( Pending Review ) } } const getConflictTypeBadge = (type: string | null) => { switch (type) { case 'financial': return Financial case 'personal': return Personal case 'organizational': return Organizational case 'other': return Other default: return null } } return (
{/* Header */}
{round.program.name} / {round.name}

Conflict of Interest Declarations

{/* Stats */}
Total Declarations
{totalCount}
Conflicts Declared
{conflictCount}
Reviewed
{reviewedCount}
{/* COI Table */}
Declarations Review and manage conflict of interest declarations from jury members
{coiList && coiList.length > 0 ? (
Project Juror Conflict Type Description Status Actions {coiList.map((coi) => ( {coi.assignment.project.title} {coi.user.name || coi.user.email} {coi.hasConflict ? ( Yes ) : ( No )} {coi.hasConflict ? getConflictTypeBadge(coi.conflictType) : '-'} {coi.description ? ( {coi.description} ) : ( - )} {coi.hasConflict ? (
{getReviewBadge(coi.reviewAction)} {coi.reviewedBy && (

by {coi.reviewedBy.name || coi.reviewedBy.email} {coi.reviewedAt && ( <> {formatDistanceToNow(new Date(coi.reviewedAt), { addSuffix: true })} )}

)}
) : ( N/A )}
{coi.hasConflict && ( reviewCOI.mutate({ id: coi.id, reviewAction: 'cleared', }) } > Clear reviewCOI.mutate({ id: coi.id, reviewAction: 'reassigned', }) } > Reassign reviewCOI.mutate({ id: coi.id, reviewAction: 'noted', }) } > Note )}
))}
) : (

No Declarations Yet

{conflictsOnly ? 'No conflicts of interest have been declared for this round' : 'No jury members have submitted COI declarations for this round yet'}

)}
) } function COISkeleton() { return (
{[1, 2, 3].map((i) => ( ))}
) } export default function COIManagementPage({ params }: PageProps) { const { id } = use(params) return ( }> ) }