'use client' import { useMemo, useState } from 'react' import Link from 'next/link' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { ArrowUpDown, GraduationCap, Search, Users } from 'lucide-react' import { formatEnumLabel } from '@/lib/utils' type SortKey = 'name' | 'load' | 'capacity' | 'lastActivity' function formatRelativePast(date: Date | string | null): string { if (!date) return '—' const d = typeof date === 'string' ? new Date(date) : date const ms = Date.now() - d.getTime() const days = Math.floor(ms / 86_400_000) const hours = Math.floor(ms / 3_600_000) if (days >= 1) return `${days}d ago` if (hours >= 1) return `${hours}h ago` const minutes = Math.floor(ms / 60_000) return `${Math.max(0, minutes)}m ago` } const STATUS_BADGE: Record< 'unassigned' | 'assigned' | 'active' | 'stalled', { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' } > = { unassigned: { label: 'Unassigned', variant: 'outline' }, assigned: { label: 'Assigned', variant: 'secondary' }, active: { label: 'Active', variant: 'default' }, stalled: { label: 'Stalled', variant: 'destructive' }, } type Mentor = { id: string name: string | null email: string country: string | null expertiseTags: string[] currentAssignments: number completedAssignments: number maxAssignments: number | null capacityRemaining: number | null lastActivityAt: Date | string | null } function MentorListPanel() { const [search, setSearch] = useState('') const [sortKey, setSortKey] = useState('load') const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc') const { data, isLoading } = trpc.mentor.getMentorPool.useQuery({}) const filtered = useMemo(() => { if (!data) return [] const q = search.trim().toLowerCase() let rows: Mentor[] = data.mentors if (q) { rows = rows.filter((m) => [m.name ?? '', m.email, m.country ?? '', ...m.expertiseTags] .join(' ') .toLowerCase() .includes(q), ) } rows = [...rows].sort((a, b) => { let av: string | number = 0 let bv: string | number = 0 switch (sortKey) { case 'name': av = (a.name ?? '').toLowerCase() bv = (b.name ?? '').toLowerCase() break case 'load': av = a.currentAssignments bv = b.currentAssignments break case 'capacity': av = a.capacityRemaining ?? Number.MAX_SAFE_INTEGER bv = b.capacityRemaining ?? Number.MAX_SAFE_INTEGER break case 'lastActivity': av = a.lastActivityAt ? new Date(a.lastActivityAt).getTime() : 0 bv = b.lastActivityAt ? new Date(b.lastActivityAt).getTime() : 0 break } if (av < bv) return sortDir === 'asc' ? -1 : 1 if (av > bv) return sortDir === 'asc' ? 1 : -1 return 0 }) return rows }, [data, search, sortKey, sortDir]) const toggleSort = (key: SortKey) => { if (sortKey === key) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')) else { setSortKey(key) setSortDir(key === 'name' ? 'asc' : 'desc') } } const SortHeader = ({ k, children, align = 'left', }: { k: SortKey children: React.ReactNode align?: 'left' | 'right' }) => ( ) return (
Pool size
{data?.poolSize ?? '—'}
Active assignments
{data?.totalCurrentAssignments ?? '—'}
Average load
{data && data.poolSize > 0 ? (data.totalCurrentAssignments / data.poolSize).toFixed(1) : '—'}
Mentor list
setSearch(e.target.value)} placeholder="Search by name, email, country, or expertise tag…" className="pl-9" />
{isLoading ? (
{[1, 2, 3, 4, 5].map((i) => ( ))}
) : filtered.length === 0 ? (
{search ? 'No matching mentors.' : 'No mentors yet.'}
) : (
Mentor Expertise Country Active Completed Capacity Last activity {filtered.map((m) => (
{m.name ?? 'Unnamed'}
{m.email}
{m.expertiseTags.slice(0, 4).map((tag) => ( {tag} ))} {m.expertiseTags.length > 4 && ( +{m.expertiseTags.length - 4} )}
{m.country ?? '—'} {m.currentAssignments} {m.completedAssignments} {m.capacityRemaining != null ? m.capacityRemaining : '∞'} {formatRelativePast(m.lastActivityAt)}
))}
)}
) } type StatusFilter = 'all' | 'unassigned' | 'assigned' | 'active' | 'stalled' function MenteeActivityPanel() { const [search, setSearch] = useState('') const [statusFilter, setStatusFilter] = useState('all') const { data, isLoading } = trpc.mentor.getMenteeActivity.useQuery({}) const filtered = useMemo(() => { if (!data) return [] const q = search.trim().toLowerCase() return data.rows.filter((r) => { if (statusFilter !== 'all' && r.status !== statusFilter) return false if (!q) return true const hay = [ r.project.title, r.project.country ?? '', r.teamLead?.name ?? '', r.teamLead?.email ?? '', r.mentor?.name ?? '', r.mentor?.email ?? '', ] .join(' ') .toLowerCase() return hay.includes(q) }) }, [data, search, statusFilter]) const totals = data?.totals ?? { unassigned: 0, assigned: 0, active: 0, stalled: 0 } const StatusPill = ({ value, label, count }: { value: StatusFilter; label: string; count: number }) => ( ) return (
Unassigned
{totals.unassigned}
Assigned
{totals.assigned}
Active
{totals.active}
Stalled
{totals.stalled}
Mentee teams
setSearch(e.target.value)} placeholder="Search by project, team lead, or mentor…" className="pl-9" />
{isLoading ? (
{[1, 2, 3, 4, 5].map((i) => ( ))}
) : filtered.length === 0 ? (
{search || statusFilter !== 'all' ? 'No matching teams.' : 'No teams have requested mentorship yet.'}
) : (
Project Status Mentor Messages Files Last activity {filtered.map((r) => { const badge = STATUS_BADGE[r.status] return (
{r.project.title}
{r.teamLead?.name ?? r.teamLead?.email ?? '—'} {r.project.oceanIssue && ( <> {' · '} {formatEnumLabel(r.project.oceanIssue)} )}
{badge.label} {r.mentor ? (
{r.mentor.name ?? r.mentor.email}
{r.mentor.currentLoad} {r.mentor.maxAssignments != null ? `/${r.mentor.maxAssignments}` : ''} {' load'}
) : ( )}
{r.messageCount} {r.fileCount} {formatRelativePast(r.lastActivityAt as unknown as Date | null)}
) })}
)}
) } export default function MentorsListPage() { return (

Mentors

Manage the mentor pool and track mentee teams across the program.

Mentors Mentees & Activity
) }