# Exercise Responses API - Frontend Integration Guide **Module:** Teacher - Exercise Responses **Date:** 2025-11-24 **Status:** Ready for Frontend Integration --- ## 🎯 Quick Reference ### Base Endpoint ``` /api/teacher/attempts ``` ### Authentication All endpoints require: - JWT Bearer token - Role: ADMIN_TEACHER or SUPER_ADMIN --- ## 📋 Available Endpoints ### 1. Get Paginated Attempts List **Endpoint:** `GET /api/teacher/attempts` **Query Parameters:** ```typescript { page?: number; // Default: 1 limit?: number; // Default: 20, Max: 100 student_id?: string; // UUID exercise_id?: string; // UUID module_id?: string; // UUID classroom_id?: string; // UUID from_date?: string; // ISO 8601 (e.g., "2024-01-01T00:00:00Z") to_date?: string; // ISO 8601 is_correct?: boolean; // true or false sort_by?: 'submitted_at' | 'score' | 'time'; // Default: 'submitted_at' sort_order?: 'asc' | 'desc'; // Default: 'desc' } ``` **Response:** ```typescript { data: AttemptResponseDto[]; total: number; page: number; limit: number; total_pages: number; } ``` **Example Request:** ```typescript const response = await fetch('/api/teacher/attempts?page=1&limit=20&is_correct=true', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); ``` --- ### 2. Get Attempt Detail **Endpoint:** `GET /api/teacher/attempts/:id` **Parameters:** - `id` (path parameter) - Attempt UUID **Response:** ```typescript { id: string; student_id: string; student_name: string; exercise_id: string; exercise_title: string; module_name: string; attempt_number: number; submitted_answers: object; is_correct: boolean; score: number; time_spent_seconds: number; hints_used: number; comodines_used: string[]; xp_earned: number; ml_coins_earned: number; submitted_at: string; // Additional fields for detail: correct_answer: object; exercise_type: string; max_score: number; } ``` **Example Request:** ```typescript const response = await fetch(`/api/teacher/attempts/${attemptId}`, { headers: { 'Authorization': `Bearer ${token}` } }); ``` --- ### 3. Get Student Attempts **Endpoint:** `GET /api/teacher/attempts/student/:studentId` **Parameters:** - `studentId` (path parameter) - Student profile UUID **Response:** ```typescript AttemptResponseDto[] // Array of attempts ``` **Example Request:** ```typescript const response = await fetch(`/api/teacher/attempts/student/${studentId}`, { headers: { 'Authorization': `Bearer ${token}` } }); ``` --- ### 4. Get Exercise Responses **Endpoint:** `GET /api/teacher/exercises/:exerciseId/responses` **Parameters:** - `exerciseId` (path parameter) - Exercise UUID **Response:** ```typescript { data: AttemptResponseDto[]; total: number; page: number; limit: number; total_pages: number; } ``` **Example Request:** ```typescript const response = await fetch(`/api/teacher/exercises/${exerciseId}/responses`, { headers: { 'Authorization': `Bearer ${token}` } }); ``` --- ## 🎨 TypeScript Interfaces ```typescript // apps/frontend/src/apps/teacher/types/exercise-responses.types.ts export enum AttemptSortField { SUBMITTED_AT = 'submitted_at', SCORE = 'score', TIME = 'time', } export enum SortOrder { ASC = 'asc', DESC = 'desc', } export interface GetAttemptsQuery { page?: number; limit?: number; student_id?: string; exercise_id?: string; module_id?: string; classroom_id?: string; from_date?: string; to_date?: string; is_correct?: boolean; sort_by?: AttemptSortField; sort_order?: SortOrder; } export interface AttemptResponse { id: string; student_id: string; student_name: string; exercise_id: string; exercise_title: string; module_name: string; attempt_number: number; submitted_answers: Record; is_correct: boolean; score: number; time_spent_seconds: number; hints_used: number; comodines_used: string[]; xp_earned: number; ml_coins_earned: number; submitted_at: string; } export interface AttemptDetail extends AttemptResponse { correct_answer: Record; exercise_type: string; max_score: number; } export interface AttemptsListResponse { data: AttemptResponse[]; total: number; page: number; limit: number; total_pages: number; } ``` --- ## 🔧 Suggested React Hooks ### useExerciseResponses Hook ```typescript // apps/frontend/src/apps/teacher/hooks/useExerciseResponses.ts import { useState, useEffect } from 'react'; import { exerciseResponsesApi } from '@/services/api/teacher/exerciseResponsesApi'; import type { GetAttemptsQuery, AttemptsListResponse } from '../types/exercise-responses.types'; export const useExerciseResponses = (filters: GetAttemptsQuery) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchAttempts = async () => { try { setLoading(true); const result = await exerciseResponsesApi.getAttempts(filters); setData(result); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; fetchAttempts(); }, [JSON.stringify(filters)]); return { data, loading, error }; }; ``` ### useStudentAttempts Hook ```typescript export const useStudentAttempts = (studentId: string) => { const [attempts, setAttempts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchAttempts = async () => { try { setLoading(true); const result = await exerciseResponsesApi.getStudentAttempts(studentId); setAttempts(result); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; if (studentId) { fetchAttempts(); } }, [studentId]); return { attempts, loading, error }; }; ``` ### useAttemptDetail Hook ```typescript export const useAttemptDetail = (attemptId: string) => { const [attempt, setAttempt] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchAttempt = async () => { try { setLoading(true); const result = await exerciseResponsesApi.getAttemptDetail(attemptId); setAttempt(result); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; if (attemptId) { fetchAttempt(); } }, [attemptId]); return { attempt, loading, error }; }; ``` --- ## 🌐 API Client Implementation ```typescript // apps/frontend/src/services/api/teacher/exerciseResponsesApi.ts import { apiClient } from '../apiClient'; import type { GetAttemptsQuery, AttemptsListResponse, AttemptResponse, AttemptDetail, } from '@/apps/teacher/types/exercise-responses.types'; export const exerciseResponsesApi = { /** * Get paginated list of attempts with filters */ async getAttempts(query: GetAttemptsQuery): Promise { const params = new URLSearchParams(); Object.entries(query).forEach(([key, value]) => { if (value !== undefined) { params.append(key, String(value)); } }); const response = await apiClient.get(`/teacher/attempts?${params.toString()}`); return response.data; }, /** * Get attempt detail by ID */ async getAttemptDetail(attemptId: string): Promise { const response = await apiClient.get(`/teacher/attempts/${attemptId}`); return response.data; }, /** * Get all attempts by student */ async getStudentAttempts(studentId: string): Promise { const response = await apiClient.get(`/teacher/attempts/student/${studentId}`); return response.data; }, /** * Get all responses for an exercise */ async getExerciseResponses(exerciseId: string): Promise { const response = await apiClient.get(`/teacher/exercises/${exerciseId}/responses`); return response.data; }, }; ``` --- ## 🎨 Example React Components ### AttemptsList Component ```typescript // apps/frontend/src/apps/teacher/components/attempts/AttemptsList.tsx import React from 'react'; import { useExerciseResponses } from '@/apps/teacher/hooks/useExerciseResponses'; import { AttemptCard } from './AttemptCard'; interface AttemptsListProps { classroomId?: string; studentId?: string; } export const AttemptsList: React.FC = ({ classroomId, studentId, }) => { const [page, setPage] = React.useState(1); const { data, loading, error } = useExerciseResponses({ page, limit: 20, classroom_id: classroomId, student_id: studentId, sort_by: 'submitted_at', sort_order: 'desc', }); if (loading) return
Loading...
; if (error) return
Error: {error.message}
; if (!data) return null; return (

Student Attempts ({data.total})

{data.data.map((attempt) => ( ))}
); }; ``` ### AttemptCard Component ```typescript // apps/frontend/src/apps/teacher/components/attempts/AttemptCard.tsx import React from 'react'; import type { AttemptResponse } from '@/apps/teacher/types/exercise-responses.types'; interface AttemptCardProps { attempt: AttemptResponse; } export const AttemptCard: React.FC = ({ attempt }) => { return (

