import { apiFetchJson } from "../../../lib/api"; import type { ApiAssignment, ApiClassroom, ApiListResponse, ApiReviewQueueItem, ApiReviewSummary, ApiStudent } from "../../../lib/api-types"; import { getAssignmentReviewHref, getDashboardTeacherClassroomHref, } from "../../../lib/routes"; import { teacherDashboardLabels } from "../../../content/dashboard-labels"; import { isIndividualRedoAssignment } from "./dashboard-teacher-assignments.data"; import { buildTeacherShell, formatRelativeTime, formatShortDate, initialsFor, queueStatusLabel, queueStatusTone, studentNote, } from "./dashboard-teacher-classroom-detail.helpers"; import type { TeacherClassroomDetailData, TeacherClassroomRedoAssignmentItem } from "./dashboard-teacher-classroom-detail.types"; export type { TeacherClassroomDetailData, TeacherClassroomDetailStudentItem, TeacherClassroomRedoAssignmentItem } from "./dashboard-teacher-classroom-detail.types"; export const getTeacherClassroomDetailData = async ( teacherId: number, classroomId: number, selectedStudentId?: number | null, ): Promise => { const [classroomsResponse, assignmentsResponse] = await Promise.all([ apiFetchJson>(`/api/teachers/${teacherId}/classrooms`), apiFetchJson>(`/api/teachers/${teacherId}/assignments`), ]); const classrooms = classroomsResponse.data; const classroom = classrooms.find((entry) => entry.id === classroomId); if (!classroom) return null; const classroomAssignments = assignmentsResponse.data.filter((assignment) => assignment.classroom_id === classroomId); const redoAssignments = classroomAssignments.filter(isIndividualRedoAssignment); const [studentsResponse, reviewSummaryEntries, reviewQueueEntries] = await Promise.all([ apiFetchJson>(`/api/classrooms/${classroomId}/students`), Promise.all(classroomAssignments.map(async (assignment) => [assignment.id, await apiFetchJson(`/api/assignments/${assignment.id}/review-summary`)] as const)), Promise.all(redoAssignments.map(async (assignment) => [assignment.id, (await apiFetchJson>(`/api/assignments/${assignment.id}/review`)).data] as const)), ]); const students = studentsResponse.data.slice().sort((left, right) => left.full_name.localeCompare(right.full_name)); const reviewSummaryByAssignment = new Map(reviewSummaryEntries); const reviewQueueByAssignment = new Map(reviewQueueEntries); const selectedStudent = (selectedStudentId != null ? students.find((student) => student.id === selectedStudentId) : null) ?? students[0] ?? null; const studentItems = students.map((student) => { const studentRedoRows = redoAssignments .map((assignment) => ({ assignment, row: (reviewQueueByAssignment.get(assignment.id) ?? []).find((item) => item.student_id === student.id) ?? null })) .filter((entry) => entry.row != null); const liveRedoCount = studentRedoRows.filter((entry) => entry.assignment.status !== "closed").length; const closedRedoCount = studentRedoRows.filter((entry) => entry.assignment.status === "closed").length; const submittedCount = studentRedoRows.filter((entry) => (entry.row?.submitted_questions ?? 0) > 0).length; return { id: student.id, name: student.full_name, email: student.email, initials: initialsFor(student.full_name), statusLabel: submittedCount > 0 ? "Needs review" : liveRedoCount > 0 ? "Redo active" : closedRedoCount > 0 ? "Redo history" : "No redo", redoCountLabel: `${studentRedoRows.length} redo assignment${studentRedoRows.length === 1 ? "" : "s"}`, note: studentNote(submittedCount, liveRedoCount, closedRedoCount), href: getDashboardTeacherClassroomHref(classroomId, student.id), selected: student.id === selectedStudent?.id, }; }); const selectedStudentRedoAssignments = selectedStudent ? redoAssignments .map((assignment) => { const queueRow = (reviewQueueByAssignment.get(assignment.id) ?? []).find((item) => item.student_id === selectedStudent.id); if (!queueRow) return null; const latestActivity = queueRow.latest_submitted_at ?? queueRow.latest_reviewed_at; const summary = reviewSummaryByAssignment.get(assignment.id); return { id: assignment.id, title: assignment.title, statusLabel: assignment.status === "closed" ? "Closed" : queueStatusLabel(queueRow), statusTone: assignment.status === "closed" ? "success" : queueStatusTone(queueRow), dueLabel: `Due ${formatShortDate(assignment.due_at)}`, progressLabel: `${queueRow.reviewed_questions}/${queueRow.total_questions} reviewed`, nextStepLabel: queueRow.next_step_outcome ? `Next step: ${queueRow.next_step_outcome.replace(/_/g, " ")}` : "Next step still pending", note: (queueRow.submitted_questions ?? 0) > 0 ? `${queueRow.submitted_questions} question${queueRow.submitted_questions === 1 ? " is" : "s are"} waiting for review.` : summary && summary.reviewed > 0 ? `Latest activity ${formatRelativeTime(latestActivity)}.` : assignment.status === "closed" ? "Closed redo assignment kept here for this student’s history." : "Assigned redo work with no submission yet.", href: getAssignmentReviewHref("teacher", assignment.id), }; }) .filter((item): item is TeacherClassroomRedoAssignmentItem => item != null) .sort((left, right) => { const leftClosed = left.statusLabel === "Closed" ? 1 : 0; const rightClosed = right.statusLabel === "Closed" ? 1 : 0; return leftClosed - rightClosed || left.title.localeCompare(right.title); }) : []; const liveRedoCount = redoAssignments.filter((assignment) => assignment.status !== "closed").length; const classroomNeedsReviewCount = redoAssignments.reduce((total, assignment) => { const rows = reviewQueueByAssignment.get(assignment.id) ?? []; return total + rows.filter((row) => row.submitted_questions > 0).length; }, 0); return { shell: buildTeacherShell(classroom.name, classroom.code, classrooms.length, classroomNeedsReviewCount), classroom: { id: classroom.id, name: classroom.name, description: classroom.description?.trim() || "Track each student’s follow-up work and individual redo assignments from here.", codeLabel: classroom.code ? `Invite code ${classroom.code}` : "Invite code not set", stats: [ { label: teacherDashboardLabels.classroomDetail.students, value: `${students.length}`, note: students.length === 0 ? "No students in this classroom yet" : "Roster ready for drilldown" }, { label: teacherDashboardLabels.classroomDetail.redoAssignments, value: `${redoAssignments.length}`, note: `${liveRedoCount} live · ${redoAssignments.length - liveRedoCount} closed` }, { label: teacherDashboardLabels.classroomDetail.waitingReview, value: `${classroomNeedsReviewCount}`, note: classroomNeedsReviewCount > 0 ? "Student-specific redo work needs attention" : "Nothing urgent in redo follow-up" }, ], }, students: { title: teacherDashboardLabels.classroomDetail.students, description: "Pick a student to inspect their individual redo assignments in this classroom.", items: studentItems, }, selectedStudent: { id: selectedStudent?.id ?? null, name: selectedStudent?.full_name ?? null, email: selectedStudent?.email ?? null, note: selectedStudent ? selectedStudentRedoAssignments.length > 0 ? `${selectedStudentRedoAssignments.length} redo assignment${selectedStudentRedoAssignments.length === 1 ? "" : "s"} tied to this student in ${classroom.name}.` : "This student does not have any individual redo assignments in this classroom yet." : "Select a student to view their individual redo assignments.", }, redoAssignments: { title: selectedStudent ? `${selectedStudent.full_name} redo assignments` : "Student redo assignments", description: selectedStudent ? "These are the individual redo assignments created specifically for the selected student." : "Select a student from the roster to inspect their individual redo assignments.", items: selectedStudentRedoAssignments, }, }; };