From ea46d7293f6f6daa7ad4ead8b731a0ab72240cd9 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 6 Mar 2026 10:22:40 +0100 Subject: [PATCH] feat: show applicant's current round instead of assignments in members table For APPLICANT users in the admin members list, the Assignments column now shows the project's current round name and state badge (Active, Pending, Rejected, etc.) instead of "0 assigned". Co-Authored-By: Claude Opus 4.6 --- src/components/admin/members-content.tsx | 52 ++++++++++++++++-- src/server/routers/user.ts | 67 +++++++++++++++++++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/src/components/admin/members-content.tsx b/src/components/admin/members-content.tsx index 71f5e20..fd15957 100644 --- a/src/components/admin/members-content.tsx +++ b/src/components/admin/members-content.tsx @@ -426,7 +426,29 @@ export function MembersContent() {
- {user.role === 'MENTOR' ? ( + {user.role === 'APPLICANT' ? ( + (() => { + const info = (user as unknown as { applicantRoundInfo?: { roundName: string; state: string } | null }).applicantRoundInfo + if (!info) return - + const stateColor = info.state === 'REJECTED' ? 'destructive' as const + : info.state === 'WITHDRAWN' ? 'secondary' as const + : info.state === 'PASSED' ? 'success' as const + : 'default' as const + const stateLabel = info.state === 'IN_PROGRESS' ? 'Active' + : info.state === 'PENDING' ? 'Pending' + : info.state === 'COMPLETED' ? 'Completed' + : info.state === 'PASSED' ? 'Passed' + : info.state + return ( +
+ {info.roundName} + + {stateLabel} + +
+ ) + })() + ) : user.role === 'MENTOR' ? (

{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.mentorAssignments} mentored

) : (

{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned

@@ -520,9 +542,33 @@ export function MembersContent() {
- Assignments + + {user.role === 'APPLICANT' ? 'Current Round' : 'Assignments'} + - {user.role === 'MENTOR' + {user.role === 'APPLICANT' ? ( + (() => { + const info = (user as unknown as { applicantRoundInfo?: { roundName: string; state: string } | null }).applicantRoundInfo + if (!info) return - + const stateColor = info.state === 'REJECTED' ? 'destructive' as const + : info.state === 'WITHDRAWN' ? 'secondary' as const + : info.state === 'PASSED' ? 'success' as const + : 'default' as const + const stateLabel = info.state === 'IN_PROGRESS' ? 'Active' + : info.state === 'PENDING' ? 'Pending' + : info.state === 'COMPLETED' ? 'Completed' + : info.state === 'PASSED' ? 'Passed' + : info.state + return ( + + {info.roundName} + + {stateLabel} + + + ) + })() + ) : user.role === 'MENTOR' ? `${(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.mentorAssignments} mentored` : `${(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned`} diff --git a/src/server/routers/user.ts b/src/server/routers/user.ts index 5812a79..5f7b0a0 100644 --- a/src/server/routers/user.ts +++ b/src/server/routers/user.ts @@ -297,8 +297,73 @@ export const userRouter = router({ const usersWithAvatars = await attachAvatarUrls(users) + // For APPLICANT users, attach their project's current round info + const applicantIds = users.filter((u) => u.role === 'APPLICANT').map((u) => u.id) + const applicantRoundMap = new Map() + + if (applicantIds.length > 0) { + // Find each applicant's project, then the latest round state + const projects = await ctx.prisma.project.findMany({ + where: { + OR: [ + { submittedByUserId: { in: applicantIds } }, + { teamMembers: { some: { userId: { in: applicantIds } } } }, + ], + }, + include: { + teamMembers: { select: { userId: true } }, + projectRoundStates: { + select: { + state: true, + round: { select: { name: true, sortOrder: true } }, + }, + orderBy: { round: { sortOrder: 'desc' } }, + }, + }, + }) + + // Build a map of userId -> project's current round info + for (const proj of projects) { + const userIds = [ + proj.submittedByUserId, + ...proj.teamMembers.map((tm) => tm.userId), + ].filter((id): id is string => id !== null) + + // Find the latest active round state (non-terminal first, fallback to terminal) + const latestActive = proj.projectRoundStates.find((rs) => + rs.state === 'IN_PROGRESS' || rs.state === 'PENDING' + ) + const latestTerminal = proj.projectRoundStates.find((rs) => + rs.state === 'REJECTED' || rs.state === 'WITHDRAWN' + ) + const latest = latestActive ?? proj.projectRoundStates[0] + + for (const uid of userIds) { + if (!applicantIds.includes(uid)) continue + if (latestTerminal && !latestActive) { + applicantRoundMap.set(uid, { + roundName: latestTerminal.round.name, + state: latestTerminal.state, + }) + } else if (latest) { + applicantRoundMap.set(uid, { + roundName: latest.round.name, + state: latest.state, + }) + } else { + applicantRoundMap.set(uid, null) + } + } + } + } + + const enrichedUsers = usersWithAvatars.map((u) => ({ + ...u, + applicantRoundInfo: applicantRoundMap.get(u.id) ?? null, + })) + return { - users: usersWithAvatars, + users: enrichedUsers, total, page, perPage,