feat: admin evaluation editing, ranking improvements, status transition fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m26s

- Add adminEditEvaluation mutation and getJurorEvaluations query
- Create shared EvaluationEditSheet component with inline feedback editing
- Add Evaluations tab to member detail page (grouped by round)
- Make jury group member names clickable (link to member detail)
- Replace inline EvaluationDetailSheet on project page with shared component
- Fix project status transition validation (skip when status unchanged)
- Fix frontend to not send status when unchanged on project edit
- Ranking dashboard improvements and boolean decision converter fixes
- Backfill script updates for binary decisions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 10:46:52 +01:00
parent 49e706f2cf
commit c6ebd169dd
11 changed files with 857 additions and 245 deletions

View File

@@ -1780,4 +1780,87 @@ export const evaluationRouter = router({
submissions,
}
}),
/**
* Admin: edit the feedbackText on a submitted evaluation.
*/
adminEditEvaluation: adminProcedure
.input(
z.object({
evaluationId: z.string(),
feedbackText: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
const evaluation = await ctx.prisma.evaluation.findUnique({
where: { id: input.evaluationId },
select: { id: true, feedbackText: true },
})
if (!evaluation) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Evaluation not found' })
}
const updated = await ctx.prisma.evaluation.update({
where: { id: input.evaluationId },
data: { feedbackText: input.feedbackText },
})
await logAudit({
prisma: ctx.prisma,
userId: ctx.user.id,
action: 'ADMIN_EDIT_EVALUATION_FEEDBACK',
entityType: 'Evaluation',
entityId: input.evaluationId,
detailsJson: {
adminUserId: ctx.user.id,
before: (evaluation.feedbackText ?? '').slice(0, 200),
after: input.feedbackText.slice(0, 200),
},
})
return updated
}),
/**
* Admin: get all evaluations submitted by a specific juror.
*/
getJurorEvaluations: adminProcedure
.input(z.object({ userId: z.string() }))
.query(async ({ ctx, input }) => {
const assignments = await ctx.prisma.assignment.findMany({
where: { userId: input.userId },
include: {
project: { select: { id: true, title: true } },
round: { select: { id: true, name: true, roundType: true, sortOrder: true } },
evaluation: {
select: {
id: true,
globalScore: true,
binaryDecision: true,
feedbackText: true,
status: true,
submittedAt: true,
criterionScoresJson: true,
},
},
},
orderBy: [
{ round: { sortOrder: 'asc' } },
{ project: { title: 'asc' } },
],
})
return assignments
.filter((a) => a.evaluation !== null)
.map((a) => ({
assignmentId: a.id,
roundId: a.roundId,
roundName: a.round.name,
roundType: a.round.roundType,
projectId: a.project.id,
projectTitle: a.project.title,
evaluation: a.evaluation!,
}))
}),
})