{attempt.student_name}

Attempt #{attempt.attempt_number}

{attempt.exercise_title}

{attempt.module_name}

Score: {attempt.score}
Time: {formatTime(attempt.time_spent_seconds)}
+{attempt.xp_earned} XP +{attempt.ml_coins_earned} ML
{attempt.comodines_used.length > 0 && (
Power-ups: {attempt.comodines_used.map((comodin, idx) => ( {comodin} ))}
)}
{new Date(attempt.submitted_at).toLocaleString()}
); }; const formatTime = (seconds: number): string => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; ``` ### AttemptDetailModal Component ```typescript // apps/frontend/src/apps/teacher/components/attempts/AttemptDetailModal.tsx import React from 'react'; import { useAttemptDetail } from '@/apps/teacher/hooks/useExerciseResponses'; interface AttemptDetailModalProps { attemptId: string; onClose: () => void; } export const AttemptDetailModal: React.FC = ({ attemptId, onClose, }) => { const { attempt, loading, error } = useAttemptDetail(attemptId); if (loading) return
Loading...
; if (error) return
Error: {error.message}
; if (!attempt) return null; return (
e.stopPropagation()}>

Attempt Detail

{attempt.student_name}

{attempt.exercise_title}

{attempt.is_correct ? '✓ Correct' : '✗ Incorrect'}
Score: {attempt.score}/{attempt.max_score}
Time: {formatTime(attempt.time_spent_seconds)}
Hints used: {attempt.hints_used}

