import type { PrismaClient } from '@generated/prisma/client'; import type { AttendanceStatus, AttendeeHistory, ProblemDetails, Result, } from '@gruperly/shared'; import { err, ok } from '@gruperly/shared'; import { notFoundResourceProblem } from '@/http/problem-builders'; import prisma from '@/lib/prisma'; import { type AnalyticsAccessDb, findGroupForUser } from '../../lib/access'; export const HISTORY_SESSIONS_LIMIT = 20; type GetAttendeeHistoryDeps = { db?: AnalyticsAccessDb & Pick; }; export class GetAttendeeHistory { constructor(private readonly deps: GetAttendeeHistoryDeps = {}) {} async execute( groupId: string, attendeeId: string, userId: string, ): Promise> { const db = this.deps.db ?? prisma; const access = await findGroupForUser(db, groupId, userId); if (!access.ok) { return access; } const attendee = await db.attendee.findFirst({ where: { id: attendeeId, groupId }, select: { id: true, fullName: true }, }); if (!attendee) { return err(notFoundResourceProblem('Attendee', attendeeId)); } const sessions = await db.classSession.findMany({ where: { groupId }, orderBy: { startsAt: 'desc' }, take: HISTORY_SESSIONS_LIMIT, select: { id: true, startsAt: true }, }); const sessionIds = sessions.map((session) => session.id); const attendances = sessionIds.length ? await db.attendance.findMany({ where: { attendeeId, classSessionId: { in: sessionIds } }, select: { classSessionId: true, status: true }, }) : []; const statusBySession = new Map( attendances.map((record) => [record.classSessionId, record.status]), ); const orderedSessions = sessions .slice() .reverse() .map((session) => ({ classSessionId: session.id, startsAt: session.startsAt.toISOString(), status: statusBySession.get(session.id) ?? null, })); const presentCount = attendances.filter((record) => record.status === 'PRESENT').length; const attendanceRate = sessions.length > 0 ? Math.round((presentCount / sessions.length) * 100) : 0; return ok({ attendeeId: attendee.id, fullName: attendee.fullName, groupId, groupName: access.value.name, attendanceRate, sessions: orderedSessions, }); } }