UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 13.3 kB
{"version":3,"sources":["../src/hooks/useExamDetails.ts","../src/types/examDetails.ts"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport dayjs from 'dayjs';\nimport type { BaseApiClient } from '../types/api';\nimport {\n StudentAnswerStatus,\n type ExamDetailsData,\n type ExamStudentResult,\n type ExamStats,\n type ExamDetailsPagination,\n type ExamDetailsFilters,\n} from '../types/examDetails';\nimport { createFetchErrorHandler } from '../utils/hookErrorHandler';\n\n/**\n * Student activity status from backend\n */\nenum StudentActivityStatus {\n AGUARDANDO_RESPOSTA = 'AGUARDANDO_RESPOSTA',\n NAO_ENTREGUE = 'NAO_ENTREGUE',\n CONCLUIDO = 'CONCLUIDO',\n AGUARDANDO_CORRECAO = 'AGUARDANDO_CORRECAO',\n}\n\n/**\n * API student type\n */\ninterface ApiExamStudent {\n studentId: string;\n studentName: string;\n answeredAt: string | null;\n timeSpent: number;\n score: number | null;\n status: StudentActivityStatus;\n}\n\n/**\n * API breakdown item type\n */\ninterface ApiBreakdownItem {\n school: { id: string; name: string } | null;\n schoolYear: { id: string; name: string } | null;\n class: { id: string; name: string } | null;\n totalStudents: number;\n answeredStudents: number;\n completionPercentage: number;\n}\n\n/**\n * Exam details API response type\n */\nexport interface ExamDetailsApiResponse {\n message: string;\n data: {\n students: ApiExamStudent[];\n pagination: {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n };\n generalStats: {\n averageScore: number;\n completionPercentage: number;\n };\n questionStats: {\n mostCorrect: number[];\n mostIncorrect: number[];\n notAnswered: number[];\n };\n breakdown: ApiBreakdownItem[];\n };\n}\n\n/**\n * Exam info API response type\n */\nexport interface ExamInfoApiResponse {\n message: string;\n data: {\n id: string;\n title: string;\n startDate: string | null;\n createdAt: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Hook state interface\n */\nexport interface UseExamDetailsState {\n examData: ExamDetailsData | null;\n loading: boolean;\n error: string | null;\n pagination: ExamDetailsPagination;\n}\n\n/**\n * Hook return type\n */\nexport interface UseExamDetailsReturn extends UseExamDetailsState {\n fetchExamDetails: (\n examId: string,\n filters?: ExamDetailsFilters\n ) => Promise<void>;\n}\n\n/**\n * Default pagination values\n */\nexport const DEFAULT_EXAM_DETAILS_PAGINATION: ExamDetailsPagination = {\n total: 0,\n page: 1,\n limit: 10,\n totalPages: 0,\n};\n\n/**\n * Map backend status to frontend status\n */\nexport const mapBackendStatusToFrontend = (\n status: StudentActivityStatus\n): StudentAnswerStatus => {\n // If student completed or is awaiting correction, answer sheet was received\n // If awaiting response or not delivered, still waiting for answer sheet\n if (\n status === StudentActivityStatus.CONCLUIDO ||\n status === StudentActivityStatus.AGUARDANDO_CORRECAO\n ) {\n return StudentAnswerStatus.ANSWER_SHEET_RECEIVED;\n }\n return StudentAnswerStatus.AWAITING_ANSWER_SHEET;\n};\n\n/**\n * Transform API student to frontend format\n */\nexport const transformStudent = (\n student: ApiExamStudent\n): ExamStudentResult => ({\n id: student.studentId,\n studentId: student.studentId,\n studentName: student.studentName,\n status: mapBackendStatusToFrontend(student.status),\n answerReceivedAt: student.answeredAt\n ? dayjs(student.answeredAt).format('DD MMM YYYY')\n : null,\n score: student.score,\n});\n\n/**\n * Handle errors during exam fetch\n */\nexport const handleExamDetailsFetchError = createFetchErrorHandler(\n 'Erro ao validar dados de detalhes da prova',\n 'Erro ao carregar detalhes da prova'\n);\n\n/**\n * Build query params from filters\n */\nconst buildQueryParams = (\n filters?: ExamDetailsFilters\n): Record<string, unknown> => {\n if (!filters) return {};\n\n const params: Record<string, unknown> = {};\n for (const key in filters) {\n const value = filters[key as keyof ExamDetailsFilters];\n if (value !== undefined && value !== null) {\n params[key] = value;\n }\n }\n return params;\n};\n\n/**\n * Hook implementation\n */\nconst useExamDetailsImpl = (apiClient: BaseApiClient): UseExamDetailsReturn => {\n const [state, setState] = useState<UseExamDetailsState>({\n examData: null,\n loading: false,\n error: null,\n pagination: DEFAULT_EXAM_DETAILS_PAGINATION,\n });\n\n /**\n * Fetch exam details from API\n */\n const fetchExamDetails = useCallback(\n async (examId: string, filters?: ExamDetailsFilters) => {\n setState((prev) => ({ ...prev, loading: true, error: null }));\n\n try {\n const params = buildQueryParams(filters);\n\n // Fetch exam info and details in parallel using activities endpoints\n // Use /quiz endpoint instead of /:id to support all user profiles (teachers, managers)\n const [examInfoResponse, examDetailsResponse] = await Promise.all([\n apiClient.get<ExamInfoApiResponse>(`/activities/${examId}/quiz`),\n apiClient.get<ExamDetailsApiResponse>(\n `/activities/${examId}/details`,\n {\n params,\n }\n ),\n ]);\n\n const examInfo = examInfoResponse.data;\n const examDetails = examDetailsResponse.data;\n\n // Transform students\n const students = examDetails.data.students.map(transformStudent);\n\n // Build exam stats\n const stats: ExamStats = {\n averageScore: examDetails.data.generalStats.averageScore,\n mostCorrectQuestions: examDetails.data.questionStats.mostCorrect,\n mostIncorrectQuestions: examDetails.data.questionStats.mostIncorrect,\n unansweredQuestions: examDetails.data.questionStats.notAnswered,\n };\n\n // Extract school and class from breakdown\n const breakdown = examDetails.data.breakdown;\n\n // Get unique school names from breakdown\n const schoolNames = [\n ...new Set(\n breakdown\n .map((b) => b.school?.name)\n .filter((name): name is string => !!name)\n ),\n ];\n\n // Get unique class names from breakdown\n const classNames = [\n ...new Set(\n breakdown\n .map((b) => b.class?.name)\n .filter((name): name is string => !!name)\n ),\n ];\n\n // Build exam data\n const examData: ExamDetailsData = {\n id: examInfo.data.id,\n title: examInfo.data.title,\n startDate: examInfo.data.startDate\n ? dayjs(examInfo.data.startDate).format('DD/MM/YYYY')\n : '-',\n school: schoolNames.length > 0 ? schoolNames.join(', ') : '-',\n className: classNames.length > 0 ? classNames.join(', ') : '-',\n createdAt: dayjs(examInfo.data.createdAt).format('DD/MM/YYYY'),\n stats,\n students,\n };\n\n setState({\n examData,\n loading: false,\n error: null,\n pagination: examDetails.data.pagination,\n });\n } catch (error) {\n const errorMessage = handleExamDetailsFetchError(error);\n setState((prev) => ({\n ...prev,\n loading: false,\n error: errorMessage,\n }));\n }\n },\n [apiClient]\n );\n\n return {\n ...state,\n fetchExamDetails,\n };\n};\n\n/**\n * Factory function to create useExamDetails hook\n *\n * @param apiClient - API client instance (axios, fetch wrapper, etc.)\n * @returns Hook for managing exam details\n *\n * @example\n * ```tsx\n * // In your app setup\n * import { createUseExamDetails } from 'analytica-frontend-lib';\n * import api from '@/services/apiService';\n *\n * export const useExamDetails = createUseExamDetails(api);\n *\n * // In your component\n * const { examData, loading, error, pagination, fetchExamDetails } = useExamDetails();\n *\n * useEffect(() => {\n * if (examId) {\n * fetchExamDetails(examId, { page: 1, limit: 10 });\n * }\n * }, [examId]);\n * ```\n */\nexport const createUseExamDetails = (apiClient: BaseApiClient) => {\n return (): UseExamDetailsReturn => useExamDetailsImpl(apiClient);\n};\n\n/**\n * Alias for createUseExamDetails\n */\nexport const createExamDetailsHook = createUseExamDetails;\n","/**\n * Exam Details Types\n *\n * This file contains types for the exam details page,\n * including student results, statistics, and status enums.\n */\n\n/**\n * Student answer status enum\n * Represents the state of a student's answer sheet submission\n */\nexport enum StudentAnswerStatus {\n AWAITING_ANSWER_SHEET = 'AWAITING_ANSWER_SHEET',\n ANSWER_SHEET_RECEIVED = 'ANSWER_SHEET_RECEIVED',\n}\n\n/**\n * Student answer display status for UI\n */\nexport enum StudentAnswerDisplayStatus {\n AWAITING_ANSWER_SHEET = 'Aguardando gabarito',\n ANSWER_SHEET_RECEIVED = 'Gabarito recebido',\n}\n\n/**\n * Student result in exam table\n */\nexport interface ExamStudentResult {\n id: string;\n studentId: string;\n studentName: string;\n status: StudentAnswerStatus;\n answerReceivedAt: string | null;\n score: number | null;\n}\n\n/**\n * Student table item for TableProvider\n * Extends Record<string, unknown> for compatibility with generic table\n */\nexport interface ExamStudentTableItem extends Record<string, unknown> {\n id: string;\n studentId: string;\n studentName: string;\n status: StudentAnswerStatus;\n answerReceivedAt: string | null;\n score: number | null;\n}\n\n/**\n * Exam statistics data\n */\nexport interface ExamStats {\n averageScore: number;\n mostCorrectQuestions: number[];\n mostIncorrectQuestions: number[];\n unansweredQuestions: number[];\n}\n\n/**\n * Exam details data (used for page display)\n */\nexport interface ExamDetailsData {\n id: string;\n title: string;\n startDate: string;\n school: string;\n className: string;\n createdAt: string;\n stats: ExamStats;\n students: ExamStudentResult[];\n}\n\n/**\n * Pagination data for exam details\n */\nexport interface ExamDetailsPagination {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n}\n\n/**\n * Query filters for exam details\n */\nexport interface ExamDetailsFilters {\n page?: number;\n limit?: number;\n sortBy?: 'name' | 'score' | 'answeredAt';\n sortOrder?: 'asc' | 'desc';\n status?: string;\n}\n"],"mappings":";;;;;AAAA,SAAS,UAAU,mBAAmB;AACtC,OAAO,WAAW;;;ACUX,IAAK,sBAAL,kBAAKA,yBAAL;AACL,EAAAA,qBAAA,2BAAwB;AACxB,EAAAA,qBAAA,2BAAwB;AAFd,SAAAA;AAAA,GAAA;AAQL,IAAK,6BAAL,kBAAKC,gCAAL;AACL,EAAAA,4BAAA,2BAAwB;AACxB,EAAAA,4BAAA,2BAAwB;AAFd,SAAAA;AAAA,GAAA;;;AD2FL,IAAM,kCAAyD;AAAA,EACpE,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,YAAY;AACd;AAKO,IAAM,6BAA6B,CACxC,WACwB;AAGxB,MACE,WAAW,+BACX,WAAW,iDACX;AACA;AAAA,EACF;AACA;AACF;AAKO,IAAM,mBAAmB,CAC9B,aACuB;AAAA,EACvB,IAAI,QAAQ;AAAA,EACZ,WAAW,QAAQ;AAAA,EACnB,aAAa,QAAQ;AAAA,EACrB,QAAQ,2BAA2B,QAAQ,MAAM;AAAA,EACjD,kBAAkB,QAAQ,aACtB,MAAM,QAAQ,UAAU,EAAE,OAAO,aAAa,IAC9C;AAAA,EACJ,OAAO,QAAQ;AACjB;AAKO,IAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AACF;AAKA,IAAM,mBAAmB,CACvB,YAC4B;AAC5B,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,SAAkC,CAAC;AACzC,aAAW,OAAO,SAAS;AACzB,UAAM,QAAQ,QAAQ,GAA+B;AACrD,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAKA,IAAM,qBAAqB,CAAC,cAAmD;AAC7E,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA8B;AAAA,IACtD,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,YAAY;AAAA,EACd,CAAC;AAKD,QAAM,mBAAmB;AAAA,IACvB,OAAO,QAAgB,YAAiC;AACtD,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,SAAS,MAAM,OAAO,KAAK,EAAE;AAE5D,UAAI;AACF,cAAM,SAAS,iBAAiB,OAAO;AAIvC,cAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChE,UAAU,IAAyB,eAAe,MAAM,OAAO;AAAA,UAC/D,UAAU;AAAA,YACR,eAAe,MAAM;AAAA,YACrB;AAAA,cACE;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAED,cAAM,WAAW,iBAAiB;AAClC,cAAM,cAAc,oBAAoB;AAGxC,cAAM,WAAW,YAAY,KAAK,SAAS,IAAI,gBAAgB;AAG/D,cAAM,QAAmB;AAAA,UACvB,cAAc,YAAY,KAAK,aAAa;AAAA,UAC5C,sBAAsB,YAAY,KAAK,cAAc;AAAA,UACrD,wBAAwB,YAAY,KAAK,cAAc;AAAA,UACvD,qBAAqB,YAAY,KAAK,cAAc;AAAA,QACtD;AAGA,cAAM,YAAY,YAAY,KAAK;AAGnC,cAAM,cAAc;AAAA,UAClB,GAAG,IAAI;AAAA,YACL,UACG,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,EACzB,OAAO,CAAC,SAAyB,CAAC,CAAC,IAAI;AAAA,UAC5C;AAAA,QACF;AAGA,cAAM,aAAa;AAAA,UACjB,GAAG,IAAI;AAAA,YACL,UACG,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI,EACxB,OAAO,CAAC,SAAyB,CAAC,CAAC,IAAI;AAAA,UAC5C;AAAA,QACF;AAGA,cAAM,WAA4B;AAAA,UAChC,IAAI,SAAS,KAAK;AAAA,UAClB,OAAO,SAAS,KAAK;AAAA,UACrB,WAAW,SAAS,KAAK,YACrB,MAAM,SAAS,KAAK,SAAS,EAAE,OAAO,YAAY,IAClD;AAAA,UACJ,QAAQ,YAAY,SAAS,IAAI,YAAY,KAAK,IAAI,IAAI;AAAA,UAC1D,WAAW,WAAW,SAAS,IAAI,WAAW,KAAK,IAAI,IAAI;AAAA,UAC3D,WAAW,MAAM,SAAS,KAAK,SAAS,EAAE,OAAO,YAAY;AAAA,UAC7D;AAAA,UACA;AAAA,QACF;AAEA,iBAAS;AAAA,UACP;AAAA,UACA,SAAS;AAAA,UACT,OAAO;AAAA,UACP,YAAY,YAAY,KAAK;AAAA,QAC/B,CAAC;AAAA,MACH,SAAS,OAAO;AACd,cAAM,eAAe,4BAA4B,KAAK;AACtD,iBAAS,CAAC,UAAU;AAAA,UAClB,GAAG;AAAA,UACH,SAAS;AAAA,UACT,OAAO;AAAA,QACT,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,EACF;AACF;AA0BO,IAAM,uBAAuB,CAAC,cAA6B;AAChE,SAAO,MAA4B,mBAAmB,SAAS;AACjE;AAKO,IAAM,wBAAwB;","names":["StudentAnswerStatus","StudentAnswerDisplayStatus"]}