fix: submission round completion %, document details, project teams UX
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m32s

- Fix 0% completion on SUBMISSION pipeline cards — now based on teams
  with uploads / total projects instead of evaluation completions
- Add page count, detected language, and non-English warning indicator
  to Recent Documents list items
- Add project avatar (logo) to document and project list rows
- Make document rows clickable into project detail page
- Remove team name from project list (none have names), show country only
- Rename "Teams Submitted" to "Teams with Uploads" for clarity
- Add "See all" link to Projects section → /observer/projects
- Rename section from "Project Teams" to "Projects"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 15:50:51 +01:00
parent ec30dc83d6
commit 0390d05727
3 changed files with 99 additions and 48 deletions

View File

@@ -887,6 +887,7 @@ export const analyticsRouter = router({
allAssignmentCounts,
allCompletedEvals,
allDistinctJurors,
allSubmissionFileCounts,
] = await Promise.all([
ctx.prisma.projectRoundState.groupBy({
by: ['roundId', 'state'],
@@ -910,6 +911,15 @@ export const analyticsRouter = router({
by: ['roundId', 'userId'],
where: { roundId: { in: roundIds } },
}),
// Count distinct projects with files per round (for SUBMISSION completion)
ctx.prisma.$queryRaw<{ roundId: string; teamsWithFiles: bigint; totalFiles: bigint }[]>`
SELECT "roundId",
COUNT(DISTINCT "projectId")::bigint as "teamsWithFiles",
COUNT(id)::bigint as "totalFiles"
FROM "ProjectFile"
WHERE "roundId" = ANY(${roundIds})
GROUP BY "roundId"
`,
])
// Build lookup maps
@@ -935,16 +945,34 @@ export const analyticsRouter = router({
jurorCountByRound.set(j.roundId, (jurorCountByRound.get(j.roundId) || 0) + 1)
}
const submissionFilesByRound = new Map<string, { teamsWithFiles: number; totalFiles: number }>()
for (const sf of allSubmissionFileCounts) {
submissionFilesByRound.set(sf.roundId, {
teamsWithFiles: Number(sf.teamsWithFiles),
totalFiles: Number(sf.totalFiles),
})
}
const roundOverviews = rounds.map((round) => {
const stateBreakdown = statesByRound.get(round.id) || []
const totalProjects = stateBreakdown.reduce((sum, ps) => sum + ps.count, 0)
const totalAssignments = assignmentCountByRound.get(round.id) || 0
const completedEvaluations = completedEvalsByRound.get(round.id) || 0
const completionRate = (round.status === 'ROUND_CLOSED' || round.status === 'ROUND_ARCHIVED')
? 100
: totalAssignments > 0
? Math.min(100, Math.round((completedEvaluations / totalAssignments) * 100))
const submissionFiles = submissionFilesByRound.get(round.id)
let completionRate: number
if (round.status === 'ROUND_CLOSED' || round.status === 'ROUND_ARCHIVED') {
completionRate = 100
} else if (round.roundType === 'SUBMISSION') {
// For submission rounds, completion = teams that uploaded at least one file / total projects
completionRate = totalProjects > 0 && submissionFiles
? Math.min(100, Math.round((submissionFiles.teamsWithFiles / totalProjects) * 100))
: 0
} else if (totalAssignments > 0) {
completionRate = Math.min(100, Math.round((completedEvaluations / totalAssignments) * 100))
} else {
completionRate = 0
}
return {
roundId: round.id,
@@ -2143,8 +2171,12 @@ export const analyticsRouter = router({
fileName: true,
fileType: true,
createdAt: true,
pageCount: true,
detectedLang: true,
langConfidence: true,
analyzedAt: true,
project: {
select: { id: true, title: true, teamName: true },
select: { id: true, title: true, teamName: true, logoKey: true },
},
},
})