COI gate + admin review, mobile file viewer fixes for iOS
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m12s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m12s
- Integrate COI declaration dialog into jury evaluate page (blocks evaluation until declared) - Add COI review section to admin round page with clear/reassign/note actions - Fix mobile: remove inline preview (viewport too small), add labeled buttons - Fix iOS: open-in-new-tab uses synchronous window.open to avoid popup blocker - Fix iOS: download falls back to direct link if fetch+blob fails (CORS/Safari) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,8 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { MultiWindowDocViewer } from '@/components/jury/multi-window-doc-viewer'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ArrowLeft, Save, Send, AlertCircle, ThumbsUp, ThumbsDown, Clock, CheckCircle2 } from 'lucide-react'
|
||||
import { COIDeclarationDialog } from '@/components/forms/coi-declaration-dialog'
|
||||
import { ArrowLeft, Save, Send, AlertCircle, ThumbsUp, ThumbsDown, Clock, CheckCircle2, ShieldAlert } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import type { EvaluationConfig } from '@/types/competition-configs'
|
||||
|
||||
@@ -68,6 +69,14 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
{ enabled: !!myAssignment?.id }
|
||||
)
|
||||
|
||||
// COI (Conflict of Interest) check
|
||||
const { data: coiStatus, isLoading: coiLoading } = trpc.evaluation.getCOIStatus.useQuery(
|
||||
{ assignmentId: myAssignment?.id ?? '' },
|
||||
{ enabled: !!myAssignment?.id }
|
||||
)
|
||||
const [coiCompleted, setCOICompleted] = useState(false)
|
||||
const [coiHasConflict, setCOIHasConflict] = useState(false)
|
||||
|
||||
// Fetch the active evaluation form for this round
|
||||
const { data: activeForm } = trpc.evaluation.getStageForm.useQuery(
|
||||
{ roundId },
|
||||
@@ -385,6 +394,13 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// COI config
|
||||
const coiRequired = evalConfig?.coiRequired ?? true
|
||||
|
||||
// Determine COI state: declared via server or just completed in this session
|
||||
const coiDeclared = coiCompleted || coiStatus !== undefined
|
||||
const coiConflict = coiHasConflict || (coiStatus?.hasConflict ?? false)
|
||||
|
||||
// Check if round is active
|
||||
const isRoundActive = round.status === 'ROUND_ACTIVE'
|
||||
|
||||
@@ -421,6 +437,79 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// COI gate: if COI is required, not yet declared, and we have an assignment
|
||||
if (coiRequired && myAssignment && !coiLoading && !coiDeclared) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={`/jury/competitions/${roundId}/projects/${projectId}` as Route}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Project
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-brand-blue dark:text-foreground">
|
||||
Evaluate Project
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1">{project.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
<COIDeclarationDialog
|
||||
open={true}
|
||||
assignmentId={myAssignment.id}
|
||||
projectTitle={project.title}
|
||||
onComplete={(hasConflict) => {
|
||||
setCOICompleted(true)
|
||||
setCOIHasConflict(hasConflict)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// COI conflict declared — block evaluation
|
||||
if (coiRequired && coiConflict) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={`/jury/competitions/${roundId}/projects/${projectId}` as Route}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Project
|
||||
</Link>
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-brand-blue dark:text-foreground">
|
||||
Evaluate Project
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-1">{project.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Card className="border-l-4 border-l-amber-500">
|
||||
<CardContent className="flex items-start gap-4 p-6">
|
||||
<div className="rounded-xl bg-amber-50 dark:bg-amber-950/40 p-3">
|
||||
<ShieldAlert className="h-6 w-6 text-amber-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Conflict of Interest Declared</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
You declared a conflict of interest for this project. An administrator will
|
||||
review your declaration. You cannot evaluate this project while the conflict
|
||||
is under review.
|
||||
</p>
|
||||
<Button variant="outline" size="sm" className="mt-4" asChild>
|
||||
<Link href={`/jury/competitions/${roundId}` as Route}>
|
||||
Back to Round
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
Reference in New Issue
Block a user