UNPKG

analytica-frontend-lib

Version:

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

1 lines 1.8 MB
{"version":3,"sources":["../src/utils/utils.ts","../src/utils/dropdown.ts","../src/utils/activityFilters.ts","../src/components/Quiz/useQuizStore.ts","../src/utils/questionTypeUtils.ts","../src/types/activityDetails.ts","../src/utils/activityDetailsUtils.ts","../src/components/Text/Text.tsx","../src/components/Button/Button.tsx","../src/components/Badge/Badge.tsx","../src/components/Alert/Alert.tsx","../src/components/LatexRenderer/LatexRenderer.tsx","../src/components/IconButton/IconButton.tsx","../src/components/IconRoundedButton/IconRoundedButton.tsx","../src/components/NavButton/NavButton.tsx","../src/components/SelectionButton/SelectionButton.tsx","../src/components/CheckBox/CheckBox.tsx","../src/components/ImageUpload/ImageUpload.tsx","../src/components/CheckBox/CheckboxList.tsx","../src/components/CheckBoxGroup/CheckBoxGroup.tsx","../src/components/CheckBoxGroup/CheckBoxGroup.helpers.ts","../src/components/AlertManager/AlertsManager.tsx","../src/components/AlertManager/useAlertForm.ts","../src/components/AlertManager/AlertSteps/MessageStep.tsx","../src/components/Input/Input.tsx","../src/components/TextArea/TextArea.tsx","../src/components/AlertManager/AlertSteps/RecipientsStep.tsx","../src/components/AlertManager/AlertSteps/DateStep.tsx","../src/components/AlertManager/AlertSteps/PreviewStep.tsx","../src/components/AlertManager/validation.ts","../src/components/Modal/Modal.tsx","../src/components/Modal/utils/videoUtils.ts","../src/components/Divider/Divider.tsx","../src/components/Table/Table.tsx","../src/components/NoSearchResult/NoSearchResult.tsx","../src/components/EmptyState/EmptyState.tsx","../src/components/Skeleton/Skeleton.tsx","../src/components/Table/TablePagination.tsx","../src/components/AlertManagerView/AlertsManagerView.tsx","../src/components/Radio/Radio.tsx","../src/components/Toast/Toast.tsx","../src/components/Toast/utils/ToastStore.ts","../src/components/Toast/utils/Toaster.tsx","../src/components/Search/Search.tsx","../src/components/DropdownMenu/DropdownMenu.tsx","../src/components/ThemeToggle/ThemeToggle.tsx","../src/hooks/useTheme.ts","../src/store/themeStore.ts","../src/components/Chips/Chips.tsx","../src/components/ProgressBar/ProgressBar.tsx","../src/components/ProgressCircle/ProgressCircle.tsx","../src/components/Stepper/Stepper.tsx","../src/components/Calendar/Calendar.tsx","../src/components/DateTimeInput/DateTimeInput.tsx","../src/components/CorrectActivityModal/CorrectActivityModal.tsx","../src/components/Alternative/Alternative.tsx","../src/components/Accordation/Accordation.tsx","../src/components/Card/Card.tsx","../src/components/IconRender/IconRender.tsx","../src/assets/icons/subjects/ChatPT.tsx","../src/assets/icons/subjects/ChatEN.tsx","../src/assets/icons/subjects/ChatES.tsx","../src/components/Accordation/AccordionGroup.tsx","../src/components/FileAttachment/FileAttachment.tsx","../src/types/studentActivityCorrection.ts","../src/components/AlertDialog/AlertDialog.tsx","../src/components/LoadingModal/loadingModal.tsx","../src/components/NotificationCard/NotificationCard.tsx","../src/hooks/useMobile.ts","../src/store/notificationStore.ts","../src/types/notifications.ts","../src/assets/icons/subjects/BookOpenText.tsx","../src/assets/icons/subjects/HeadCircuit.tsx","../src/assets/icons/subjects/Microscope.tsx","../src/enums/SubjectEnum.ts","../src/components/SubjectInfo/SubjectInfo.tsx","../src/hooks/useNotificationStore.ts","../src/hooks/useNotifications.ts","../src/types/questionTypes.ts","../src/hooks/useActivityFiltersData.ts","../src/hooks/useQuestionsList.ts","../src/components/ActivityCreate/ActivityCreate.tsx","../src/components/ActivityListQuestions/ActivityListQuestions.tsx","../src/utils/questionFiltersConverter.ts","../src/components/ActivityCreate/ActivityCreate.types.ts","../src/components/ActivityCreate/ActivityCreate.utils.ts","../src/components/ActivityCreate/components/ActivityCreateSkeleton.tsx","../src/components/ActivityCreate/components/ActivityCreateHeader.tsx","../src/types/questions.ts","../src/components/Filter/FilterModal.tsx","../src/components/Filter/useTableFilter.ts","../src/components/ActivityFilters/ActivityFilters.tsx","../src/components/TableProvider/TableProvider.tsx","../src/components/Select/Select.tsx","../src/components/Menu/Menu.tsx","../src/components/StatisticsCard/StatisticsCard.tsx","../src/components/NotFound/NotFound.tsx","../src/components/VideoPlayer/VideoPlayer.tsx","../src/components/DownloadButton/DownloadButton.tsx","../src/components/Whiteboard/Whiteboard.tsx","../src/components/Auth/Auth.tsx","../src/components/Auth/zustandAuthAdapter.ts","../src/components/Auth/useUrlAuthentication.ts","../src/components/Auth/useApiConfig.ts","../src/components/Quiz/Quiz.tsx","../src/components/Quiz/QuizContent.tsx","../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/Quiz/QuizResult.tsx","../src/components/BreadcrumbMenu/BreadcrumbMenu.tsx","../src/components/BreadcrumbMenu/useBreadcrumbBuilder.ts","../src/components/BreadcrumbMenu/breadcrumbStore.ts","../src/components/BreadcrumbMenu/useUrlParams.ts","../src/hooks/useAppInitialization.ts","../src/hooks/useInstitution.ts","../src/store/appStore.ts","../src/store/authStore.ts","../src/hooks/useAppContent.ts","../src/store/questionFiltersStore.ts","../src/components/ActivityCardQuestionBanks/ActivityCardQuestionBanks.tsx","../src/utils/questionRenderer.ts","../src/components/ActivityCardQuestionPreview/ActivityCardQuestionPreview.tsx","../src/components/ActivityPreview/ActivityPreview.tsx","../src/components/QuestionsPdfGenerator/QuestionsPdfGenerator.tsx","../src/components/ActivityDetails/ActivityDetails.tsx","../src/components/Support/Support.tsx","../src/components/Support/schema/index.ts","../src/components/Support/components/TicketModal.tsx","../src/types/support.ts","../src/components/Support/utils/supportUtils.tsx","../src/components/SendActivityModal/SendActivityModal.tsx","../src/components/SendActivityModal/hooks/useSendActivityModal.ts","../src/components/SendActivityModal/validation.ts","../src/components/SendActivityModal/types.ts","../src/components/RecommendedLessonsHistory/RecommendedLessonsHistory.tsx","../src/types/common.ts","../src/types/recommendedLessons.ts","../src/hooks/useRecommendedLessons.ts","../src/components/RecommendedLessonDetails/RecommendedLessonDetails.tsx","../src/components/RecommendedLessonDetails/components/Breadcrumb.tsx","../src/components/RecommendedLessonDetails/components/LessonHeader.tsx","../src/components/RecommendedLessonDetails/utils/lessonDetailsUtils.ts","../src/components/RecommendedLessonDetails/components/LoadingSkeleton.tsx","../src/components/RecommendedLessonDetails/components/ResultsSection.tsx","../src/components/RecommendedLessonDetails/components/StudentsTable.tsx","../src/components/RecommendedLessonDetails/components/StudentPerformanceModal.tsx","../src/components/RecommendedLessonDetails/types.ts","../src/hooks/useRecommendedLessonDetails.ts","../src/components/ActivitiesHistory/ActivitiesHistory.tsx","../src/components/ActivitiesHistory/tabs/HistoryTab.tsx","../src/components/ActivitiesHistory/components/ErrorDisplay.tsx","../src/components/ActivitiesHistory/config/historyTableColumns.tsx","../src/components/ActivitiesHistory/utils/renderSubjectCell.tsx","../src/components/ActivitiesHistory/utils/renderTruncatedText.tsx","../src/components/ActivitiesHistory/utils/filterBuilders.ts","../src/components/ActivitiesHistory/utils/filterOptions.ts","../src/types/activitiesHistory.ts","../src/components/ActivitiesHistory/config/historyFiltersConfig.ts","../src/hooks/useActivitiesHistory.ts","../src/utils/hookErrorHandler.ts","../src/components/ActivitiesHistory/tabs/ModelsTab.tsx","../src/components/ActivitiesHistory/config/modelsTableColumns.tsx","../src/components/ActivitiesHistory/config/modelsFiltersConfig.ts","../src/hooks/useActivityModels.ts","../src/components/ActivitiesHistory/tabs/DraftsTab.tsx","../src/utils/subjectMappers.ts","../src/utils/filterHelpers.ts","../src/hooks/useChat.ts","../src/hooks/useChatRooms.ts","../src/types/chat.ts","../src/components/Chat/Chat.tsx","../src/components/Chat/ChatLoading.tsx","../src/utils/chatUtils.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport { syncDropdownState } from './dropdown';\nexport {\n getSelectedIdsFromCategories,\n toggleArrayItem,\n toggleSingleValue,\n areFiltersEqual,\n} from './activityFilters';\nexport {\n mapQuestionTypeToEnum,\n mapQuestionTypeToEnumRequired,\n} from './questionTypeUtils';\nexport {\n getStatusBadgeConfig,\n formatTimeSpent,\n formatQuestionNumbers,\n formatDateToBrazilian,\n} from './activityDetailsUtils';\n\n/**\n * Retorna a cor hexadecimal com opacidade 0.3 (4d) se não estiver em dark mode.\n * Se estiver em dark mode, retorna a cor original.\n *\n * @param hexColor - Cor hexadecimal (ex: \"#0066b8\" ou \"0066b8\")\n * @param isDark - booleano indicando se está em dark mode\n * @returns string - cor hexadecimal com opacidade se necessário\n */\nexport function getSubjectColorWithOpacity(\n hexColor: string | undefined,\n isDark: boolean\n): string | undefined {\n if (!hexColor) return undefined;\n // Remove o '#' se existir\n let color = hexColor.replace(/^#/, '').toLowerCase();\n\n if (isDark) {\n // Se está em dark mode, sempre remove opacidade se existir\n if (color.length === 8) {\n color = color.slice(0, 6);\n }\n return `#${color}`;\n } else {\n // Se não está em dark mode (light mode)\n let resultColor: string;\n if (color.length === 6) {\n // Adiciona opacidade 0.3 (4D) para cores de 6 dígitos\n resultColor = `#${color}4d`;\n } else if (color.length === 8) {\n // Já tem opacidade, retorna como está\n resultColor = `#${color}`;\n } else {\n // Para outros tamanhos (3, 4, 5 dígitos), retorna como está\n resultColor = `#${color}`;\n }\n return resultColor;\n }\n}\n","import type { Dispatch, SetStateAction } from 'react';\n\n/**\n * Synchronizes dropdown state when it closes externally\n * This ensures the toggle button state matches the dropdown state\n *\n * @param open - Current dropdown open state\n * @param isActive - Current active state of the toggle button\n * @param setActiveStates - Function to update active states\n * @param key - Key identifier for the specific dropdown\n */\nexport const syncDropdownState = (\n open: boolean,\n isActive: boolean,\n setActiveStates: Dispatch<SetStateAction<Record<string, boolean>>>,\n key: string\n) => {\n if (!open && isActive) {\n setActiveStates((prev) => ({ ...prev, [key]: false }));\n }\n};\n","import type { CategoryConfig } from '../components/CheckBoxGroup/CheckBoxGroup';\nimport type { ActivityFiltersData } from '../types/activityFilters';\n\n/**\n * Extracts selected IDs from knowledge categories by their keys\n * @param categories - Array of category configurations\n * @param keys - Object mapping output keys to category keys\n * @returns Object with extracted selected IDs for each output key\n */\nexport function getSelectedIdsFromCategories(\n categories: CategoryConfig[],\n keys: Record<string, string>\n): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n\n for (const [outputKey, categoryKey] of Object.entries(keys)) {\n const category = categories.find((c) => c.key === categoryKey);\n result[outputKey] = category?.selectedIds || [];\n }\n\n return result;\n}\n\n/**\n * Toggles an item in an array (adds if not present, removes if present)\n * @param array - Current array\n * @param item - Item to toggle\n * @returns New array with item toggled\n */\nexport function toggleArrayItem<T>(array: T[], item: T): T[] {\n return array.includes(item)\n ? array.filter((i) => i !== item)\n : [...array, item];\n}\n\n/**\n * Toggles a single value (returns null if current value, otherwise returns new value)\n * @param currentValue - Current selected value\n * @param newValue - Value to toggle to\n * @returns New value or null if toggling off\n */\nexport function toggleSingleValue<T>(\n currentValue: T | null,\n newValue: T\n): T | null {\n return currentValue === newValue ? null : newValue;\n}\n\n/**\n * Compares two arrays for equality (order-independent)\n * @param a - First array\n * @param b - Second array\n * @param comparator - Optional comparator function. If not provided, uses string conversion and localeCompare\n * @returns true if arrays contain the same elements, false otherwise\n */\nfunction arraysEqual<T>(\n a: T[],\n b: T[],\n comparator?: (x: T, y: T) => number\n): boolean {\n if (a.length !== b.length) return false;\n\n if (comparator) {\n const sortedA = [...a].sort(comparator);\n const sortedB = [...b].sort(comparator);\n return sortedA.every((val, index) => val === sortedB[index]);\n }\n\n const sortedA = [...a].sort((x, y) => String(x).localeCompare(String(y)));\n const sortedB = [...b].sort((x, y) => String(x).localeCompare(String(y)));\n return sortedA.every((val, index) => val === sortedB[index]);\n}\n\nexport function areFiltersEqual(\n filters1: ActivityFiltersData | null,\n filters2: ActivityFiltersData | null\n): boolean {\n if (filters1 === filters2) return true;\n if (!filters1 || !filters2) return false;\n\n return (\n arraysEqual(filters1.types, filters2.types) &&\n arraysEqual(filters1.bankIds, filters2.bankIds) &&\n arraysEqual(filters1.yearIds, filters2.yearIds) &&\n arraysEqual(filters1.knowledgeIds, filters2.knowledgeIds) &&\n arraysEqual(filters1.topicIds, filters2.topicIds) &&\n arraysEqual(filters1.subtopicIds, filters2.subtopicIds) &&\n arraysEqual(filters1.contentIds, filters2.contentIds)\n );\n}\n","import { create } from 'zustand';\nimport { devtools } from 'zustand/middleware';\n\nexport enum QUESTION_DIFFICULTY {\n FACIL = 'FACIL',\n MEDIO = 'MEDIO',\n DIFICIL = 'DIFICIL',\n}\n\n// Enum para tipos de quiz\nexport enum QUIZ_TYPE {\n SIMULADO = 'SIMULADO',\n QUESTIONARIO = 'QUESTIONARIO',\n ATIVIDADE = 'ATIVIDADE',\n}\n\nexport enum QUESTION_TYPE {\n ALTERNATIVA = 'ALTERNATIVA',\n DISSERTATIVA = 'DISSERTATIVA',\n MULTIPLA_ESCOLHA = 'MULTIPLA_ESCOLHA',\n VERDADEIRO_FALSO = 'VERDADEIRO_FALSO',\n LIGAR_PONTOS = 'LIGAR_PONTOS',\n PREENCHER = 'PREENCHER',\n IMAGEM = 'IMAGEM',\n}\n\nexport enum QUESTION_STATUS {\n PENDENTE_AVALIACAO = 'PENDENTE_AVALIACAO',\n RESPOSTA_CORRETA = 'RESPOSTA_CORRETA',\n RESPOSTA_INCORRETA = 'RESPOSTA_INCORRETA',\n NAO_RESPONDIDO = 'NAO_RESPONDIDO',\n}\n\nexport enum ANSWER_STATUS {\n RESPOSTA_CORRETA = 'RESPOSTA_CORRETA',\n RESPOSTA_INCORRETA = 'RESPOSTA_INCORRETA',\n PENDENTE_AVALIACAO = 'PENDENTE_AVALIACAO',\n NAO_RESPONDIDO = 'NAO_RESPONDIDO',\n}\n\nexport enum SUBTYPE_ENUM {\n PROVA = 'PROVA',\n ENEM_PROVA_1 = 'ENEM_PROVA_1',\n ENEM_PROVA_2 = 'ENEM_PROVA_2',\n VESTIBULAR = 'VESTIBULAR',\n SIMULADO = 'SIMULADO',\n SIMULADAO = 'SIMULADAO',\n}\n\nexport interface QuestionResult {\n answers: {\n id: string;\n questionId: string;\n answer: string | null;\n selectedOptions: {\n optionId: string;\n }[];\n answerStatus: ANSWER_STATUS;\n statement: string;\n questionType: QUESTION_TYPE;\n difficultyLevel: QUESTION_DIFFICULTY;\n solutionExplanation: string | null;\n correctOption: string;\n createdAt: string;\n updatedAt: string;\n options?: {\n id: string;\n option: string;\n isCorrect: boolean;\n }[];\n knowledgeMatrix: {\n areaKnowledge: {\n id: string;\n name: string;\n } | null;\n subject: {\n id: string;\n name: string;\n color: string;\n icon: string;\n } | null;\n topic: {\n id: string;\n name: string;\n } | null;\n subtopic: {\n id: string;\n name: string;\n } | null;\n content: {\n id: string;\n name: string;\n } | null;\n }[];\n teacherFeedback: string | null;\n attachment: string | null;\n score: number | null;\n gradedAt: string | null;\n gradedBy: string | null;\n }[];\n statistics: {\n totalAnswered: number;\n correctAnswers: number;\n incorrectAnswers: number;\n pendingAnswers: number;\n score: number;\n timeSpent: number;\n };\n}\n\nexport interface Question {\n id: string;\n statement: string;\n questionType: QUESTION_TYPE;\n difficultyLevel: QUESTION_DIFFICULTY;\n description: string;\n examBoard: string | null;\n examYear: string | null;\n solutionExplanation: string | null;\n answer: null;\n answerStatus: ANSWER_STATUS;\n options: {\n id: string;\n option: string;\n }[];\n knowledgeMatrix: {\n areaKnowledge: {\n id: string;\n name: string;\n };\n subject: {\n id: string;\n name: string;\n color: string;\n icon: string;\n };\n topic: {\n id: string;\n name: string;\n };\n subtopic: {\n id: string;\n name: string;\n };\n content: {\n id: string;\n name: string;\n };\n }[];\n correctOptionIds?: string[];\n}\n\nexport interface QuizInterface {\n id: string;\n title: string;\n type: QUIZ_TYPE;\n subtype: SUBTYPE_ENUM | string;\n difficulty: string | null;\n notification: string | null;\n status: string;\n startDate: string | null;\n finalDate: string | null;\n canRetry: boolean;\n createdAt: string | null;\n updatedAt: string | null;\n questions: Question[];\n}\n\nexport interface UserAnswerItem {\n questionId: string;\n activityId: string;\n userId: string;\n answer: string | null;\n optionId: string | null;\n questionType: QUESTION_TYPE;\n answerStatus: ANSWER_STATUS;\n}\n\nexport interface QuizState {\n // Data\n quiz: QuizInterface | null;\n\n // UI State\n currentQuestionIndex: number;\n selectedAnswers: Record<string, string>;\n userAnswers: UserAnswerItem[];\n timeElapsed: number;\n isStarted: boolean;\n isFinished: boolean;\n userId: string;\n variant: 'result' | 'default';\n minuteCallback: (() => void) | null;\n dissertativeCharLimit?: number;\n // Actions\n setQuiz: (quiz: QuizInterface) => void;\n setQuestionResult: (questionResult: QuestionResult) => void;\n setUserId: (userId: string) => void;\n setUserAnswers: (userAnswers: UserAnswerItem[]) => void;\n setVariant: (variant: 'result' | 'default') => void;\n setDissertativeCharLimit: (limit?: number) => void;\n getDissertativeCharLimit: () => number | undefined;\n // Quiz Navigation\n goToNextQuestion: () => void;\n goToPreviousQuestion: () => void;\n goToQuestion: (index: number) => void;\n\n // Quiz Actions\n selectAnswer: (questionId: string, answerId: string) => void;\n selectMultipleAnswer: (questionId: string, answerIds: string[]) => void;\n selectDissertativeAnswer: (questionId: string, answer: string) => void;\n skipQuestion: () => void;\n skipCurrentQuestionIfUnanswered: () => void;\n addUserAnswer: (questionId: string, answerId?: string) => void;\n startQuiz: () => void;\n finishQuiz: () => void;\n resetQuiz: () => void;\n\n // Timer\n updateTime: (time: number) => void;\n startTimer: () => void;\n stopTimer: () => void;\n\n // Minute Callback\n setMinuteCallback: (callback: (() => void) | null) => void;\n startMinuteCallback: () => void;\n stopMinuteCallback: () => void;\n\n // Getters\n getCurrentQuestion: () => Question | null;\n getTotalQuestions: () => number;\n getAnsweredQuestions: () => number;\n getUnansweredQuestions: () => number[];\n getSkippedQuestions: () => number;\n getProgress: () => number;\n isQuestionAnswered: (questionId: string) => boolean;\n isQuestionSkipped: (questionId: string) => boolean;\n getCurrentAnswer: () => UserAnswerItem | undefined;\n getAllCurrentAnswer: () => UserAnswerItem[] | undefined;\n getQuizTitle: () => string;\n formatTime: (seconds: number) => string;\n getUserAnswers: () => UserAnswerItem[];\n getUnansweredQuestionsFromUserAnswers: () => number[];\n getQuestionsGroupedBySubject: () => { [key: string]: Question[] };\n getUserId: () => string;\n setCurrentQuestion: (question: Question) => void;\n\n // New methods for userAnswers\n getQuestionIndex: (questionId: string) => number;\n getUserAnswerByQuestionId: (questionId: string) => UserAnswerItem | null;\n isQuestionAnsweredByUserAnswers: (questionId: string) => boolean;\n getQuestionStatusFromUserAnswers: (\n questionId: string\n ) => 'answered' | 'unanswered' | 'skipped';\n getUserAnswersForActivity: () => UserAnswerItem[];\n // Answer status management\n setAnswerStatus: (questionId: string, status: ANSWER_STATUS) => void;\n getAnswerStatus: (questionId: string) => ANSWER_STATUS | null;\n\n // Question Result\n questionsResult: QuestionResult | null;\n currentQuestionResult: QuestionResult['answers'] | null;\n setQuestionsResult: (questionsResult: QuestionResult) => void;\n setCurrentQuestionResult: (\n currentQuestionResult: QuestionResult['answers']\n ) => void;\n getQuestionResultByQuestionId: (\n questionId: string\n ) => QuestionResult['answers'][number] | null;\n getQuestionResultStatistics: () => QuestionResult['statistics'] | null;\n getQuestionResult: () => QuestionResult | null;\n getCurrentQuestionResult: () => QuestionResult['answers'] | null;\n}\n\n// Constants\nexport const MINUTE_INTERVAL_MS = 60000; // 60 seconds = 1 minute\n\nexport const useQuizStore = create<QuizState>()(\n devtools(\n (set, get) => {\n let timerInterval: ReturnType<typeof setInterval> | null = null;\n let minuteCallbackInterval: ReturnType<typeof setInterval> | null = null;\n\n const startTimer = () => {\n if (get().isFinished) {\n return;\n }\n\n if (timerInterval) {\n clearInterval(timerInterval);\n }\n\n timerInterval = setInterval(() => {\n const { timeElapsed } = get();\n set({ timeElapsed: timeElapsed + 1 });\n }, 1000);\n };\n\n const stopTimer = () => {\n if (timerInterval) {\n clearInterval(timerInterval);\n timerInterval = null;\n }\n };\n\n const setMinuteCallback = (callback: (() => void) | null) => {\n set({ minuteCallback: callback });\n };\n\n const startMinuteCallback = () => {\n const { minuteCallback, isFinished } = get();\n\n if (isFinished || !minuteCallback) {\n return;\n }\n\n if (minuteCallbackInterval) {\n clearInterval(minuteCallbackInterval);\n }\n\n minuteCallbackInterval = setInterval(() => {\n const {\n minuteCallback: currentCallback,\n isFinished: currentIsFinished,\n } = get();\n\n if (currentIsFinished || !currentCallback) {\n stopMinuteCallback();\n return;\n }\n\n currentCallback();\n }, MINUTE_INTERVAL_MS);\n };\n\n const stopMinuteCallback = () => {\n if (minuteCallbackInterval) {\n clearInterval(minuteCallbackInterval);\n minuteCallbackInterval = null;\n }\n };\n\n return {\n // Initial State\n quiz: null,\n currentQuestionIndex: 0,\n selectedAnswers: {},\n userAnswers: [],\n timeElapsed: 0,\n isStarted: false,\n isFinished: false,\n userId: '',\n variant: 'default',\n minuteCallback: null,\n dissertativeCharLimit: undefined,\n questionsResult: null,\n currentQuestionResult: null,\n // Setters\n setQuiz: (quiz) => set({ quiz }),\n setUserId: (userId) => set({ userId }),\n setUserAnswers: (userAnswers) => set({ userAnswers }),\n getUserId: () => get().userId,\n setVariant: (variant) => set({ variant }),\n setQuestionResult: (questionsResult) => set({ questionsResult }),\n setDissertativeCharLimit: (limit?: number) =>\n set({ dissertativeCharLimit: limit }),\n getDissertativeCharLimit: () => get().dissertativeCharLimit,\n // Navigation\n goToNextQuestion: () => {\n const { currentQuestionIndex, getTotalQuestions } = get();\n const totalQuestions = getTotalQuestions();\n\n if (currentQuestionIndex < totalQuestions - 1) {\n set({ currentQuestionIndex: currentQuestionIndex + 1 });\n }\n },\n\n goToPreviousQuestion: () => {\n const { currentQuestionIndex } = get();\n\n if (currentQuestionIndex > 0) {\n set({ currentQuestionIndex: currentQuestionIndex - 1 });\n }\n },\n\n goToQuestion: (index) => {\n const { getTotalQuestions } = get();\n const totalQuestions = getTotalQuestions();\n\n if (index >= 0 && index < totalQuestions) {\n set({ currentQuestionIndex: index });\n }\n },\n\n selectAnswer: (questionId, answerId) => {\n const { quiz, userAnswers } = get();\n\n if (!quiz) return;\n\n const activityId = quiz.id;\n const userId = get().getUserId();\n\n if (!userId || userId === '') {\n // Silent validation - userId not set\n return;\n }\n\n const question = quiz.questions.find((q) => q.id === questionId);\n if (!question) return;\n\n const existingAnswerIndex = userAnswers.findIndex(\n (answer) => answer.questionId === questionId\n );\n\n const newUserAnswer: UserAnswerItem = {\n questionId,\n activityId,\n userId,\n answer:\n question.questionType === QUESTION_TYPE.DISSERTATIVA\n ? answerId\n : null,\n optionId:\n question.questionType === QUESTION_TYPE.DISSERTATIVA\n ? null\n : answerId,\n questionType: question.questionType,\n answerStatus: ANSWER_STATUS.PENDENTE_AVALIACAO,\n };\n\n let updatedUserAnswers;\n if (existingAnswerIndex !== -1) {\n updatedUserAnswers = [...userAnswers];\n updatedUserAnswers[existingAnswerIndex] = newUserAnswer;\n } else {\n updatedUserAnswers = [...userAnswers, newUserAnswer];\n }\n\n set({\n userAnswers: updatedUserAnswers,\n });\n },\n\n selectMultipleAnswer: (questionId, answerIds) => {\n const { quiz, userAnswers } = get();\n\n if (!quiz) return;\n\n const activityId = quiz.id;\n const userId = get().getUserId();\n\n if (!userId || userId === '') {\n // Silent validation - userId not set\n return;\n }\n\n const question = quiz.questions.find((q) => q.id === questionId);\n if (!question) return;\n\n // Remove all existing answers for this questionId\n const filteredUserAnswers = userAnswers.filter(\n (answer) => answer.questionId !== questionId\n );\n\n // Create new UserAnswerItem objects for each answerId\n const newUserAnswers: UserAnswerItem[] = answerIds.map(\n (answerId) => ({\n questionId,\n activityId,\n userId,\n answer: null, // selectMultipleAnswer is for non-dissertative questions\n optionId: answerId, // selectMultipleAnswer should only set optionId\n questionType: question.questionType,\n answerStatus: ANSWER_STATUS.PENDENTE_AVALIACAO,\n })\n );\n\n // Combine filtered answers with new answers\n const updatedUserAnswers = [\n ...filteredUserAnswers,\n ...newUserAnswers,\n ];\n\n set({\n userAnswers: updatedUserAnswers,\n });\n },\n\n selectDissertativeAnswer: (questionId, answer) => {\n const { quiz, userAnswers, dissertativeCharLimit } = get();\n\n if (!quiz) return;\n\n const activityId = quiz.id;\n const userId = get().getUserId();\n\n if (!userId || userId === '') {\n // Silent validation - userId not set\n return;\n }\n\n const question = quiz.questions.find((q) => q.id === questionId);\n if (\n !question ||\n question.questionType !== QUESTION_TYPE.DISSERTATIVA\n ) {\n // Silent validation - wrong question type\n return;\n }\n\n // Validate character limit if set\n let validatedAnswer = answer;\n if (\n dissertativeCharLimit !== undefined &&\n answer.length > dissertativeCharLimit\n ) {\n validatedAnswer = answer.substring(0, dissertativeCharLimit);\n }\n\n const existingAnswerIndex = userAnswers.findIndex(\n (answerItem) => answerItem.questionId === questionId\n );\n\n const newUserAnswer: UserAnswerItem = {\n questionId,\n activityId,\n userId,\n answer: validatedAnswer,\n optionId: null,\n questionType: QUESTION_TYPE.DISSERTATIVA,\n answerStatus: ANSWER_STATUS.PENDENTE_AVALIACAO,\n };\n\n let updatedUserAnswers;\n if (existingAnswerIndex !== -1) {\n updatedUserAnswers = [...userAnswers];\n updatedUserAnswers[existingAnswerIndex] = newUserAnswer;\n } else {\n updatedUserAnswers = [...userAnswers, newUserAnswer];\n }\n\n set({\n userAnswers: updatedUserAnswers,\n });\n },\n\n skipQuestion: () => {\n const { getCurrentQuestion, userAnswers, quiz } = get();\n const currentQuestion = getCurrentQuestion();\n\n if (!quiz) return;\n\n if (currentQuestion) {\n const activityId = quiz.id;\n const userId = get().getUserId();\n\n if (!userId || userId === '') {\n // Silent validation - userId not set\n return;\n }\n\n const existingAnswerIndex = userAnswers.findIndex(\n (answer) => answer.questionId === currentQuestion.id\n );\n\n const newUserAnswer: UserAnswerItem = {\n questionId: currentQuestion.id,\n activityId,\n userId,\n answer: null,\n optionId: null,\n questionType: currentQuestion.questionType,\n answerStatus: ANSWER_STATUS.PENDENTE_AVALIACAO,\n };\n\n let updatedUserAnswers;\n if (existingAnswerIndex !== -1) {\n // Update existing answer\n updatedUserAnswers = [...userAnswers];\n updatedUserAnswers[existingAnswerIndex] = newUserAnswer;\n } else {\n // Add new answer\n updatedUserAnswers = [...userAnswers, newUserAnswer];\n }\n\n set({\n userAnswers: updatedUserAnswers,\n });\n }\n },\n\n skipCurrentQuestionIfUnanswered: () => {\n const { getCurrentQuestion, getCurrentAnswer, skipQuestion } = get();\n const currentQuestion = getCurrentQuestion();\n const currentAnswer = getCurrentAnswer();\n\n // Se não há questão atual, não faz nada\n if (!currentQuestion) return;\n\n // Se não há resposta ou a resposta está vazia (null), marca como pulada\n if (\n !currentAnswer ||\n (currentAnswer.optionId === null && currentAnswer.answer === null)\n ) {\n skipQuestion();\n }\n },\n\n addUserAnswer: (questionId, answerId) => {\n const { quiz, userAnswers } = get();\n\n if (!quiz) return;\n\n // Add to userAnswers array with new structure\n const activityId = quiz.id;\n const userId = get().getUserId();\n\n if (!userId || userId === '') {\n // Silent validation - userId not set\n return;\n }\n\n const question = quiz.questions.find((q) => q.id === questionId);\n if (!question) return;\n\n const existingAnswerIndex = userAnswers.findIndex(\n (answer) => answer.questionId === questionId\n );\n\n const newUserAnswer: UserAnswerItem = {\n questionId,\n activityId,\n userId,\n answer:\n question.questionType === QUESTION_TYPE.DISSERTATIVA\n ? answerId || null\n : null,\n optionId:\n question.questionType !== QUESTION_TYPE.DISSERTATIVA\n ? answerId || null\n : null,\n questionType: question.questionType,\n answerStatus: ANSWER_STATUS.PENDENTE_AVALIACAO,\n };\n\n if (existingAnswerIndex !== -1) {\n // Update existing answer\n const updatedUserAnswers = [...userAnswers];\n updatedUserAnswers[existingAnswerIndex] = newUserAnswer;\n set({ userAnswers: updatedUserAnswers });\n } else {\n // Add new answer\n set({ userAnswers: [...userAnswers, newUserAnswer] });\n }\n },\n\n startQuiz: () => {\n set({ isStarted: true, timeElapsed: 0 });\n startTimer();\n startMinuteCallback();\n },\n\n finishQuiz: () => {\n set({ isFinished: true });\n stopTimer();\n stopMinuteCallback();\n },\n\n resetQuiz: () => {\n stopTimer();\n stopMinuteCallback();\n set({\n quiz: null,\n currentQuestionIndex: 0,\n selectedAnswers: {},\n userAnswers: [],\n timeElapsed: 0,\n isStarted: false,\n isFinished: false,\n userId: '',\n variant: 'default',\n minuteCallback: null,\n dissertativeCharLimit: undefined,\n questionsResult: null,\n currentQuestionResult: null,\n });\n },\n\n // Timer\n updateTime: (time) => set({ timeElapsed: time }),\n startTimer,\n stopTimer,\n\n // Minute Callback\n setMinuteCallback,\n startMinuteCallback,\n stopMinuteCallback,\n\n // Getters\n getCurrentQuestion: () => {\n const { currentQuestionIndex, quiz } = get();\n\n if (!quiz) {\n return null;\n }\n\n return quiz.questions[currentQuestionIndex];\n },\n\n getTotalQuestions: () => {\n const { quiz } = get();\n\n return quiz?.questions?.length || 0;\n },\n\n getAnsweredQuestions: () => {\n const { userAnswers } = get();\n return userAnswers.filter(\n (answer) => answer.optionId !== null || answer.answer !== null\n ).length;\n },\n\n getUnansweredQuestions: () => {\n const { quiz, userAnswers } = get();\n if (!quiz) return [];\n\n const unansweredQuestions: number[] = [];\n\n quiz.questions.forEach((question, index) => {\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === question.id\n );\n const isAnswered =\n userAnswer &&\n (userAnswer.optionId !== null || userAnswer.answer !== null);\n const isSkipped =\n userAnswer &&\n userAnswer.optionId === null &&\n userAnswer.answer === null;\n\n if (!isAnswered && !isSkipped) {\n unansweredQuestions.push(index + 1); // index + 1 para mostrar número da questão\n }\n });\n return unansweredQuestions;\n },\n\n getSkippedQuestions: () => {\n const { userAnswers } = get();\n return userAnswers.filter(\n (answer) => answer.optionId === null && answer.answer === null\n ).length;\n },\n\n getProgress: () => {\n const { getTotalQuestions, getAnsweredQuestions } = get();\n const total = getTotalQuestions();\n const answered = getAnsweredQuestions();\n\n return total > 0 ? (answered / total) * 100 : 0;\n },\n\n isQuestionAnswered: (questionId) => {\n const { userAnswers } = get();\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === questionId\n );\n return userAnswer\n ? userAnswer.optionId !== null || userAnswer.answer !== null\n : false;\n },\n\n isQuestionSkipped: (questionId) => {\n const { userAnswers } = get();\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === questionId\n );\n return userAnswer\n ? userAnswer.optionId === null && userAnswer.answer === null\n : false;\n },\n\n getCurrentAnswer: () => {\n const { getCurrentQuestion, userAnswers } = get();\n const currentQuestion = getCurrentQuestion();\n\n if (!currentQuestion) return undefined;\n\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === currentQuestion.id\n );\n\n // Retorna undefined se a resposta está vazia (não respondida)\n const hasAnswerContent = (ua?: UserAnswerItem | null) =>\n !!ua &&\n ((ua.optionId !== null && ua.optionId !== '') ||\n (ua.answer !== null && ua.answer !== ''));\n\n if (!hasAnswerContent(userAnswer)) {\n return undefined;\n }\n\n return userAnswer;\n },\n\n getAllCurrentAnswer: () => {\n const { getCurrentQuestion, userAnswers } = get();\n const currentQuestion = getCurrentQuestion();\n\n if (!currentQuestion) return undefined;\n\n const userAnswer = userAnswers.filter(\n (answer) => answer.questionId === currentQuestion.id\n );\n\n return userAnswer;\n },\n\n getQuizTitle: () => {\n const { quiz } = get();\n\n return quiz?.title || 'Quiz';\n },\n\n formatTime: (seconds: number) => {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n },\n\n getUserAnswers: () => {\n const { userAnswers } = get();\n return userAnswers;\n },\n\n getUnansweredQuestionsFromUserAnswers: () => {\n const { quiz, userAnswers } = get();\n if (!quiz) return [];\n\n const unansweredQuestions: number[] = [];\n\n quiz.questions.forEach((question, index) => {\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === question.id\n );\n const hasAnswer =\n userAnswer &&\n (userAnswer.optionId !== null || userAnswer.answer !== null);\n const isSkipped =\n userAnswer &&\n userAnswer.optionId === null &&\n userAnswer.answer === null;\n\n // Se não há resposta do usuário OU se a questão foi pulada\n if (!hasAnswer || isSkipped) {\n unansweredQuestions.push(index + 1); // index + 1 para mostrar número da questão\n }\n });\n\n return unansweredQuestions;\n },\n\n getQuestionsGroupedBySubject: () => {\n const { getQuestionResult, quiz, variant } = get();\n const questions =\n variant == 'result'\n ? getQuestionResult()?.answers\n : quiz?.questions;\n if (!questions) return {};\n const groupedQuestions: {\n [key: string]: (Question | QuestionResult['answers'][number])[];\n } = {};\n questions.forEach((question) => {\n const subjectId =\n question.knowledgeMatrix?.[0]?.subject?.id || 'Sem matéria';\n\n if (!groupedQuestions[subjectId]) {\n groupedQuestions[subjectId] = [];\n }\n\n groupedQuestions[subjectId].push(question);\n });\n\n return groupedQuestions;\n },\n\n // New methods for userAnswers\n getUserAnswerByQuestionId: (questionId) => {\n const { userAnswers } = get();\n return (\n userAnswers.find((answer) => answer.questionId === questionId) ||\n null\n );\n },\n isQuestionAnsweredByUserAnswers: (questionId) => {\n const { userAnswers } = get();\n const answer = userAnswers.find(\n (answer) => answer.questionId === questionId\n );\n return answer\n ? answer.optionId !== null || answer.answer !== null\n : false;\n },\n getQuestionStatusFromUserAnswers: (questionId) => {\n const { userAnswers } = get();\n const answer = userAnswers.find(\n (answer) => answer.questionId === questionId\n );\n if (!answer) return 'unanswered';\n if (answer.optionId === null) return 'skipped';\n return 'answered';\n },\n getUserAnswersForActivity: () => {\n const { userAnswers } = get();\n return userAnswers;\n },\n setCurrentQuestion: (question) => {\n const { quiz, variant, questionsResult } = get();\n if (!quiz) return;\n let questionIndex = 0;\n if (variant == 'result') {\n if (!questionsResult) return;\n const questionResult =\n questionsResult.answers.find((q) => q.id === question.id) ??\n questionsResult.answers.find((q) => q.questionId === question.id);\n if (!questionResult) return;\n questionIndex = quiz.questions.findIndex(\n (q) => q.id === questionResult.questionId\n );\n } else {\n questionIndex = quiz.questions.findIndex(\n (q) => q.id === question.id\n );\n }\n\n // Validate that the question was found before updating currentQuestionIndex\n if (questionIndex === -1) {\n // Silent validation - question not found\n return;\n }\n\n set({ currentQuestionIndex: questionIndex });\n },\n\n setAnswerStatus: (questionId, status) => {\n const { userAnswers } = get();\n const existingAnswerIndex = userAnswers.findIndex(\n (answer) => answer.questionId === questionId\n );\n\n if (existingAnswerIndex !== -1) {\n const updatedUserAnswers = [...userAnswers];\n updatedUserAnswers[existingAnswerIndex] = {\n ...updatedUserAnswers[existingAnswerIndex],\n answerStatus: status,\n };\n set({ userAnswers: updatedUserAnswers });\n }\n },\n\n getAnswerStatus: (questionId) => {\n const { userAnswers } = get();\n const userAnswer = userAnswers.find(\n (answer) => answer.questionId === questionId\n );\n return userAnswer ? userAnswer.answerStatus : null;\n },\n getQuestionIndex: (questionId) => {\n const { questionsResult, variant, quiz } = get();\n if (variant == 'result') {\n if (!questionsResult) return 0;\n\n let idx = questionsResult.answers.findIndex(\n (q) => q.questionId === questionId\n );\n if (idx === -1) {\n idx = questionsResult.answers.findIndex(\n (q) => q.id === questionId\n );\n }\n return idx !== -1 ? idx + 1 : 0;\n } else {\n if (!quiz) return 0;\n const idx = quiz.questions.findIndex((q) => q.id === questionId);\n return idx !== -1 ? idx + 1 : 0;\n }\n },\n\n // Question Result\n getQuestionResultByQuestionId: (questionId) => {\n const { questionsResult } = get();\n const question = questionsResult?.answers.find(\n (answer) => answer.questionId === questionId\n );\n\n return question || null;\n },\n getQuestionResultStatistics: () => {\n const { questionsResult } = get();\n return questionsResult?.statistics || null;\n },\n getQuestionResult: () => {\n const { questionsResult } = get();\n return questionsResult;\n },\n setQuestionsResult: (questionsResult) => {\n set({ questionsResult });\n },\n setCurrentQuestionResult: (currentQuestionResult) => {\n set({ currentQuestionResult });\n },\n getCurrentQuestionResult: () => {\n const { currentQuestionResult } = get();\n return currentQuestionResult;\n },\n };\n },\n {\n name: 'quiz-store',\n }\n )\n);\n","import { QUESTION_TYPE } from '../components/Quiz/useQuizStore';\n\n/**\n * Maps API question type string to QUESTION_TYPE enum\n * Converts input to uppercase for case-insensitive matching\n *\n * @param type - Question type string from API\n * @param fallback - Optional fallback QUESTION_TYPE if mapping fails. If not provided, returns null\n * @returns QUESTION_TYPE enum value or null/fallback if not found\n */\nexport function mapQuestionTypeToEnum(\n type: string,\n fallback?: QUESTION_TYPE\n): QUESTION_TYPE | null {\n const upperType = type.toUpperCase();\n\n const typeMap: Record<string, QUESTION_TYPE> = {\n ALTERNATIVA: QUESTION_TYPE.ALTERNATIVA,\n DISSERTATIVA: QUESTION_TYPE.DISSERTATIVA,\n MULTIPLA_ESCOLHA: QUESTION_TYPE.MULTIPLA_ESCOLHA,\n VERDADEIRO_FALSO: QUESTION_TYPE.VERDADEIRO_FALSO,\n IMAGEM: QUESTION_TYPE.IMAGEM,\n LIGAR_PONTOS: QUESTION_TYPE.LIGAR_PONTOS,\n PREENCHER: QUESTION_TYPE.PREENCHER,\n };\n\n return typeMap[upperType] ?? fallback ?? null;\n}\n\n/**\n * Maps API question type string to QUESTION_TYPE enum with required fallback\n * Always returns a QUESTION_TYPE (never null)\n *\n * @param type - Question type string from API\n * @param fallback - Fallback QUESTION_TYPE if mapping fails (defaults to ALTERNATIVA)\n * @returns QUESTION_TYPE enum value\n */\nexport function mapQuestionTypeToEnumRequired(\n type: string,\n fallback: QUESTION_TYPE = QUESTION_TYPE.ALTERNATIVA\n): QUESTION_TYPE {\n return mapQuestionTypeToEnum(type, fallback) ?? fallback;\n}\n","/**\n * Activity Details Types\n * Types and helper functions for activity details components\n */\n\n/**\n * Student activity status enum\n */\nexport const STUDENT_ACTIVITY_STATUS = {\n CONCLUIDO: 'CONCLUIDO',\n AGUARDANDO_CORRECAO: 'AGUARDANDO_CORRECAO',\n AGUARDANDO_RESPOSTA: 'AGUARDANDO_RESPOSTA',\n NAO_ENTREGUE: 'NAO_ENTREGUE',\n} as const;\n\nexport type StudentActivityStatus =\n (typeof STUDENT_ACTIVITY_STATUS)[keyof typeof STUDENT_ACTIVITY_STATUS];\n\n/**\n * Student data interface\n */\nexport interface ActivityStudentData {\n studentId: string;\n studentName: string;\n answeredAt: string | null;\n timeSpent: number;\n score: number | null;\n status: StudentActivityStatus;\n}\n\n/**\n * Pagination interface\n */\nexport interface Pagination {\n total: number;\n page: number;\n limit: number;\n totalPages: number;\n hasNext?: boolean;\n hasPrev?: boolean;\n}\n\n/**\n * General statistics interface\n */\nexport interface GeneralStats {\n averageScore: number;\n completionPercentage: number;\n}\n\n/**\n * Question statistics interface\n */\nexport interface QuestionStats {\n mostCorrect: number[];\n mostIncorrect: number[];\n notAnswered: number[];\n}\n\n/**\n * Activity metadata interface\n */\nexport interface ActivityMetadata {\n id: string;\n title: string;\n startDate: string | null;\n finalDate: string | null;\n schoolName: string;\n year: string;\n subjectName: string;\n className: string;\n}\n\n/**\n * Activity details data interface\n */\nexport interface ActivityDetailsData {\n activity?: ActivityMetadata;\n students: ActivityStudentData[];\n pagination: Pagination;\n generalStats: GeneralStats;\n questionStats: QuestionStats;\n}\n\n/**\n * Activity details query params interface\n */\nexport interface ActivityDetailsQueryParams {\n page?: number;\n limit?: number;\n sortBy?: 'name' | 'score' | 'answeredAt';\n sortOrder?: 'asc' | 'desc';\n status?: StudentActivityStatus;\n}\n\n/**\n * Activity student table item interface\n */\nexport interface ActivityStudentTableItem extends Record<string, unknown> {\n id: string;\n studentId: string;\n studentName: string;\n status: StudentActivityStatus;\n answeredAt: string | null;\n timeSpent: number;\n score: number | null;\n}\n\n/**\n * Status badge configuration interface\n */\nexport interface StatusBadgeConfig {\n label: string;\n bgColor: string;\n textColor: string;\n}\n\n/**\n * Activity availability status enum\n * Used to determine if an activity is available based on start/end dates\n */\nexport const ACTIVITY_AVAILABILITY = {\n DISPONIVEL: 'DISPONIVEL',\n NAO_INICIADA: 'NAO_INICIADA',\n EXPIRADA: 'EXPIRADA',\n} as const;\n\nexport type ActivityAvailability =\n (typeof ACTIVITY_AVAIL