diff --git a/src/app/(admin)/admin/rounds/[roundId]/page.tsx b/src/app/(admin)/admin/rounds/[roundId]/page.tsx
index 6effb46..c9bbcfd 100644
--- a/src/app/(admin)/admin/rounds/[roundId]/page.tsx
+++ b/src/app/(admin)/admin/rounds/[roundId]/page.tsx
@@ -98,6 +98,7 @@ import { WaitlistCard } from '@/components/admin/grand-finale/waitlist-card'
import { FinalistEnrollmentCard } from '@/components/admin/grand-finale/finalist-enrollment-card'
import { FinalDocsReminderButton } from '@/components/admin/grand-finale/final-docs-reminder-button'
import { FinalDocsUploadsToggle } from '@/components/admin/grand-finale/final-docs-uploads-toggle'
+import { ReviewDocsPicker } from '@/components/admin/grand-finale/review-docs-picker'
import { RankingDashboard } from '@/components/admin/round/ranking-dashboard'
import { CoverageReport } from '@/components/admin/assignment/coverage-report'
import { AssignmentPreviewSheet } from '@/components/admin/assignment/assignment-preview-sheet'
@@ -1543,6 +1544,7 @@ export default function RoundDetailPage() {
+
diff --git a/src/components/admin/grand-finale/review-docs-picker.tsx b/src/components/admin/grand-finale/review-docs-picker.tsx
new file mode 100644
index 0000000..4dbe710
--- /dev/null
+++ b/src/components/admin/grand-finale/review-docs-picker.tsx
@@ -0,0 +1,79 @@
+'use client'
+
+import { trpc } from '@/lib/trpc/client'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Checkbox } from '@/components/ui/checkbox'
+import { Switch } from '@/components/ui/switch'
+import { Label } from '@/components/ui/label'
+import { toast } from 'sonner'
+import { Eye } from 'lucide-react'
+
+/**
+ * Admin picker: which previously-submitted documents finale judges see on the
+ * review page. Default (switch off) shows everything; switching to curated
+ * mode starts with all slots ticked, and the admin unticks what to hide.
+ * Grand Final round uploads are always visible regardless.
+ */
+export function ReviewDocsPicker({ programId, roundId }: { programId: string; roundId: string }) {
+ const utils = trpc.useUtils()
+ const { data } = trpc.finalist.getReviewDocSettings.useQuery({ programId, roundId })
+ const set = trpc.finalist.setReviewVisibleRequirements.useMutation({
+ onSuccess: () => utils.finalist.getReviewDocSettings.invalidate({ programId, roundId }),
+ onError: (e) => toast.error(e.message),
+ })
+ if (!data || data.options.length === 0) return null
+
+ const curated = data.selectedIds !== null
+ const selected = new Set(data.selectedIds ?? data.options.map((o) => o.requirementId))
+ const toggleSlot = (id: string, on: boolean) => {
+ const next = new Set(selected)
+ if (on) next.add(id)
+ else next.delete(id)
+ set.mutate({ roundId, requirementIds: [...next] })
+ }
+
+ return (
+
+
+
+ Documents shown to judges
+
+
+ Choose which previously submitted documents judges see on the finalist review page.
+ Documents uploaded directly to this Grand Final round are always visible.
+
+
+
+
+
+ set.mutate({ roundId, requirementIds: v ? data.options.map((o) => o.requirementId) : null })}
+ />
+
+ {curated ? 'Curated — judges see only the checked documents' : 'Showing all submitted documents'}
+
+
+ {curated && (
+
+ {data.options.map((o) => (
+
+ toggleSlot(o.requirementId, v === true)}
+ />
+ {o.name} — {o.roundName}
+
+ ({o.fileCount} file{o.fileCount === 1 ? '' : 's'})
+
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/applicant/final-documents-banner.tsx b/src/components/applicant/final-documents-banner.tsx
index 614be06..aadd850 100644
--- a/src/components/applicant/final-documents-banner.tsx
+++ b/src/components/applicant/final-documents-banner.tsx
@@ -8,14 +8,15 @@ import { FileText, Video, CheckCircle2, Circle, Clock, Upload } from 'lucide-rea
export function FinalDocumentsBanner() {
const { data: status } = trpc.applicant.getFinalDocumentStatus.useQuery()
- if (!status) return null
+ if (!status || status.requirements.length === 0) return null
const fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'long', timeStyle: 'short' })
const zone = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' })
.formatToParts(new Date()).find((p) => p.type === 'timeZoneName')?.value
const uploadedCount = status.requirements.filter((r) => r.uploaded).length
const total = status.requirements.length
- const done = status.allRequiredUploaded
+ const optionalMode = !status.hasRequired
+ const done = status.hasRequired ? status.allRequiredUploaded : status.allUploaded
return (
@@ -24,7 +25,9 @@ export function FinalDocumentsBanner() {
{done ? : }
- {done ? 'Grand Final documents submitted' : 'Upload your Grand Final documents'}
+ {done
+ ? optionalMode ? 'Grand Final documents uploaded' : 'Grand Final documents submitted'
+ : optionalMode ? 'Upload updated Grand Final documents (optional)' : 'Upload your Grand Final documents'}
({uploadedCount} of {total})
diff --git a/src/components/applicant/final-documents-panel.tsx b/src/components/applicant/final-documents-panel.tsx
index fbe0902..934eff3 100644
--- a/src/components/applicant/final-documents-panel.tsx
+++ b/src/components/applicant/final-documents-panel.tsx
@@ -18,7 +18,8 @@ export function FinalDocumentsPanel(props: Props) {
{ enabled: props.variant === 'mentor' },
)
const status = props.variant === 'team' ? teamQuery.data : mentorQuery.data
- if (!status) return null
+ if (!status || status.requirements.length === 0) return null
+ const done = status.hasRequired ? status.allRequiredUploaded : status.allUploaded
const fmt = new Intl.DateTimeFormat(undefined, { dateStyle: 'long', timeStyle: 'short' })
return (
@@ -26,8 +27,8 @@ export function FinalDocumentsPanel(props: Props) {
Final Documents
- {status.allRequiredUploaded
- ? Submitted
+ {done
+ ? {status.hasRequired ? 'Submitted' : 'Uploaded'}
: status.deadline && (
Due {fmt.format(new Date(status.deadline))}
@@ -36,6 +37,7 @@ export function FinalDocumentsPanel(props: Props) {
{props.variant === 'team' ? 'Your final deliverables for the Grand Finale.' : 'This team\'s final deliverables for the Grand Finale.'}
+ {!status.hasRequired && ' These uploads are optional.'}
@@ -48,7 +50,7 @@ export function FinalDocumentsPanel(props: Props) {
{r.file?.fileName ?? 'Not yet uploaded'}
))}
- {props.variant === 'team' && !status.allRequiredUploaded && (
+ {props.variant === 'team' && !done && (