feat: show applicant's current round instead of assignments in members table
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 10:22:40 +01:00
parent 0d9a985377
commit ea46d7293f
2 changed files with 115 additions and 4 deletions

View File

@@ -426,7 +426,29 @@ export function MembersContent() {
</TableCell>
<TableCell>
<div>
{user.role === 'MENTOR' ? (
{user.role === 'APPLICANT' ? (
(() => {
const info = (user as unknown as { applicantRoundInfo?: { roundName: string; state: string } | null }).applicantRoundInfo
if (!info) return <span className="text-sm text-muted-foreground">-</span>
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 (
<div className="flex flex-col gap-0.5">
<span className="text-sm">{info.roundName}</span>
<Badge variant={stateColor} className="w-fit text-[10px] px-1.5 py-0">
{stateLabel}
</Badge>
</div>
)
})()
) : user.role === 'MENTOR' ? (
<p>{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.mentorAssignments} mentored</p>
) : (
<p>{(user as unknown as { _count: { mentorAssignments: number; assignments: number } })._count.assignments} assigned</p>
@@ -520,9 +542,33 @@ export function MembersContent() {
</div>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Assignments</span>
<span className="text-muted-foreground">
{user.role === 'APPLICANT' ? 'Current Round' : 'Assignments'}
</span>
<span>
{user.role === 'MENTOR'
{user.role === 'APPLICANT' ? (
(() => {
const info = (user as unknown as { applicantRoundInfo?: { roundName: string; state: string } | null }).applicantRoundInfo
if (!info) return <span className="text-muted-foreground">-</span>
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 (
<span className="flex items-center gap-1.5">
<span>{info.roundName}</span>
<Badge variant={stateColor} className="text-[10px] px-1.5 py-0">
{stateLabel}
</Badge>
</span>
)
})()
) : 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`}
</span>

View File

@@ -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<string, { roundName: string; state: string } | null>()
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,