import { useEffect, useMemo, useState } from 'react' import { filterLearners, loadAdminLearnerData, progressSummary, type AdminLearnerData, type LearnerEnrollment, type LearnerProfile, type LearnerRole, type LearnerUserStatus, } from '../admin-learners' type LoadState = 'loading' | 'ready' | 'error' type StatusFilter = 'all' | LearnerUserStatus type RoleFilter = 'all' | LearnerRole const STATUS_FILTERS: Array<{ value: StatusFilter; label: string }> = [ { value: 'all', label: 'All statuses' }, { value: 'active', label: 'Active' }, { value: 'pending', label: 'Pending' }, { value: 'inactive', label: 'Inactive' }, { value: 'unknown', label: 'Unknown' }, ] const ROLE_FILTERS: Array<{ value: RoleFilter; label: string }> = [ { value: 'all', label: 'All roles' }, { value: 'learner', label: 'Learner' }, { value: 'instructor', label: 'Instructor' }, { value: 'editor', label: 'Editor' }, { value: 'admin', label: 'Admin' }, { value: 'unknown', label: 'Unknown' }, ] function roleClassName(role: LearnerRole): string { if (role === 'learner') return 'border-zinc-200 bg-zinc-100 text-zinc-700' if (role === 'instructor') return 'border-blue-200 bg-blue-50 text-blue-700' if (role === 'editor') return 'border-purple-200 bg-purple-50 text-purple-700' if (role === 'admin') return 'border-emerald-200 bg-emerald-50 text-emerald-700' return 'border-zinc-200 bg-zinc-100 text-zinc-500' } function statusClassName(status: LearnerUserStatus): string { if (status === 'active') return 'border-emerald-200 bg-emerald-50 text-emerald-700' if (status === 'pending') return 'border-amber-200 bg-amber-50 text-amber-700' if (status === 'inactive') return 'border-zinc-200 bg-zinc-100 text-zinc-600' return 'border-zinc-200 bg-zinc-100 text-zinc-500' } function enrollmentStatusClassName(status: LearnerEnrollment['status']): string { if (status === 'completed') return 'border-emerald-200 bg-emerald-50 text-emerald-700' if (status === 'in_progress') return 'border-blue-200 bg-blue-50 text-blue-700' if (status === 'failed') return 'border-rose-200 bg-rose-50 text-rose-700' if (status === 'not_started') return 'border-zinc-200 bg-zinc-100 text-zinc-600' return 'border-zinc-200 bg-zinc-100 text-zinc-500' } function formatDate(value: number | null): string { if (value === null) return 'Not available' return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short', }).format(value) } function totalTrackedProgress(enrollments: LearnerEnrollment[]): number | null { const tracked = enrollments.filter(enrollment => enrollment.progress !== null) if (tracked.length === 0) return null const sum = tracked.reduce((acc, enrollment) => acc + (enrollment.progress ?? 0), 0) return Math.round(sum / tracked.length) } export function AdminLearnersPage() { const [state, setState] = useState('loading') const [data, setData] = useState(null) const [error, setError] = useState('') const [query, setQuery] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [roleFilter, setRoleFilter] = useState('all') const [selectedLearnerID, setSelectedLearnerID] = useState(null) const load = async () => { try { setState('loading') setError('') const next = await loadAdminLearnerData() setData(next) setSelectedLearnerID(prev => prev ?? next.learners[0]?.id ?? null) setState('ready') } catch (err) { setData(null) setState('error') setError(err instanceof Error ? err.message : 'Failed to load learner management data') } } useEffect(() => { void load() }, []) const filteredLearners = useMemo(() => { return filterLearners(data?.learners ?? [], query, statusFilter, roleFilter) }, [data?.learners, query, roleFilter, statusFilter]) const selectedLearner = useMemo(() => { const selectedFromFiltered = filteredLearners.find(learner => learner.id === selectedLearnerID) if (selectedFromFiltered) return selectedFromFiltered return filteredLearners[0] ?? null }, [filteredLearners, selectedLearnerID]) if (state === 'loading') { return (

Learner Management

Loading learners and enrollment records...

) } if (state === 'error') { return (

Learner Management

{error}

) } return (

Learner Management

Review learner profiles, enrollment records, and role/status context. Detailed session-level activity tracking is shown as not yet tracked when unavailable.

{data?.loadWarnings.length ? (

Data warnings

    {data.loadWarnings.map(warning => (
  • {warning}
  • ))}
) : null}

Learners

{data?.learners.length ?? 0}

Enrollments

{(data?.learners ?? []).reduce((acc, learner) => acc + learner.enrollments.length, 0)}

Tracked progress

{(() => { const values = (data?.learners ?? []) .flatMap(learner => learner.enrollments.map(enrollment => enrollment.progress)) .filter((value): value is number => value !== null) if (values.length === 0) return 'Not yet tracked' const avg = Math.round(values.reduce((acc, value) => acc + value, 0) / values.length) return `${avg}%` })()}

{filteredLearners.map(learner => ( setSelectedLearnerID(learner.id)} className={`cursor-pointer border-t border-zinc-100 ${selectedLearner?.id === learner.id ? 'bg-zinc-50' : 'hover:bg-zinc-50/70'}`} data-testid={`admin-learners-row-${learner.id}`} > ))}
Learner Status Roles Enrollments Last active

{learner.displayName}

{learner.email || 'Email not provided'}

{learner.status}
{learner.roles.map(role => ( {role} ))}
{learner.enrollments.length} {formatDate(learner.lastActiveAt)}
{filteredLearners.length === 0 ? (
No learners matched this search/filter combination.
) : null}
) }