fix(mentor): restore Add Project on mentoring rounds + gate mentor assignment
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m15s

Three related bugs around the mentoring-round Projects tab:

1. Add Project to Round was unreachable on MENTORING rounds — the table swap
   in the prior commit lost the button. Export AddProjectDialog from
   project-states-table and render it inside MentoringProjectsTable with an
   "Add" button in the filter row and a CTA in the empty state.
2. The "Assign Projects" quick action on the round overview linked to the
   global pool with an opaque filter; on MENTORING rounds it now switches
   to the Projects tab where the new Add Project button + auto-fill +
   per-team picker all live. Non-mentoring rounds keep the old behavior.
3. mentor.assign and mentor.bulkAssign now refuse projects that aren't
   enrolled in any MENTORING round (any status). The single-assign throws
   BAD_REQUEST with a guidance message; the bulk path filters them out and
   reports ineligibleProjectCount in the result so the UI can warn the
   admin instead of silently skipping.

Tests: the multi-mentor-assignment suite now sets up a MENTORING round +
ProjectRoundState for each project it tests against, matching the new gate.
This commit is contained in:
Matt
2026-05-26 15:20:01 +02:00
parent c4f7216bc1
commit 61dfc608cd
5 changed files with 183 additions and 27 deletions

View File

@@ -370,6 +370,25 @@ export const mentorRouter = router({
where: { id: input.projectId },
})
// Gate: the project MUST be in a MENTORING round (any status, including
// DRAFT, ACTIVE, or CLOSED). We do not allow mentor assignment for
// projects that aren't part of a mentoring round — those should be
// added to a mentoring round first.
const inMentoringRound = await ctx.prisma.projectRoundState.findFirst({
where: {
projectId: input.projectId,
round: { roundType: 'MENTORING' },
},
select: { id: true },
})
if (!inMentoringRound) {
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'This project is not in a mentoring round. Add it to a mentoring round first, then assign mentors.',
})
}
// Verify mentor exists
const mentor = await ctx.prisma.user.findUniqueOrThrow({
where: { id: input.mentorId },
@@ -704,7 +723,13 @@ export const mentorRouter = router({
}
const projects = await ctx.prisma.project.findMany({
where: { id: { in: input.projectIds } },
where: {
id: { in: input.projectIds },
// Gate: only projects that are in some MENTORING round (any status)
projectRoundStates: {
some: { round: { roundType: 'MENTORING' } },
},
},
select: {
id: true,
title: true,
@@ -718,6 +743,16 @@ export const mentorRouter = router({
},
})
if (projects.length === 0) {
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'None of the selected projects are in a mentoring round. Add them to a mentoring round first.',
})
}
const ineligibleCount = input.projectIds.length - projects.length
// Track per-mentor (for emails) and per-project (for team intros) state.
const perMentor = new Map<
string,
@@ -884,6 +919,7 @@ export const mentorRouter = router({
return {
totalAssigned,
totalSkipped,
ineligibleProjectCount: ineligibleCount,
touchedProjectCount: touchedProjectIds.size,
perMentor: Array.from(perMentor.entries()).map(([id, b]) => ({
mentorId: id,