Student's Answers

{JSON.stringify(attempt.submitted_answers, null, 2)}

Correct Answers

{JSON.stringify(attempt.correct_answer, null, 2)}
); }; ``` --- ## 📊 Example Use Cases ### 1. Teacher Dashboard - Recent Attempts ```typescript // Show last 10 attempts across all classrooms const { data } = useExerciseResponses({ page: 1, limit: 10, sort_by: 'submitted_at', sort_order: 'desc', }); ``` ### 2. Student Progress Page - Individual Student ```typescript // Show all attempts for a specific student const { attempts } = useStudentAttempts(studentId); ``` ### 3. Exercise Analytics - Performance Overview ```typescript // Analyze how all students performed on an exercise const { data } = useExerciseResponses({ exercise_id: exerciseId, limit: 100, }); // Calculate stats const totalAttempts = data.total; const correctAttempts = data.data.filter(a => a.is_correct).length; const avgScore = data.data.reduce((sum, a) => sum + a.score, 0) / data.data.length; ``` ### 4. Classroom Monitoring - Today's Activity ```typescript // Show attempts from today in a specific classroom const today = new Date(); today.setHours(0, 0, 0, 0); const { data } = useExerciseResponses({ classroom_id: classroomId, from_date: today.toISOString(), sort_by: 'submitted_at', sort_order: 'desc', }); ``` ### 5. Intervention Alerts - Students Struggling ```typescript // Find incorrect attempts to identify students needing help const { data } = useExerciseResponses({ classroom_id: classroomId, is_correct: false, sort_by: 'submitted_at', sort_order: 'desc', }); ``` --- ## 🎯 Integration Checklist - [ ] Create types file: `apps/frontend/src/apps/teacher/types/exercise-responses.types.ts` - [ ] Create API client: `apps/frontend/src/services/api/teacher/exerciseResponsesApi.ts` - [ ] Create hooks: `apps/frontend/src/apps/teacher/hooks/useExerciseResponses.ts` - [ ] Create components: - [ ] `AttemptsList.tsx` - [ ] `AttemptCard.tsx` - [ ] `AttemptDetailModal.tsx` - [ ] `AttemptFilters.tsx` - [ ] Integrate into pages: - [ ] Teacher Dashboard - [ ] Student Progress Page - [ ] Analytics Page - [ ] Add to teacher routes - [ ] Test all endpoints - [ ] Handle error states - [ ] Add loading states - [ ] Implement pagination --- ## 🔒 Security Notes 1. **JWT Token Required:** - All requests must include valid JWT token - Token must have ADMIN_TEACHER or SUPER_ADMIN role 2. **RLS Enforced:** - Teachers can only see attempts from students in their classrooms - Cross-tenant data is blocked - Attempting to access unauthorized data returns 403 Forbidden 3. **Input Validation:** - All IDs are validated as UUIDs - Dates must be in ISO 8601 format - Pagination limits are enforced (max 100) --- ## 📚 Related Documentation - Backend Implementation: `apps/backend/EXERCISE-RESPONSES-IMPLEMENTATION-REPORT.md` - API Testing: `apps/backend/scripts/test-exercise-responses.sh` - Entity Reference: `apps/backend/src/modules/progress/entities/exercise-attempt.entity.ts` --- **Ready for Frontend Integration!** 🚀 Contact Backend-Agent for any questions or issues. --- **Backend-Agent** | 2025-11-24