From c7488b3e07bace6f687804801e88be81e1b21f4a Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 12 Apr 2026 23:20:48 -0400 Subject: [PATCH] fix: save roundId on admin file upload and group assignments by round The admin upload flow accepted roundId but never wrote it to the ProjectFile record, causing all admin-uploaded files to appear under "General". Fixed the create call, the listByProject filter, and the listByProjectForStage grouping to also use the direct roundId field. Jury assignments on the project detail page are now grouped by round with per-round completion counts instead of a flat list. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/(admin)/admin/projects/[id]/page.tsx | 304 ++++++++++-------- .../jury/multi-window-doc-viewer.tsx | 4 +- src/server/routers/file.ts | 16 +- 3 files changed, 182 insertions(+), 142 deletions(-) diff --git a/src/app/(admin)/admin/projects/[id]/page.tsx b/src/app/(admin)/admin/projects/[id]/page.tsx index d43e0a3..0e0cf64 100644 --- a/src/app/(admin)/admin/projects/[id]/page.tsx +++ b/src/app/(admin)/admin/projects/[id]/page.tsx @@ -845,9 +845,10 @@ function ProjectDetailContent({ projectId }: { projectId: string }) { } : null, })) for (const f of files) { - const roundId = f.requirement?.roundId ?? null - const roundName = f.requirement?.round?.name ?? 'General' - const sortOrder = f.requirement?.round?.sortOrder ?? -1 + const roundId = f.requirement?.roundId ?? f.roundId ?? null + const matchedRound = roundId ? competitionRounds.find((r: any) => r.id === roundId) : null + const roundName = f.requirement?.round?.name ?? matchedRound?.name ?? 'General' + const sortOrder = f.requirement?.round?.sortOrder ?? matchedRound?.sortOrder ?? -1 const key = roundId ?? '_general' if (!groups.has(key)) { groups.set(key, { roundId, roundName, sortOrder, files: [] }) @@ -864,141 +865,172 @@ function ProjectDetailContent({ projectId }: { projectId: string }) { - {/* Assignments Section */} - {assignments && assignments.length > 0 && ( - - - -
-
- -
- -
- Jury Assignments -
- - {assignments.filter((a) => a.evaluation?.status === 'SUBMITTED') - .length}{' '} - of {assignments.length} evaluations completed - + {/* Assignments Section — grouped by round */} + {assignments && assignments.length > 0 && (() => { + // Group assignments by round + const roundGroups = new Map() + for (const a of assignments) { + const rId = a.round?.id ?? '_unknown' + const rName = a.round?.name ?? 'Unknown Round' + if (!roundGroups.has(rId)) { + roundGroups.set(rId, { roundId: rId, roundName: rName, assignments: [] }) + } + roundGroups.get(rId)!.assignments.push(a) + } + const groups = Array.from(roundGroups.values()) + + return ( + + + +
+
+ +
+ +
+ Jury Assignments +
+ + {assignments.filter((a) => a.evaluation?.status === 'SUBMITTED') + .length}{' '} + of {assignments.length} evaluations completed across {groups.length} round{groups.length !== 1 ? 's' : ''} + +
+
- -
- - - - - - Juror - Expertise - Status - Score - Decision - - - - - {assignments.map((assignment) => ( - { - if (assignment.evaluation?.status === 'SUBMITTED') { - setSelectedEvalAssignment(assignment) - } - }} - > - -
- -
-

- {assignment.user.name || 'Unnamed'} -

-

- {assignment.user.email} -

-
-
-
- -
- {assignment.user.expertiseTags?.slice(0, 2).map((tag) => ( - - {tag} - + + + {groups.map((group) => { + const submitted = group.assignments.filter((a) => a.evaluation?.status === 'SUBMITTED').length + return ( +
+
+

{group.roundName}

+ + {submitted} of {group.assignments.length} completed + +
+
+ + + Juror + Expertise + Status + Score + Decision + + + + + {group.assignments.map((assignment) => ( + { + if (assignment.evaluation?.status === 'SUBMITTED') { + setSelectedEvalAssignment(assignment) + } + }} + > + +
+ +
+

+ {assignment.user.name || 'Unnamed'} +

+

+ {assignment.user.email} +

+
+
+
+ +
+ {assignment.user.expertiseTags?.slice(0, 2).map((tag) => ( + + {tag} + + ))} + {(assignment.user.expertiseTags?.length || 0) > 2 && ( + + +{(assignment.user.expertiseTags?.length || 0) - 2} + + )} +
+
+ + + {(assignment.evaluation?.status || 'NOT_STARTED').replace( + '_', + ' ' + )} + + + + {assignment.evaluation?.globalScore !== null && + assignment.evaluation?.globalScore !== undefined ? ( + + {assignment.evaluation.globalScore}/10 + + ) : ( + - + )} + + + {assignment.evaluation?.binaryDecision !== null && + assignment.evaluation?.binaryDecision !== undefined ? ( + assignment.evaluation.binaryDecision ? ( +
+ + Yes +
+ ) : ( +
+ + No +
+ ) + ) : ( + - + )} +
+ + {assignment.evaluation?.status === 'SUBMITTED' && ( + + )} + +
))} - {(assignment.user.expertiseTags?.length || 0) > 2 && ( - - +{(assignment.user.expertiseTags?.length || 0) - 2} - - )} - - - - - {(assignment.evaluation?.status || 'NOT_STARTED').replace( - '_', - ' ' - )} - - - - {assignment.evaluation?.globalScore !== null && - assignment.evaluation?.globalScore !== undefined ? ( - - {assignment.evaluation.globalScore}/10 - - ) : ( - - - )} - - - {assignment.evaluation?.binaryDecision !== null && - assignment.evaluation?.binaryDecision !== undefined ? ( - assignment.evaluation.binaryDecision ? ( -
- - Yes -
- ) : ( -
- - No -
- ) - ) : ( - - - )} -
- - {assignment.evaluation?.status === 'SUBMITTED' && ( - - )} - - - ))} -
-
-
- - - )} + + +
+ ) + })} + +
+
+ ) + })()} {/* Evaluation Detail Sheet */} = {} for (const file of files) { - const roundName = file.requirement?.round?.name ?? 'General' - const rId = file.requirement?.round?.id ?? null + const rId = file.requirement?.round?.id ?? (file as any).roundId ?? null + const roundName = file.requirement?.round?.name ?? (rId ? 'Round Files' : 'General') const sortOrder = file.requirement?.round?.sortOrder ?? 999 if (!groupMap[roundName]) { groupMap[roundName] = { roundId: rId, roundName, sortOrder, files: [] } diff --git a/src/server/routers/file.ts b/src/server/routers/file.ts index 34a1ea2..6d5f7fb 100644 --- a/src/server/routers/file.ts +++ b/src/server/routers/file.ts @@ -206,6 +206,7 @@ export const fileRouter = router({ const file = await ctx.prisma.projectFile.create({ data: { projectId: input.projectId, + roundId: input.roundId ?? null, fileType: input.fileType, fileName: input.fileName, mimeType: input.mimeType, @@ -341,7 +342,10 @@ export const fileRouter = router({ const where: Record = { projectId: input.projectId } if (input.roundId) { - where.requirement = { roundId: input.roundId } + where.OR = [ + { requirement: { roundId: input.roundId } }, + { roundId: input.roundId, requirementId: null }, + ] } return ctx.prisma.projectFile.findMany({ @@ -429,7 +433,8 @@ export const fileRouter = router({ projectId: input.projectId, OR: [ { requirement: { roundId: { in: eligibleRoundIds } } }, - { requirementId: null }, + { roundId: { in: eligibleRoundIds }, requirementId: null }, + { roundId: null, requirementId: null }, ], }, include: { @@ -454,7 +459,8 @@ export const fileRouter = router({ files: typeof files }> = [] - const generalFiles = files.filter((f) => !f.requirementId) + // Files with no round association at all go to General + const generalFiles = files.filter((f) => !f.requirementId && !f.roundId) if (generalFiles.length > 0) { grouped.push({ roundId: null, @@ -465,7 +471,9 @@ export const fileRouter = router({ } for (const round of eligibleRounds) { - const roundFiles = files.filter((f) => f.requirement?.roundId === round.id) + const roundFiles = files.filter( + (f) => f.requirement?.roundId === round.id || (!f.requirementId && f.roundId === round.id) + ) if (roundFiles.length > 0) { grouped.push({ roundId: round.id,