feat: mentor detail side sheet + Teams column

The mentor list now ends with a Teams column showing chips of each
mentor's active assignments (truncated at 2 + overflow badge). Clicking
any row opens a right-side Sheet with the mentor's profile (expertise,
country, joined date, max assignments) and a per-team activity feed —
project, status (active / completed / dropped), assignment date, and
counts of messages / files / milestones with their last timestamp.

Stat cards on both the Mentor and Mentee panels were stale and not
particularly informative, so they're gone — the table itself is now
the focal element on each panel.

getMentorPool gained an activeTeams[] field; new getMentorDetail query
backs the side sheet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-28 19:52:17 +02:00
parent 030db533e1
commit 62ab27a05a
3 changed files with 387 additions and 103 deletions

View File

@@ -1016,6 +1016,7 @@ export const mentorRouter = router({
},
select: {
completionStatus: true,
project: { select: { id: true, title: true } },
messages: {
orderBy: { createdAt: 'desc' },
take: 1,
@@ -1043,9 +1044,14 @@ export const mentorRouter = router({
let current = 0
let completed = 0
const activityDates: Date[] = []
const activeTeams: { id: string; title: string }[] = []
for (const a of m.mentorAssignments) {
if (a.completionStatus === 'completed') completed++
else current++
if (a.completionStatus === 'completed') {
completed++
} else {
current++
activeTeams.push(a.project)
}
if (a.messages[0]) activityDates.push(a.messages[0].createdAt)
if (a.files[0]) activityDates.push(a.files[0].createdAt)
if (a.milestoneCompletions[0])
@@ -1069,6 +1075,7 @@ export const mentorRouter = router({
maxAssignments: m.maxAssignments,
capacityRemaining,
lastActivityAt,
activeTeams,
}
})
@@ -2305,4 +2312,81 @@ export const mentorRouter = router({
})
return dropped
}),
/**
* Per-mentor activity detail used by the admin mentor side sheet. For each
* (active or dropped) assignment, returns the project, key timestamps, and
* counts of messages, files, and completed milestones so the admin can see
* "what has this mentor been up to" at a glance.
*/
getMentorDetail: adminProcedure
.input(z.object({ mentorId: z.string() }))
.query(async ({ ctx, input }) => {
const mentor = await ctx.prisma.user.findUniqueOrThrow({
where: { id: input.mentorId },
select: {
id: true,
name: true,
email: true,
country: true,
expertiseTags: true,
maxAssignments: true,
createdAt: true,
},
})
const assignments = await ctx.prisma.mentorAssignment.findMany({
where: { mentorId: input.mentorId },
orderBy: { assignedAt: 'desc' },
include: {
project: {
select: {
id: true,
title: true,
competitionCategory: true,
country: true,
},
},
_count: {
select: { messages: true, files: true, milestoneCompletions: true },
},
messages: {
orderBy: { createdAt: 'desc' },
take: 1,
select: { createdAt: true },
},
files: {
orderBy: { createdAt: 'desc' },
take: 1,
select: { createdAt: true },
},
milestoneCompletions: {
orderBy: { completedAt: 'desc' },
take: 1,
select: { completedAt: true },
},
},
})
return {
mentor,
assignments: assignments.map((a) => ({
id: a.id,
assignedAt: a.assignedAt,
method: a.method,
completionStatus: a.completionStatus,
droppedAt: a.droppedAt,
droppedReason: a.droppedReason,
droppedBy: a.droppedBy,
workspaceEnabled: a.workspaceEnabled,
project: a.project,
messageCount: a._count.messages,
fileCount: a._count.files,
milestoneCompletionCount: a._count.milestoneCompletions,
lastMessageAt: a.messages[0]?.createdAt ?? null,
lastFileAt: a.files[0]?.createdAt ?? null,
lastMilestoneAt: a.milestoneCompletions[0]?.completedAt ?? null,
})),
}
}),
})