analytica-frontend-lib 1.5.21 → 1.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActivitiesHistory/index.js +7 -7
- package/dist/ActivitiesHistory/index.mjs +6 -6
- package/dist/ActivityCardQuestionBanks/index.js +7 -7
- package/dist/ActivityCardQuestionBanks/index.mjs +6 -6
- package/dist/ActivityCardQuestionPreview/index.js +7 -7
- package/dist/ActivityCardQuestionPreview/index.mjs +6 -6
- package/dist/ActivityCreate/index.js +7 -7
- package/dist/ActivityCreate/index.mjs +6 -6
- package/dist/ActivityDetails/index.js +7 -7
- package/dist/ActivityDetails/index.mjs +6 -6
- package/dist/ActivityFilters/index.js +7 -7
- package/dist/ActivityFilters/index.mjs +6 -6
- package/dist/ActivityPageLayout/index.js +7 -7
- package/dist/ActivityPageLayout/index.mjs +6 -6
- package/dist/ActivityPreview/index.js +7 -7
- package/dist/ActivityPreview/index.mjs +6 -6
- package/dist/AlertManager/index.js +7 -7
- package/dist/AlertManager/index.mjs +6 -6
- package/dist/CheckBoxGroup/index.js +7 -7
- package/dist/CheckBoxGroup/index.mjs +6 -6
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +7 -7
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +6 -6
- package/dist/ExamPageLayout/index.js +7 -7
- package/dist/ExamPageLayout/index.mjs +6 -6
- package/dist/PrintableUsersTable/index.js +7 -7
- package/dist/PrintableUsersTable/index.mjs +6 -6
- package/dist/RecommendedLessonCreate/index.js +7 -7
- package/dist/RecommendedLessonCreate/index.mjs +6 -6
- package/dist/RecommendedLessonsHistory/index.js +7 -7
- package/dist/RecommendedLessonsHistory/index.mjs +6 -6
- package/dist/SendActivityModal/SendActivityModal.js +7 -7
- package/dist/SendActivityModal/SendActivityModal.mjs +6 -6
- package/dist/SendActivityModal/index.js +7 -7
- package/dist/SendActivityModal/index.mjs +6 -6
- package/dist/SendLessonModal/index.js +7 -7
- package/dist/SendLessonModal/index.mjs +6 -6
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +7 -7
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +6 -6
- package/dist/SimulationsPage/index.js +7 -7
- package/dist/SimulationsPage/index.mjs +6 -6
- package/dist/TableProvider/index.d.ts.map +1 -1
- package/dist/TableProvider/index.js +7 -7
- package/dist/TableProvider/index.mjs +6 -6
- package/dist/TimeReport/index.d.ts +6 -0
- package/dist/TimeReport/index.d.ts.map +1 -1
- package/dist/TimeReport/index.js +2 -2
- package/dist/TimeReport/index.mjs +1 -1
- package/dist/TypeSelector/TypeSelector.types/index.js +3 -3
- package/dist/TypeSelector/TypeSelector.types/index.mjs +2 -2
- package/dist/TypeSelector/index.js +4 -4
- package/dist/TypeSelector/index.mjs +3 -3
- package/dist/UnifiedDraftModelPage/index.js +7 -7
- package/dist/UnifiedDraftModelPage/index.mjs +6 -6
- package/dist/UnifiedHistoryPage/index.js +7 -7
- package/dist/UnifiedHistoryPage/index.mjs +6 -6
- package/dist/{chunk-CTRJ2BZP.mjs → chunk-3SHJQQCA.mjs} +2 -2
- package/dist/{chunk-ZSLJ6ZRR.mjs → chunk-BMJTQFRE.mjs} +59 -11
- package/dist/chunk-BMJTQFRE.mjs.map +1 -0
- package/dist/{chunk-ZHMW7GGL.js → chunk-CG7GWF5E.js} +60 -12
- package/dist/chunk-CG7GWF5E.js.map +1 -0
- package/dist/{chunk-CJ7XFBTY.mjs → chunk-DJG7DLCI.mjs} +1 -1
- package/dist/chunk-DJG7DLCI.mjs.map +1 -0
- package/dist/{chunk-ACKFNDCN.js → chunk-E4AWPRUB.js} +11 -2
- package/dist/chunk-E4AWPRUB.js.map +1 -0
- package/dist/{chunk-64NVMQCE.js → chunk-HLCT4MJA.js} +3 -3
- package/dist/{chunk-64NVMQCE.js.map → chunk-HLCT4MJA.js.map} +1 -1
- package/dist/{chunk-RDQN3TL4.js → chunk-KYHVFLN4.js} +3 -3
- package/dist/{chunk-RDQN3TL4.js.map → chunk-KYHVFLN4.js.map} +1 -1
- package/dist/{chunk-FF664NEZ.mjs → chunk-N26JL24O.mjs} +2 -2
- package/dist/{chunk-WUMJ2OIE.mjs → chunk-NTEW5ZW4.mjs} +23 -6
- package/dist/chunk-NTEW5ZW4.mjs.map +1 -0
- package/dist/{chunk-5H627GRH.mjs → chunk-QD6QSDVH.mjs} +11 -2
- package/dist/chunk-QD6QSDVH.mjs.map +1 -0
- package/dist/{chunk-CQ6FNWAU.js → chunk-TTSTAOIZ.js} +1 -1
- package/dist/chunk-TTSTAOIZ.js.map +1 -0
- package/dist/{chunk-4TROUHWW.js → chunk-YFNVFELP.js} +192 -175
- package/dist/chunk-YFNVFELP.js.map +1 -0
- package/dist/hooks/useActivitiesHistory/index.d.ts +12 -0
- package/dist/hooks/useActivitiesHistory/index.d.ts.map +1 -1
- package/dist/hooks/useActivitiesHistory/index.js +4 -2
- package/dist/hooks/useActivitiesHistory/index.js.map +1 -1
- package/dist/hooks/useActivitiesHistory/index.mjs +3 -1
- package/dist/hooks/useActivitiesHistory.d.ts +12 -0
- package/dist/hooks/useActivitiesHistory.d.ts.map +1 -1
- package/dist/hooks/useActivityDrafts/index.js +1 -1
- package/dist/hooks/useActivityDrafts/index.mjs +1 -1
- package/dist/hooks/useActivityModels/index.js +1 -1
- package/dist/hooks/useActivityModels/index.mjs +1 -1
- package/dist/hooks/useAppContent/index.js +7 -7
- package/dist/hooks/useAppContent/index.mjs +6 -6
- package/dist/index.js +7 -7
- package/dist/index.mjs +6 -6
- package/dist/types/activitiesHistory/index.d.ts +8 -3
- package/dist/types/activitiesHistory/index.d.ts.map +1 -1
- package/dist/types/activitiesHistory/index.js +2 -2
- package/dist/types/activitiesHistory/index.mjs +1 -1
- package/dist/types/activitiesHistory.d.ts +8 -3
- package/dist/types/activitiesHistory.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4TROUHWW.js.map +0 -1
- package/dist/chunk-5H627GRH.mjs.map +0 -1
- package/dist/chunk-ACKFNDCN.js.map +0 -1
- package/dist/chunk-CJ7XFBTY.mjs.map +0 -1
- package/dist/chunk-CQ6FNWAU.js.map +0 -1
- package/dist/chunk-WUMJ2OIE.mjs.map +0 -1
- package/dist/chunk-ZHMW7GGL.js.map +0 -1
- package/dist/chunk-ZSLJ6ZRR.mjs.map +0 -1
- /package/dist/{chunk-CTRJ2BZP.mjs.map → chunk-3SHJQQCA.mjs.map} +0 -0
- /package/dist/{chunk-FF664NEZ.mjs.map → chunk-N26JL24O.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YFNVFELP.js","../src/components/CheckBoxGroup/CheckBoxGroup.tsx","../src/components/AlertManager/AlertsManager.tsx","../src/components/AlertManager/AlertSteps/MessageStep.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/shared/SendModalBase/utils/applyChainedAutoSelection.ts","../src/components/shared/SendModalBase/hooks/useDateTimeHandlers.ts","../src/components/shared/SendModalBase/hooks/useCategoryInitialization.ts","../src/components/shared/SendModalBase/hooks/useCategorySync.ts","../src/components/shared/SendModalBase/components/SendModalError.tsx","../src/components/shared/SendModalBase/components/RecipientStep.tsx","../src/components/DateTimeInput/DateTimeInput.tsx","../src/components/shared/SendModalBase/components/DeadlineStep.tsx","../src/components/shared/SendModalBase/components/SendModalFooter.tsx","../src/hooks/useActivityFiltersData.ts","../src/components/ActivityCreate/ActivityCreate.tsx","../src/hooks/useScreen.ts","../src/components/ActivityCreate/ActivityCreate.utils.ts","../src/components/ActivityCreate/components/ActivityCreateSkeleton.tsx","../src/components/ActivityCreate/components/ActivityCreateHeader.tsx","../src/components/ActivityListQuestions/ActivityListQuestions.tsx","../src/utils/questionFiltersConverter.ts","../src/assets/icons/Activities.tsx","../src/components/ActivityCreate/components/ActivityCreateContent.tsx","../src/components/RecommendedLessonCreate/RecommendedLessonCreate.tsx","../src/components/LessonFilters/LessonFilters.tsx","../src/utils/lessonFilters.ts","../src/components/ActivityFilters/components/SubjectsFilter.tsx","../src/components/ActivityFilters/components/KnowledgeStructureFilter.tsx","../src/components/ActivityFilters/components/FilterActions.tsx","../src/components/ActivityFilters/utils/useInitialFiltersLoader.ts","../src/components/ActivityFilters/utils/useKnowledgeStructureInitialFilters.ts","../src/components/LessonBank/LessonBank.tsx","../src/components/LessonBank/hooks/useLessonBank.ts","../src/assets/icons/subjects/Video.tsx","../src/components/shared/LessonWatchModal/LessonWatchModal.tsx","../src/components/shared/ToastNotification/ToastNotification.tsx","../src/components/shared/ToastNotification/useToastNotification.ts","../src/components/LessonPreview/LessonPreview.tsx","../src/components/LessonPreview/components/AddActivityOptionModal.tsx","../src/components/ChooseActivityModelModal/ChooseActivityModelModal.tsx","../src/components/ChooseActivityModelModal/components/ActivityModelsList.tsx","../src/components/shared/ModelsTabBase/createModelsTableColumnsBase.tsx","../src/components/ActivityCardQuestionPreview/ActivityCardQuestionPreview.tsx","../src/components/ChooseActivityModelModal/components/ActivityModelDetails.tsx","../src/store/lessonFiltersStore.ts","../src/components/RecommendedLessonCreate/RecommendedLessonCreate.utils.ts","../src/components/RecommendedLessonCreate/components/RecommendedLessonCreateSkeleton.tsx","../src/components/RecommendedLessonCreate/components/RecommendedLessonCreateHeader.tsx","../src/components/Table/ColumnFilterMenu.tsx","../src/components/Table/useColumnFilters.ts","../src/enums/FilterEnums.ts","../src/components/Filter/FilterModal.tsx","../src/components/SimulatedFilters/SimulatedFiltersModal.tsx","../src/components/SimulatedFilters/hooks.ts","../src/components/SimulatedFilters/StudentsFilterSection.tsx","../src/components/SimulatedSubjectMenu/SimulatedSubjectMenu.tsx","../src/components/SimulatedSubjectMenu/useSimulatedSubjects.ts","../src/components/SimulatedStudentRanking/SimulatedStudentRanking.tsx","../src/components/PerformanceDistributionChart/PerformanceDistributionChart.tsx","../src/components/SimulatedContentDetailsModal/SimulatedContentDetailsModal.tsx","../src/components/TableProvider/TableProvider.tsx","../src/components/SimulatedContentDetailsModal/useSimulatedContentDetails.ts","../src/components/EssayCompetencies/EssayCompetenciesTable.tsx","../src/components/EssayCompetencies/useEssayCompetenciesOverview.ts","../src/components/EssayCompetencies/EssayCompetenceDetailsModal.tsx","../src/components/EssayCompetencies/useEssayCompetenceDetails.ts","../src/components/EssayStudentDetailsModal/EssayStudentDetailsModal.tsx","../src/components/EssayStudentDetailsModal/useEssayStudentDetails.ts","../src/components/ActivityFilters/ActivityFilters.tsx","../src/store/questionFiltersStore.ts","../src/components/FillInBlanks/FillInBlanks.tsx","../src/utils/parseContent.ts","../src/components/ConnectDots/ConnectDots.tsx","../src/hooks/useAppContent.ts","../src/components/ActivityCardQuestionBanks/ActivityCardQuestionBanks.tsx","../src/components/ActivityPreview/ActivityPreview.tsx","../src/components/QuestionsPdfGenerator/QuestionsPdfGenerator.tsx","../src/components/HierarchicalCheckboxGroup/HierarchicalCheckboxGroup.tsx","../src/components/ActivityDetails/ActivityDetails.tsx","../src/hooks/useActivityDetails.ts","../src/components/SendActivityModal/SendActivityModal.tsx","../src/components/SaveActivityModelModal/SaveActivityModelModal.tsx","../src/components/SendLessonModal/SendLessonModal.tsx","../src/components/SendLessonModal/hooks/useSendLessonModal.ts","../src/components/SendLessonModal/validation.ts","../src/components/RecommendedLessonsHistory/RecommendedLessonsHistory.tsx","../src/components/RecommendedLessonsHistory/EditRecommendedLessonModal.tsx","../src/components/shared/SendModalBase/hooks/useEditModalForm.ts","../src/components/shared/SendModalBase/utils/splitDateTime.ts","../src/components/shared/ModelsTabBase/ModelsTabBase.tsx","../src/components/ActivitiesHistory/components/ErrorDisplay.tsx","../src/components/RecommendedLessonsHistory/config/modelsFiltersConfig.ts","../src/components/RecommendedLessonsHistory/utils/filterBuilders.ts","../src/components/RecommendedLessonsHistory/tabs/ModelsTab.tsx","../src/components/RecommendedLessonsHistory/config/draftsFiltersConfig.ts","../src/components/RecommendedLessonsHistory/tabs/DraftsTab.tsx","../src/hooks/useRecommendedLessonDetails.ts","../src/components/ActivitiesHistory/ActivitiesHistory.tsx","../src/components/ActivitiesHistory/tabs/HistoryTab.tsx","../src/components/ActivitiesHistory/config/historyTableColumns.tsx","../src/components/ActivitiesHistory/utils/renderTruncatedText.tsx","../src/components/ActivitiesHistory/utils/filterBuilders.ts","../src/components/ActivitiesHistory/utils/filterOptions.ts","../src/components/ActivitiesHistory/config/historyFiltersConfig.ts","../src/components/ActivitiesHistory/config/modelsFiltersConfig.ts","../src/components/ActivitiesHistory/tabs/ModelsTab.tsx","../src/components/ActivitiesHistory/tabs/DraftsTab.tsx","../src/components/BasePageLayout/BasePageLayout.tsx","../src/components/ActivityPageLayout/ActivityPageLayout.tsx","../src/components/UnifiedHistoryPage/UnifiedHistoryPage.tsx","../src/components/ExamPageLayout/ExamPageLayout.tsx","../src/components/ActivityPageLayout/activitiesTableConfig.tsx","../src/components/UnifiedHistoryPage/config.ts","../src/components/UnifiedHistoryPage/EditActivityModal.tsx","../src/components/UnifiedDraftModelPage/UnifiedDraftModelPage.tsx","../src/components/UnifiedDraftModelPage/config.ts","../src/hooks/useActivityDraftModelPage.ts","../src/components/ExamPageLayout/examDraftsModelsTableConfig.tsx","../src/utils/draftModelFilterHelpers.ts","../src/components/Chat/ChatLoading.tsx","../src/hooks/useForum.ts","../src/components/PeriodSelectorModal/PeriodSelectorModal.tsx","../src/components/PrintableUsersTable/PrintableUsersTable.tsx","../src/components/SimulatedPerformance/SimulatedPerformanceView.tsx","../src/components/SimulationsPage/SimulationsPage.tsx","../src/components/SimulationsPage/SimulationsDetailModal.tsx","../src/components/ExamPageLayout/GabaritoPreview.tsx","../src/components/ExamPageLayout/GabaritosBatchPreview.tsx","../src/components/ExamDetailsLayout/ExamDetailsHeader.tsx","../src/components/ExamDetailsLayout/ExamStatsCards.tsx","../src/components/ExamDetailsLayout/ExamStudentsTable.tsx","../src/components/ExamDetailsLayout/examStudentsTableConfig.tsx","../src/components/ExamDetailsLayout/ExamDetailsPage.tsx"],"names":["jsxs","jsx","useMemo","useEffect","isCurrentStepValid","canFinish","handleNext","getSubjectInfo","getStatusText","useRef","normalizeWithPositions","convertFiltersToBackendFormat","getSubjectName","generateTitle","getTypeFromUrl","getTypeFromUrlString","extractDraftFromResponse","FILTER_CATEGORY","FILTER_GROUP","buildEndpoint","TABLE_COLUMNS","CounterCard","initialState","correctOptionIds","error","WarningCircleIcon","validateDeadlineStep","MAX_STEPS","createTableColumns","mapSubjectNameToEnum","buildFiltersFromParams","getSubjectOptions","RecommendedClassPageTab","getSchoolOptions","ActivityTab","ExamTab","getActivityStatusBadgeAction","STEPPER_STEPS","PAGE_CONFIG","QRCode","formattedItemsMap","getBadgeText","isCategoryEnabled","handleAccordionValueChange"],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;AC7UA,8BAAqD;AD+UrD;AACA;AEhVA;AACE;AACA;A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AMJ,IAAA;AACE,sBAAA;AAAC,QAAA;AAAA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACE,YAAA;AACA,YAAA;AACF,UAAA;AACA,UAAA;AAEA,UAAA;AACG,YAAA;AACC,cAAA;AAAC,cAAA;AAAA,gBAAA;AACM,gBAAA;AACA,gBAAA;AACM,gBAAA;AAC2B,gBAAA;AAEW,gBAAA;AAErC,gBAAA;AAC6B,cAAA;AAC3C,YAAA;AAED,YAAA;AAAA,cAAA;AACC,cAAA;AACA,cAAA;AACA,cAAA;AACA,cAAA;AAEF,YAAA;AAAA,UAAA;AACF,QAAA;AACF,MAAA;AACC,MAAA;AAjCO,IAAA;AAoCd,EAAA;AAGM,EAAA;AAOA,IAAA;AACF,MAAA;AAOF,IAAA;AAEI,IAAA;AACF,MAAA;AAOF,IAAA;AAEO,IAAA;AAAmB,MAAA;AAE1B,IAAA;AACF,EAAA;AAGAvC,EAAAA;AACO,IAAA;AAEC,IAAA;AACD,IAAA;AAGC,IAAA;AAGD,IAAA;AAEH,MAAA;AACE,QAAA;AACE,MAAA;AACN,IAAA;AACE,EAAA;AAGF,EAAA;AAAC,IAAA;AAAA,IAAA;AACM,MAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AAEC,MAAA;AAAsC,IAAA;AACzC,EAAA;AAEJ;AD66lBY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YFNVFELP.js","sourcesContent":[null,"import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AccordionGroup,\n Badge,\n CardAccordation,\n CheckBox,\n cn,\n Text,\n Divider,\n Input,\n} from '../../';\nimport { MagnifyingGlassIcon } from '@phosphor-icons/react/dist/csr/MagnifyingGlass';\nimport {\n areSelectedIdsEqual,\n isCategoryEnabled as isCategoryEnabledHelper,\n getBadgeText as getBadgeTextHelper,\n handleAccordionValueChange as handleAccordionValueChangeHelper,\n calculateFormattedItemsForAutoSelection,\n} from './CheckBoxGroup.helpers';\n\nexport type Item = {\n id: string;\n name: string;\n [key: string]: unknown;\n};\n\nexport type CategoryConfig = {\n key: string;\n label: string;\n selectedIds?: string[];\n dependsOn?: string[];\n itens?: Item[];\n filteredBy?: { key: string; internalField: string }[];\n /** When true, renders a search input to filter this category's items by name */\n searchable?: boolean;\n};\n\nexport const CheckboxGroup = ({\n categories,\n onCategoriesChange,\n compactSingleItem = true,\n showDivider = true,\n showSingleItem = false,\n disableAutoSelection = false,\n}: {\n categories: CategoryConfig[];\n onCategoriesChange: (categories: CategoryConfig[]) => void;\n compactSingleItem?: boolean;\n showDivider?: boolean;\n showSingleItem?: boolean;\n disableAutoSelection?: boolean;\n}) => {\n const [openAccordion, setOpenAccordion] = useState<string>('');\n // Search query per category key (only used by categories with searchable: true)\n const [searchQueries, setSearchQueries] = useState<Record<string, string>>(\n {}\n );\n\n const handleSearchChange = (categoryKey: string, value: string) => {\n setSearchQueries((prev) => ({ ...prev, [categoryKey]: value }));\n };\n\n // Filters a category's formatted groups by its search query (case-insensitive).\n // Returns the groups unchanged when there is no active query.\n const filterGroupsBySearch = (\n categoryKey: string,\n groups: { groupLabel?: string; itens: Item[] }[]\n ) => {\n const query = searchQueries[categoryKey]?.trim().toLowerCase();\n if (!query) return groups;\n return groups.map((group) => ({\n ...group,\n itens: group.itens.filter((item) =>\n item.name.toLowerCase().includes(query)\n ),\n }));\n };\n\n // Refs to prevent infinite loops and track auto-selection state\n const onCategoriesChangeRef = useRef(onCategoriesChange);\n const previousCategoriesRef = useRef<CategoryConfig[]>(categories);\n // Track which categories have had auto-selection applied (by stringified selectedIds)\n const appliedAutoSelectionRef = useRef<string>('');\n\n // Update ref when onCategoriesChange changes\n useEffect(() => {\n onCategoriesChangeRef.current = onCategoriesChange;\n }, [onCategoriesChange]);\n\n // Use helper function for comparing selectedIds arrays\n\n // Auto-seleciona categorias com apenas um item (considerando itens filtrados)\n const categoriesWithAutoSelection = useMemo(() => {\n // Se auto-seleção está desabilitada, retorna as categorias sem modificação\n if (disableAutoSelection) {\n return categories;\n }\n\n return categories.map((category) => {\n // Get filtered/visible items for this category\n const filteredItems = calculateFormattedItemsForAutoSelection(\n category,\n categories\n );\n\n // Se tem apenas um item filtrado/visível e nenhum está selecionado, auto-seleciona\n if (\n filteredItems.length === 1 &&\n (!category.selectedIds || category.selectedIds.length === 0)\n ) {\n return {\n ...category,\n selectedIds: [filteredItems[0].id],\n };\n }\n return category;\n });\n }, [categories, disableAutoSelection]);\n\n // Aplica a auto-seleção se necessário\n // Note: onCategoriesChange should be memoized by the parent component to prevent re-renders\n useEffect(() => {\n // Check if categories have actually changed by comparing with previous reference\n const categoriesChanged = categories !== previousCategoriesRef.current;\n previousCategoriesRef.current = categories;\n\n // Check for auto-selection changes using efficient comparison\n const hasAutoSelectionChanges = categoriesWithAutoSelection.some(\n (cat, index) => {\n const originalCat = categories[index];\n return !areSelectedIdsEqual(cat.selectedIds, originalCat.selectedIds);\n }\n );\n\n if (!hasAutoSelectionChanges) {\n // No auto-selection needed, reset tracking if categories changed\n if (categoriesChanged) {\n appliedAutoSelectionRef.current = '';\n }\n return;\n }\n\n // Create a signature of the auto-selection to prevent duplicate notifications\n const autoSelectionSignature = JSON.stringify(\n categoriesWithAutoSelection.map((c) => c.selectedIds)\n );\n\n // Only notify if this exact auto-selection hasn't been applied yet\n if (appliedAutoSelectionRef.current !== autoSelectionSignature) {\n appliedAutoSelectionRef.current = autoSelectionSignature;\n onCategoriesChangeRef.current(categoriesWithAutoSelection);\n }\n }, [categoriesWithAutoSelection, categories]);\n\n const isCheckBoxIsSelected = (categoryKey: string, itemId: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n if (!category) return false;\n return category.selectedIds?.includes(itemId) || false;\n };\n\n const isMinimalOneCheckBoxIsSelected = (categoryKey: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n if (!category) return false;\n\n // Obtém apenas os itens visíveis (dependências + busca ativa)\n const formattedItems = getDisplayItems(categoryKey);\n const filteredItems = formattedItems.flatMap((group) => group.itens || []);\n const filteredItemIds = filteredItems.map((item) => item.id);\n\n // Verifica se pelo menos um item filtrado está selecionado\n return filteredItemIds.some((itemId) =>\n category.selectedIds?.includes(itemId)\n );\n };\n\n const areAllFilteredItemsSelected = (categoryKey: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n if (!category) return false;\n\n // Obtém apenas os itens visíveis (dependências + busca ativa)\n const formattedItems = getDisplayItems(categoryKey);\n const filteredItems = formattedItems.flatMap((group) => group.itens || []);\n\n // Se não há itens filtrados, retorna false\n if (filteredItems.length === 0) return false;\n\n const filteredItemIds = filteredItems.map((item) => item.id);\n\n // Verifica se TODOS os itens filtrados estão selecionados\n return filteredItemIds.every((itemId) =>\n category.selectedIds?.includes(itemId)\n );\n };\n\n // Helper function to create combination of two arrays\n const createCombinations = (\n acc: string[][],\n currentArray: string[]\n ): string[][] => {\n const combinations: string[][] = [];\n for (const existingCombo of acc) {\n for (const item of currentArray) {\n combinations.push([...existingCombo, item]);\n }\n }\n return combinations;\n };\n\n // Helper function to calculate cartesian product of arrays\n const cartesian = (arr: string[][]): string[][] => {\n return arr.reduce(createCombinations, [[]] as string[][]);\n };\n\n // Helper function to get selected IDs for filters\n const getSelectedIdsForFilters = (\n filters: { key: string; internalField: string; label?: string }[]\n ) => {\n return filters.map((f) => {\n const parentCat = categories.find((c) => c.key === f.key);\n if (!parentCat?.selectedIds?.length) {\n return [];\n }\n return parentCat.selectedIds;\n });\n };\n\n // Helper function to generate group label for single filter\n const generateSingleFilterLabel = (\n filter: { key: string; internalField: string },\n comboId: string\n ) => {\n const cat = categories.find((c) => c.key === filter.key);\n return cat?.itens?.find((i) => i.id === comboId)?.name || comboId;\n };\n\n // Helper function to generate group label for multiple filters\n const generateMultipleFiltersLabel = (\n filters: { key: string; internalField: string }[],\n comboIds: string[]\n ) => {\n const firstCat = categories.find((c) => c.key === filters[0].key);\n const firstVal =\n firstCat?.itens?.find((i) => i.id === comboIds[0])?.name || comboIds[0];\n\n const labelParts: string[] = [firstVal];\n\n for (let idx = 1; idx < filters.length; idx++) {\n const f = filters[idx];\n const cat = categories.find((c) => c.key === f.key);\n const val =\n cat?.itens?.find((i) => i.id === comboIds[idx])?.name || comboIds[idx];\n labelParts.push(`(${val})`);\n }\n return labelParts.join(' ');\n };\n\n // Helper function to process combination and add to grouped map\n const processCombination = (\n comboIds: string[],\n filters: { key: string; internalField: string; label?: string }[],\n category: CategoryConfig,\n groupedMap: Record<string, { groupLabel?: string; itens: Item[] }>\n ) => {\n const filteredItems = (category?.itens || []).filter((item) =>\n filters.every((f, idx) => item[f.internalField] === comboIds[idx])\n );\n\n if (filteredItems.length === 0) return;\n\n let groupLabel: string | undefined = undefined;\n\n if (filters.length === 1) {\n groupLabel = generateSingleFilterLabel(filters[0], comboIds[0]);\n } else if (filters.length > 1) {\n groupLabel = generateMultipleFiltersLabel(filters, comboIds);\n }\n\n const key = groupLabel || '';\n if (!groupedMap[key]) {\n groupedMap[key] = groupLabel ? { groupLabel, itens: [] } : { itens: [] };\n }\n groupedMap[key].itens.push(...filteredItems);\n };\n\n // Helper function to calculate formatted items for a category\n const calculateFormattedItems = (categoryKey: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n\n if (!category?.dependsOn || category.dependsOn.length === 0) {\n return [{ itens: category?.itens || [] }];\n }\n\n // Check if category is enabled based on dependencies (inline to avoid stale closure)\n const isEnabled = category.dependsOn.every((depKey) => {\n const depCat = categories.find((c) => c.key === depKey);\n return depCat?.selectedIds && depCat.selectedIds.length > 0;\n });\n\n // If category is disabled, return empty items array to hide all items\n if (!isEnabled) {\n return [{ itens: [] }];\n }\n\n const filters =\n (category.filteredBy as {\n key: string;\n internalField: string;\n label?: string;\n }[]) || [];\n\n if (filters.length === 0) {\n return [{ itens: category?.itens || [] }];\n }\n\n const selectedIdsArr = getSelectedIdsForFilters(filters);\n\n if (selectedIdsArr.some((arr) => arr.length === 0)) {\n return [{ itens: [] }];\n }\n\n const combinations = cartesian(selectedIdsArr);\n const groupedMap: Record<string, { groupLabel?: string; itens: Item[] }> =\n {};\n\n for (const comboIds of combinations) {\n processCombination(comboIds, filters, category, groupedMap);\n }\n\n const groupedItems = Object.values(groupedMap).filter(\n (g) => g.itens.length\n );\n\n return groupedItems.length ? groupedItems : [{ itens: [] }];\n };\n\n const formattedItemsMap = useMemo(() => {\n const formattedItemsMap: Record<\n string,\n { groupLabel?: string; itens: Item[] }[]\n > = {};\n\n for (const category of categories) {\n const formattedItems = calculateFormattedItems(category.key);\n formattedItemsMap[category.key] = formattedItems;\n }\n\n return formattedItemsMap;\n }, [categories]);\n\n const getFormattedItems = (categoryKey: string) => {\n return formattedItemsMap[categoryKey] || [{ itens: [] }];\n };\n\n // Items actually visible to the user: dependency-filtered items narrowed by\n // the active search query for searchable categories. Selection state, the\n // \"select all\" toggle and the badge all operate on these so they match the\n // rendered list.\n const getDisplayItems = (categoryKey: string) => {\n const formattedItems = getFormattedItems(categoryKey);\n const category = categories.find((c) => c.key === categoryKey);\n return category?.searchable\n ? filterGroupsBySearch(categoryKey, formattedItems)\n : formattedItems;\n };\n\n // Helper function to get badge text for category\n const getBadgeText = (category: CategoryConfig): string => {\n const formattedItems = getDisplayItems(category.key);\n return getBadgeTextHelper(category, formattedItems);\n };\n\n // Helper function to check if category is enabled\n const isCategoryEnabled = (category: CategoryConfig): boolean => {\n return isCategoryEnabledHelper(category, categories);\n };\n\n // Helper function to handle accordion value change\n const handleAccordionValueChange = (value: string | string[] | undefined) => {\n const newValue = handleAccordionValueChangeHelper(\n value,\n categories,\n isCategoryEnabled\n );\n if (newValue !== null) {\n setOpenAccordion(newValue);\n }\n };\n\n const getDependentCategories = (categoryKey: string): string[] => {\n return categories\n .filter((cat) => cat.dependsOn?.includes(categoryKey))\n .map((cat) => cat.key);\n };\n\n // Helper function to find items to remove from dependent category\n const findItemsToRemove = (\n depCategory: CategoryConfig,\n relevantFilter: { key: string; internalField: string },\n deselectedItemId: string\n ): string[] => {\n return (\n depCategory.itens\n ?.filter(\n (item) => item[relevantFilter.internalField] === deselectedItemId\n )\n .map((item) => item.id) || []\n );\n };\n\n // Helper function to process dependent category for deselection\n const processDependentCategory = (\n depCategoryKey: string,\n categoryKey: string,\n deselectedItemId: string,\n itemsToDeselect: Record<string, string[]>\n ) => {\n const depCategory = categories.find((c) => c.key === depCategoryKey);\n if (!depCategory?.filteredBy) return;\n\n const relevantFilter = depCategory.filteredBy.find(\n (f) => f.key === categoryKey\n );\n if (!relevantFilter) return;\n\n const itemsToRemove = findItemsToRemove(\n depCategory,\n relevantFilter,\n deselectedItemId\n );\n if (itemsToRemove.length > 0) {\n itemsToDeselect[depCategoryKey] = itemsToRemove;\n }\n };\n\n const getItemsToDeselect = (\n categoryKey: string,\n deselectedItemId: string\n ) => {\n const deselectedItem = categories\n .find((c) => c.key === categoryKey)\n ?.itens?.find((item) => item.id === deselectedItemId);\n if (!deselectedItem) return {};\n\n const itemsToDeselect: Record<string, string[]> = {};\n const dependentCategories = getDependentCategories(categoryKey);\n\n for (const depCategoryKey of dependentCategories) {\n processDependentCategory(\n depCategoryKey,\n categoryKey,\n deselectedItemId,\n itemsToDeselect\n );\n }\n\n return itemsToDeselect;\n };\n\n // Helper function to update category with new selected IDs\n const updateCategorySelectedIds = (\n updatedCategories: CategoryConfig[],\n depCategoryIndex: number,\n depCategory: CategoryConfig,\n itemIds: string[]\n ): CategoryConfig[] => {\n const newSelectedIds =\n depCategory.selectedIds?.filter((id) => !itemIds.includes(id)) || [];\n\n updatedCategories[depCategoryIndex] = {\n ...depCategory,\n selectedIds: newSelectedIds,\n };\n\n return updatedCategories;\n };\n\n // Helper function to apply recursive cascade deselection\n const applyRecursiveCascade = (\n depCategoryKey: string,\n itemIds: string[],\n updatedCategories: CategoryConfig[]\n ): CategoryConfig[] => {\n let result = updatedCategories;\n for (const itemId of itemIds) {\n result = applyCascadeDeselection(depCategoryKey, itemId, result);\n }\n return result;\n };\n\n const applyCascadeDeselection = (\n categoryKey: string,\n deselectedItemId: string,\n currentCategories: CategoryConfig[]\n ): CategoryConfig[] => {\n const itemsToDeselect = getItemsToDeselect(categoryKey, deselectedItemId);\n let updatedCategories = [...currentCategories];\n\n for (const [depCategoryKey, itemIds] of Object.entries(itemsToDeselect)) {\n const depCategoryIndex = updatedCategories.findIndex(\n (c) => c.key === depCategoryKey\n );\n\n if (depCategoryIndex !== -1) {\n const depCategory = updatedCategories[depCategoryIndex];\n updatedCategories = updateCategorySelectedIds(\n updatedCategories,\n depCategoryIndex,\n depCategory,\n itemIds\n );\n updatedCategories = applyRecursiveCascade(\n depCategoryKey,\n itemIds,\n updatedCategories\n );\n }\n }\n\n return updatedCategories;\n };\n\n const toggleAllInCategory = (categoryKey: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n if (!category) return;\n\n // Obtém apenas os itens visíveis (dependências + busca ativa)\n const formattedItems = getDisplayItems(categoryKey);\n const filteredItems = formattedItems.flatMap((group) => group.itens || []);\n const filteredItemIds = filteredItems.map((item) => item.id);\n\n // Verifica quantos itens filtrados estão selecionados\n const selectedFilteredCount = filteredItemIds.filter((itemId) =>\n category.selectedIds?.includes(itemId)\n ).length;\n\n // Se NENHUM item filtrado está selecionado OU pelo menos um está selecionado mas não todos,\n // então seleciona todos os itens filtrados\n // Se TODOS os itens filtrados estão selecionados, então deseleciona todos os filtrados\n const allFilteredSelected =\n selectedFilteredCount === filteredItemIds.length;\n\n const newSelection = allFilteredSelected\n ? category.selectedIds?.filter((id) => !filteredItemIds.includes(id)) ||\n []\n : [\n ...(category.selectedIds || []),\n ...filteredItemIds.filter(\n (id) => !category.selectedIds?.includes(id)\n ),\n ];\n\n let updatedCategories = categories.map((c) =>\n c.key === categoryKey ? { ...c, selectedIds: newSelection } : c\n );\n\n // Se está deselecionando, aplica cascata para os itens que foram deselecionados\n if (allFilteredSelected) {\n for (const itemId of filteredItemIds) {\n updatedCategories = applyCascadeDeselection(\n categoryKey,\n itemId,\n updatedCategories\n );\n }\n }\n\n onCategoriesChange(updatedCategories);\n };\n\n const toggleItem = (categoryKey: string, itemId: string) => {\n const category = categories.find((c) => c.key === categoryKey);\n if (!category) return;\n const isCurrentlySelected = category.selectedIds?.includes(itemId);\n const newSelection = isCurrentlySelected\n ? category.selectedIds?.filter((id) => id !== itemId)\n : [...(category.selectedIds || []), itemId];\n\n let updatedCategories = categories.map((c) =>\n c.key === categoryKey ? { ...c, selectedIds: newSelection } : c\n );\n\n // Se está deselecionando, aplica cascata\n if (isCurrentlySelected) {\n updatedCategories = applyCascadeDeselection(\n categoryKey,\n itemId,\n updatedCategories\n );\n }\n\n onCategoriesChange(updatedCategories);\n };\n\n // Helper component to render individual checkbox item\n const renderCheckboxItem = (item: Item, categoryKey: string) => {\n // Generate unique ID by combining category key and item id to avoid conflicts\n const uniqueId = `${categoryKey}-${item.id}`;\n\n return (\n <div key={item.id} className=\"flex items-center gap-3 px-2\">\n <CheckBox\n id={uniqueId}\n checked={isCheckBoxIsSelected(categoryKey, item.id)}\n onChange={() => toggleItem(categoryKey, item.id)}\n />\n <label\n htmlFor={uniqueId}\n className=\"text-sm text-text-950 cursor-pointer select-none\"\n >\n {item.name}\n </label>\n </div>\n );\n };\n\n // Helper component to render formatted group\n const renderFormattedGroup = (\n formattedGroup: { groupLabel?: string; itens: Item[] },\n idx: number,\n categoryKey: string\n ) => (\n <div\n key={formattedGroup.groupLabel || `group-${idx}`}\n className=\"flex flex-col gap-3\"\n >\n {'groupLabel' in formattedGroup && formattedGroup.groupLabel && (\n <Text size=\"sm\" className=\"mt-2\" weight=\"semibold\">\n {formattedGroup.groupLabel}\n </Text>\n )}\n {formattedGroup.itens?.map((item: Item) =>\n renderCheckboxItem(item, categoryKey)\n )}\n </div>\n );\n\n // Helper component to render accordion trigger\n const renderAccordionTrigger = (\n category: CategoryConfig,\n isEnabled: boolean\n ) => {\n const allSelected = areAllFilteredItemsSelected(category.key);\n const someSelected = isMinimalOneCheckBoxIsSelected(category.key);\n\n return (\n <div className=\"flex items-center justify-between w-full p-2\">\n <div className=\"flex items-center gap-3\">\n <CheckBox\n checked={allSelected}\n disabled={!isEnabled}\n indeterminate={someSelected && !allSelected}\n onChange={() => toggleAllInCategory(category.key)}\n />\n <Text\n size=\"sm\"\n weight=\"medium\"\n className={cn('text-text-800', !isEnabled && 'opacity-40')}\n >\n {category.label}\n </Text>\n </div>\n {(openAccordion === category.key || isEnabled) && (\n <Badge variant=\"solid\" action=\"info\">\n {getBadgeText(category)}\n </Badge>\n )}\n </div>\n );\n };\n\n // Helper component to render compact single item view\n const renderCompactSingleItem = (category: CategoryConfig) => {\n const formattedItems = getFormattedItems(category.key);\n const allItems = formattedItems.flatMap((group) => group.itens || []);\n\n if (allItems.length !== 1) {\n return null;\n }\n\n const singleItem = allItems[0];\n\n return (\n <div\n key={category.key}\n className=\"flex items-center justify-between w-full px-3 py-2\"\n >\n <Text size=\"sm\" weight=\"bold\" className=\"text-text-800\">\n {category.label}\n </Text>\n <Text size=\"sm\" className=\"text-text-950\">\n {singleItem.name}\n </Text>\n </div>\n );\n };\n\n // Helper component to render category accordion\n const renderCategoryAccordion = (category: CategoryConfig) => {\n // Check if category is enabled based on dependencies\n const isEnabled = isCategoryEnabled(category);\n const hasOnlyOneItem = category.itens?.length === 1;\n\n if (hasOnlyOneItem && !compactSingleItem && !showSingleItem) {\n return null;\n }\n\n const formattedItems = getFormattedItems(category.key);\n const allItems = formattedItems.flatMap((group) => group.itens || []);\n const hasOnlyOneAvailableItem = allItems.length === 1;\n\n // If compactSingleItem is enabled and there's only one available item, render compact version\n if (compactSingleItem && hasOnlyOneAvailableItem && isEnabled) {\n return (\n <div key={category.key}>\n {renderCompactSingleItem(category)}\n {showDivider && <Divider />}\n </div>\n );\n }\n\n const hasNoItems = formattedItems.every(\n (group) => !group.itens || group.itens.length === 0\n );\n\n // Items visible to the user (dependency filter + active search)\n const displayGroups = getDisplayItems(category.key);\n\n const hasActiveSearch =\n !!category.searchable && !!searchQueries[category.key]?.trim();\n\n const hasNoSearchResults =\n hasActiveSearch &&\n !hasNoItems &&\n displayGroups.every((group) => !group.itens || group.itens.length === 0);\n\n return (\n <div key={category.key}>\n <CardAccordation\n value={category.key}\n disabled={!isEnabled}\n className={cn(\n 'bg-transparent border-0',\n openAccordion === category.key && 'bg-background-50 border-none'\n )}\n trigger={renderAccordionTrigger(category, isEnabled)}\n >\n <div className=\"flex flex-col gap-3 pt-2\">\n {category.searchable && !hasNoItems && (\n <Input\n type=\"text\"\n size=\"small\"\n aria-label=\"Buscar\"\n value={searchQueries[category.key] ?? ''}\n onChange={(e) =>\n handleSearchChange(category.key, e.target.value)\n }\n placeholder=\"Buscar\"\n iconLeft={<MagnifyingGlassIcon size={18} />}\n />\n )}\n {renderAccordionContent(\n category,\n isEnabled,\n hasNoItems,\n hasNoSearchResults,\n displayGroups\n )}\n </div>\n </CardAccordation>\n {openAccordion !== category.key && showDivider && <Divider />}\n </div>\n );\n };\n\n // Renders the item list (or an appropriate empty state) inside an accordion\n const renderAccordionContent = (\n category: CategoryConfig,\n isEnabled: boolean,\n hasNoItems: boolean,\n hasNoSearchResults: boolean,\n displayGroups: { groupLabel?: string; itens: Item[] }[]\n ) => {\n if (hasNoItems && isEnabled) {\n return (\n <div className=\"px-2 py-4\">\n <Text size=\"sm\" className=\"text-text-500 text-center\">\n Sem dados\n </Text>\n </div>\n );\n }\n\n if (hasNoSearchResults) {\n return (\n <div className=\"px-2 py-4\">\n <Text size=\"sm\" className=\"text-text-500 text-center\">\n Nenhum resultado encontrado\n </Text>\n </div>\n );\n }\n\n return displayGroups.map((formattedGroup, idx) =>\n renderFormattedGroup(formattedGroup, idx, category.key)\n );\n };\n\n // Auto-collapse accordion when category becomes disabled\n useEffect(() => {\n if (!openAccordion) return;\n\n const category = categories.find((c) => c.key === openAccordion);\n if (!category) return;\n\n // Check if the open category is now disabled\n const isEnabled = isCategoryEnabled(category);\n\n // If category is disabled, close it\n if (!isEnabled) {\n // Use setTimeout to ensure this runs after any other state updates\n setTimeout(() => {\n setOpenAccordion('');\n }, 0);\n }\n }, [categories, openAccordion]);\n\n return (\n <AccordionGroup\n type=\"single\"\n collapsible\n value={openAccordion}\n onValueChange={handleAccordionValueChange}\n >\n {categories.map(renderCategoryAccordion)}\n </AccordionGroup>\n );\n};\n","import {\n useState,\n useEffect,\n ReactNode,\n useMemo,\n useCallback,\n useSyncExternalStore,\n useRef,\n} from 'react';\nimport { Button, Modal, Stepper } from '../..';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport { StepData } from '../Stepper/Stepper';\nimport { useAlertFormStore } from './useAlertForm';\nimport type { AlertsConfig, CategoryConfig } from '.';\nimport {\n MessageStep,\n RecipientsStep,\n DateStep,\n PreviewStep,\n} from './AlertSteps';\nimport {\n canFinish as canFinishValidation,\n isCurrentStepValid as isCurrentStepValidValidation,\n handleNext as handleNextValidation,\n} from './validation';\nimport { applyChainedAutoSelection } from '../shared/SendModalBase';\nimport { useDynamicStudentFetching } from '../../utils/useDynamicStudentFetching';\n\ninterface AlertsManagerProps {\n config: AlertsConfig;\n isOpen?: boolean;\n onClose?: () => void;\n /** URL da imagem após upload (prioritária - será exibida primeiro) */\n imageLink?: string | null;\n /** Imagem padrão a ser exibida quando não há imagem selecionada (deve ser URL string - o front deve converter File para URL se necessário) */\n defaultImage?: string | null;\n}\n\nconst StepWrapper = ({ children }: { children: ReactNode }) => (\n <div>{children}</div>\n);\n\nexport const AlertsManager = ({\n config,\n isOpen = false,\n onClose,\n imageLink,\n defaultImage,\n}: AlertsManagerProps) => {\n const [isModalOpen, setIsModalOpen] = useState(isOpen);\n const [currentStep, setCurrentStep] = useState(0);\n const [completedSteps, setCompletedSteps] = useState<number[]>([]);\n const [categories, setCategories] = useState(config.categories);\n\n // Subscribe to form store changes using useSyncExternalStore\n const formData = useSyncExternalStore(\n useAlertFormStore.subscribe,\n useAlertFormStore.getState\n );\n\n // Sincroniza com a prop isOpen\n useEffect(() => {\n setIsModalOpen(isOpen);\n }, [isOpen]);\n\n /**\n * Track if categories have been initialized for this modal session\n */\n const categoriesInitializedRef = useRef(false);\n\n const { labels, behavior, steps: customSteps } = config;\n\n /**\n * Handle categories change and fetch students dynamically if fetchStudentsByFilters is provided\n */\n const { handleCategoriesChange: baseHandleCategoriesChange } =\n useDynamicStudentFetching(setCategories, {\n fetchStudentsByFilters: behavior?.fetchStudentsByFilters,\n });\n\n const handleCategoriesChange = useCallback(\n async (updatedCategories: CategoryConfig[]) => {\n await baseHandleCategoriesChange(updatedCategories);\n },\n [baseHandleCategoriesChange]\n );\n\n /**\n * Initialize categories with auto-selection when modal opens\n */\n useEffect(() => {\n if (\n isModalOpen &&\n config.categories.length > 0 &&\n !categoriesInitializedRef.current\n ) {\n const autoSelectedCategories = applyChainedAutoSelection(\n config.categories\n );\n setCategories(autoSelectedCategories);\n\n // Trigger handleCategoriesChange to fetch students if needed\n // This is important when auto-selection happens (e.g., single school/series/class)\n // Note: previousSelectionsRef will be updated inside handleCategoriesChange\n void handleCategoriesChange(autoSelectedCategories);\n\n categoriesInitializedRef.current = true;\n }\n }, [\n isModalOpen,\n config.categories,\n applyChainedAutoSelection,\n handleCategoriesChange,\n ]);\n\n /**\n * Reset initialization flag when modal closes\n */\n useEffect(() => {\n if (!isModalOpen) {\n categoriesInitializedRef.current = false;\n }\n }, [isModalOpen]);\n\n // Steps padrão se não fornecidos\n const defaultSteps: StepData[] = [\n {\n id: '1',\n label: 'Mensagem',\n state: 'completed',\n },\n {\n id: '2',\n label: labels?.recipientsTitle || 'Destinatários',\n state: 'current',\n },\n {\n id: '3',\n label: labels?.dateLabel || 'Data de envio',\n state: 'pending',\n },\n {\n id: '4',\n label: labels?.previewTitle || 'Prévia',\n state: 'pending',\n },\n ];\n\n const steps = customSteps || defaultSteps;\n\n const handleCloseModal = () => {\n setIsModalOpen(false);\n onClose?.();\n };\n\n // Verifica se o step atual é válido\n const isCurrentStepValid = useCallback(() => {\n return isCurrentStepValidValidation(\n currentStep,\n formData,\n categories,\n customSteps\n );\n }, [currentStep, categories, customSteps, formData]);\n\n // Verifica se pode finalizar\n const canFinish = useCallback(() => {\n return canFinishValidation(formData, categories);\n }, [categories, formData]);\n\n const handleNext = useCallback(() => {\n const result = handleNextValidation({\n currentStep,\n steps,\n formData,\n categories,\n customSteps,\n completedSteps,\n setCompletedSteps,\n setCurrentStep,\n });\n\n if (!result.success && result.error) {\n alert(result.error);\n }\n }, [\n currentStep,\n steps,\n categories,\n customSteps,\n completedSteps,\n setCompletedSteps,\n setCurrentStep,\n formData,\n ]);\n\n const handlePrevious = useCallback(() => {\n if (currentStep > 0) {\n setCurrentStep(currentStep - 1);\n }\n }, [currentStep, setCurrentStep]);\n\n const handleFinish = async () => {\n if (!completedSteps.includes(currentStep)) {\n setCompletedSteps([...completedSteps, currentStep]);\n }\n\n const alertData = {\n title: formData.title,\n message: formData.message,\n image: formData.image,\n date: formData.date,\n time: formData.time,\n sendToday: formData.sendToday,\n sendCopyToEmail: formData.sendCopyToEmail,\n recipientCategories: Object.fromEntries(\n Object.entries(formData.recipientCategories).map(([key, c]) => [\n key,\n {\n selectedIds: c.selectedIds || [],\n allSelected: !!c.allSelected,\n },\n ])\n ),\n };\n\n try {\n if (behavior?.onSendAlert) {\n await behavior.onSendAlert(alertData);\n } else {\n console.log('Dados do formulário:', alertData);\n alert('Aviso enviado com sucesso!');\n }\n handleCloseModal();\n } catch (error) {\n console.error('Erro ao enviar aviso:', error);\n }\n };\n\n const dynamicSteps: StepData[] = (steps as StepData[]).map(\n (step, index: number) => {\n if (completedSteps.includes(index)) {\n return { ...step, state: 'completed' as const };\n }\n\n if (index === currentStep) {\n return { ...step, state: 'current' as const };\n }\n\n return { ...step, state: 'pending' as const };\n }\n );\n\n // Memoize step content to prevent re-renders and focus loss\n const currentStepContent = useMemo(() => {\n if (customSteps?.[currentStep]?.component) {\n const CustomComponent = customSteps[currentStep].component;\n return (\n <StepWrapper>\n <CustomComponent onNext={handleNext} onPrevious={handlePrevious} />\n </StepWrapper>\n );\n }\n\n switch (currentStep) {\n case 0:\n return (\n <StepWrapper>\n <MessageStep\n labels={labels}\n allowImageAttachment={behavior?.allowImageAttachment}\n />\n </StepWrapper>\n );\n case 1:\n return (\n <StepWrapper>\n <RecipientsStep\n categories={categories}\n labels={labels}\n onCategoriesChange={handleCategoriesChange}\n />\n </StepWrapper>\n );\n case 2:\n return (\n <StepWrapper>\n <DateStep\n labels={labels}\n allowScheduling={behavior?.allowScheduling}\n allowEmailCopy={behavior?.allowEmailCopy}\n />\n </StepWrapper>\n );\n case 3:\n return (\n <StepWrapper>\n <PreviewStep imageLink={imageLink} defaultImage={defaultImage} />\n </StepWrapper>\n );\n default:\n return null;\n }\n }, [\n currentStep,\n customSteps,\n categories,\n labels,\n behavior,\n handleNext,\n handlePrevious,\n imageLink,\n defaultImage,\n ]); // handleNext e handlePrevious agora estão nas dependências\n\n const isFirstStep = currentStep === 0;\n const isLastStep = currentStep === steps.length - 1;\n\n return (\n <Modal\n isOpen={isModalOpen}\n onClose={handleCloseModal}\n title={labels?.modalTitle || 'Enviar aviso'}\n size={'md'}\n contentClassName=\"p-0\"\n footer={\n <div className=\"flex gap-3 justify-end w-full\">\n <div className=\"flex gap-3\">\n <Button variant=\"link\" size=\"small\" onClick={handleCloseModal}>\n {labels?.cancelButton || 'Cancelar'}\n </Button>\n {!isFirstStep && (\n <Button\n variant=\"outline\"\n size=\"small\"\n iconLeft={<CaretLeftIcon />}\n onClick={handlePrevious}\n >\n {labels?.previousButton || 'Anterior'}\n </Button>\n )}\n {isLastStep ? (\n <Button\n variant=\"solid\"\n iconLeft={<PaperPlaneTiltIcon />}\n size=\"small\"\n onClick={handleFinish}\n disabled={!canFinish()}\n >\n {labels?.finishButton || 'Enviar Aviso'}\n </Button>\n ) : (\n <Button\n variant=\"solid\"\n iconRight={<CaretRightIcon />}\n size=\"small\"\n onClick={handleNext}\n disabled={!isCurrentStepValid()}\n >\n {labels?.nextButton || 'Próximo'}\n </Button>\n )}\n </div>\n </div>\n }\n >\n <div className=\"flex flex-col h-[calc(100vh-14rem)] max-h-[600px]\">\n {/* Stepper fixo no topo */}\n <div className=\"shrink-0 px-6 pt-4\">\n <Stepper\n steps={dynamicSteps}\n size=\"small\"\n showProgress\n responsive\n progressText={`Etapa ${currentStep + 1} de ${steps.length}`}\n />\n </div>\n\n {/* Área de conteúdo com scroll */}\n <div className=\"flex-1 overflow-y-auto px-6 pb-4\">\n {currentStepContent}\n </div>\n </div>\n </Modal>\n );\n};\n","import { useState } from 'react';\nimport { useShallow } from 'zustand/react/shallow';\nimport Input from '../../Input/Input';\nimport TextArea from '../../TextArea/TextArea';\nimport ImageUpload from '../../ImageUpload/ImageUpload';\nimport Text from '../../Text/Text';\nimport { useAlertFormStore } from '../useAlertForm';\nimport { LabelsConfig } from '../types';\n\ninterface MessageStepProps {\n labels?: LabelsConfig;\n allowImageAttachment?: boolean;\n}\n\nconst ACCEPTED_IMAGE_EXTENSIONS = '.jpg,.jpeg,.png,.gif,.webp,.svg';\nconst MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;\nconst MAX_IMAGE_SIZE_LABEL = '5MB';\n\nexport const MessageStep = ({\n labels,\n allowImageAttachment = true,\n}: MessageStepProps) => {\n const [imageError, setImageError] = useState<string | null>(null);\n const { title, message, image, setTitle, setMessage, setImage } =\n useAlertFormStore(\n useShallow((state) => ({\n title: state.title,\n message: state.message,\n image: state.image,\n setTitle: state.setTitle,\n setMessage: state.setMessage,\n setImage: state.setImage,\n }))\n );\n\n const handleFileSelect = (file: File) => {\n setImageError(null);\n setImage(file);\n };\n\n const handleRemoveFile = () => {\n setImageError(null);\n setImage(null);\n };\n\n const handleTypeError = () => {\n setImageError(\n 'Formato de imagem não suportado. Use jpg, jpeg, png, gif, webp ou svg.'\n );\n };\n\n const handleSizeError = () => {\n setImageError(\n `Imagem muito grande. O tamanho máximo permitido é ${MAX_IMAGE_SIZE_LABEL}.`\n );\n };\n\n const isImageFile = image instanceof File;\n return (\n <section className=\"flex flex-col gap-4\">\n <Input\n required\n label={labels?.titleLabel || 'Título'}\n placeholder={labels?.titlePlaceholder || 'Digite o título do aviso'}\n variant=\"rounded\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n\n <TextArea\n required\n label={labels?.messageLabel || 'Mensagem'}\n placeholder={labels?.messagePlaceholder || 'Digite a mensagem do aviso'}\n value={message}\n onChange={(e) => setMessage(e.target.value)}\n />\n\n {allowImageAttachment && (\n <div className=\"flex flex-col gap-2\">\n <ImageUpload\n selectedFile={isImageFile ? image : null}\n onFileSelect={handleFileSelect}\n onRemoveFile={handleRemoveFile}\n accept={ACCEPTED_IMAGE_EXTENSIONS}\n maxSize={MAX_IMAGE_SIZE_BYTES}\n onTypeError={handleTypeError}\n onSizeError={handleSizeError}\n />\n {imageError && (\n <Text size=\"sm\" className=\"text-error-600\" role=\"alert\">\n {imageError}\n </Text>\n )}\n </div>\n )}\n </section>\n );\n};\n","import { useEffect, useMemo } from 'react';\nimport { Text } from '../../..';\nimport { CheckboxGroup } from '../../CheckBoxGroup/CheckBoxGroup';\nimport { useAlertFormStore } from '../useAlertForm';\nimport type { CategoryConfig, LabelsConfig } from '../types';\n\ninterface RecipientsStepProps {\n categories: CategoryConfig[];\n labels?: LabelsConfig;\n onCategoriesChange?: (categories: CategoryConfig[]) => void;\n}\n\nexport const RecipientsStep = ({\n categories,\n labels,\n onCategoriesChange,\n}: RecipientsStepProps) => {\n const { recipientCategories, initializeCategory } = useAlertFormStore();\n\n // Initialize categories on mount\n useEffect(() => {\n for (const category of categories) {\n const existingCategory = recipientCategories[category.key];\n if (!existingCategory) {\n initializeCategory({\n key: category.key,\n label: category.label,\n availableItems: category.itens || [],\n selectedIds: category.selectedIds || [],\n allSelected: false,\n });\n }\n }\n }, [categories, recipientCategories, initializeCategory]);\n\n // Handle categories change from CheckboxGroup\n const handleCategoriesChange = (updatedCategories: CategoryConfig[]) => {\n // Update store for each category\n for (const category of updatedCategories) {\n const selectedIds = category.selectedIds || [];\n const allSelected = selectedIds.length === (category.itens?.length || 0);\n\n initializeCategory({\n key: category.key,\n label: category.label,\n availableItems: category.itens || [],\n selectedIds,\n allSelected,\n });\n }\n\n // Call parent callback if provided\n if (onCategoriesChange) {\n onCategoriesChange(updatedCategories);\n }\n };\n\n // Sync categories with store state\n const syncedCategories = useMemo(() => {\n return categories.map((category) => {\n const storeCategory = recipientCategories[category.key];\n if (storeCategory) {\n return {\n ...category,\n selectedIds: storeCategory.selectedIds,\n };\n }\n return category;\n });\n }, [categories, recipientCategories]);\n\n return (\n <section className=\"flex flex-col gap-4\">\n <Text size=\"lg\" weight=\"medium\" className=\"text-text-950\">\n {labels?.recipientsDescription || 'Para quem você vai enviar o aviso?'}\n </Text>\n\n <CheckboxGroup\n categories={syncedCategories}\n onCategoriesChange={handleCategoriesChange}\n />\n </section>\n );\n};\n","import { CheckBox, Text, DateTimeInput } from '../../..';\nimport { useAlertFormStore } from '../useAlertForm';\nimport { LabelsConfig } from '../types';\n\ninterface DateStepProps {\n labels?: LabelsConfig;\n allowScheduling?: boolean;\n allowEmailCopy?: boolean;\n}\n\n/**\n * DateStep component for AlertManager\n * Handles scheduling configuration for alerts\n */\nexport const DateStep = ({\n labels,\n allowScheduling = true,\n allowEmailCopy = true,\n}: DateStepProps) => {\n const date = useAlertFormStore((state) => state.date);\n const time = useAlertFormStore((state) => state.time);\n const sendToday = useAlertFormStore((state) => state.sendToday);\n const sendCopyToEmail = useAlertFormStore((state) => state.sendCopyToEmail);\n const setDate = useAlertFormStore((state) => state.setDate);\n const setTime = useAlertFormStore((state) => state.setTime);\n const setSendToday = useAlertFormStore((state) => state.setSendToday);\n const setSendCopyToEmail = useAlertFormStore(\n (state) => state.setSendCopyToEmail\n );\n\n return (\n <section className=\"flex flex-col gap-4\">\n <span className=\"flex flex-row items-center gap-6\">\n <Text\n weight=\"normal\"\n className={\n 'cursor-pointer select-none leading-[150%] flex items-center font-roboto'\n }\n >\n {labels?.sendTodayLabel || 'Enviar Hoje?'}\n </Text>\n <CheckBox\n label=\"Sim\"\n checked={sendToday}\n onChange={(e) => setSendToday(e.target.checked)}\n />\n </span>\n\n {allowScheduling && (\n <DateTimeInput\n label={labels?.dateLabel || 'Data de envio'}\n date={date}\n time={time}\n onDateChange={setDate}\n onTimeChange={setTime}\n disabled={sendToday}\n timeLabel={labels?.timeLabel || 'Hora de envio'}\n testId=\"alert-datetime\"\n />\n )}\n\n {allowEmailCopy && (\n <CheckBox\n label={labels?.sendCopyToEmailLabel || 'Enviar cópia para e-mail'}\n checked={sendCopyToEmail}\n onChange={(e) => setSendCopyToEmail(e.target.checked)}\n />\n )}\n </section>\n );\n};\n","import { useMemo, useEffect } from 'react';\nimport { Text } from '../../..';\nimport { useAlertFormStore } from '../useAlertForm';\n\ninterface PreviewStepProps {\n /** URL da imagem após upload (prioritária - será exibida primeiro) */\n imageLink?: string | null;\n /** Imagem padrão a ser exibida quando não há imagem selecionada (deve ser URL string - o front deve converter File para URL se necessário) */\n defaultImage?: string | null;\n}\n\nexport const PreviewStep = ({ imageLink, defaultImage }: PreviewStepProps) => {\n const title = useAlertFormStore((state) => state.title);\n const message = useAlertFormStore((state) => state.message);\n const image = useAlertFormStore((state) => state.image);\n\n // Criar URL blob apenas no cliente e apenas quando necessário, ou usar URL direta\n // Nota: blob URL é criado apenas para a imagem selecionada pelo usuário, não para defaultImage\n const imageUrl = useMemo(() => {\n if (globalThis.window === undefined) {\n return undefined;\n }\n\n if (image instanceof File) {\n return globalThis.window.URL.createObjectURL(image);\n }\n\n if (typeof image === 'string') {\n return image;\n }\n\n return undefined;\n }, [image]);\n\n // Limpar URL blob quando componente desmontar ou imagem mudar\n // Apenas limpar se for um blob URL (criado a partir de File), não URLs externas\n useEffect(() => {\n return () => {\n if (\n globalThis.window !== undefined &&\n imageUrl &&\n image instanceof File\n ) {\n URL.revokeObjectURL(imageUrl);\n }\n };\n }, [imageUrl, image]);\n\n // Prioridade: imageLink (URL após upload) > imageUrl (do store) > defaultImage\n // imageLink e defaultImage já devem ser URLs strings prontas - não criamos blob URL aqui\n const finalImageUrl = imageLink || imageUrl || defaultImage || undefined;\n\n return (\n <section className=\"flex flex-col gap-4\">\n <div className=\"bg-background-50 px-5 py-6 flex flex-col items-center gap-4 rounded-xl\">\n {/* Renderiza imagem apenas se houver URL válida */}\n {finalImageUrl && (\n <img src={finalImageUrl} alt={title || 'Imagem do alerta'} />\n )}\n <div className=\"flex flex-col items-center text-center gap-3\">\n <Text size=\"lg\" weight=\"semibold\">\n {title || 'Nenhum Título de Alerta'}\n </Text>\n <Text size=\"sm\" weight=\"normal\" className=\"text-text-500\">\n {message ||\n 'Aqui aparecerá a mensagem do alerta definido pelo usuário'}\n </Text>\n </div>\n </div>\n </section>\n );\n};\n","import type { AlertData, CategoryConfig, StepConfig } from './types';\n\nconst BASE_STEPS_COUNT = 4;\n\n/**\n * Parâmetros para a função handleNext\n */\ninterface HandleNextParams {\n currentStep: number;\n steps: StepConfig[];\n formData: AlertData;\n categories: CategoryConfig[];\n customSteps?: StepConfig[];\n completedSteps?: number[];\n setCompletedSteps?: (steps: number[]) => void;\n setCurrentStep?: (step: number) => void;\n}\n\n/**\n * Valida o step de mensagem\n */\nexport const validateMessageStep = (formData: AlertData): boolean | string => {\n if (!formData.title?.trim()) return 'Título é obrigatório';\n if (!formData.message?.trim()) return 'Mensagem é obrigatória';\n return true;\n};\n\n/**\n * Valida o step de destinatários\n */\nexport const validateRecipientsStep = (\n categories: CategoryConfig[]\n): boolean | string => {\n if (categories.length === 0) return 'Nenhuma categoria configurada';\n // Index access instead of Array.prototype.at() for old-browser support\n // (Chrome < 92 / Android 7), see domainUtils.ts / FRONTEND-LOGIN-WEB-20.\n const lastCategory = categories[categories.length - 1];\n if (!lastCategory?.selectedIds?.length) return 'Selecione destinatários';\n return true;\n};\n\n/**\n * Valida o step de data\n */\nexport const validateDateStep = (formData: AlertData): boolean | string => {\n if (!formData.sendToday && !formData.date) return 'Data é obrigatória';\n return true;\n};\n\n/**\n * Verifica se pode finalizar o formulário\n */\nexport const canFinish = (\n formData: AlertData,\n categories: CategoryConfig[]\n): boolean => {\n return (\n validateMessageStep(formData) === true &&\n validateRecipientsStep(categories) === true &&\n validateDateStep(formData) === true\n );\n};\n\n/**\n * Verifica se o step atual é válido\n */\nexport const isCurrentStepValid = (\n currentStep: number,\n formData: AlertData,\n categories: CategoryConfig[],\n customSteps?: StepConfig[]\n): boolean => {\n return (\n validateCurrentStep(currentStep, formData, categories, customSteps) === true\n );\n};\n\n/**\n * Valida o step atual\n */\nexport const validateCurrentStep = (\n currentStep: number,\n formData: AlertData,\n categories: CategoryConfig[],\n customSteps?: StepConfig[]\n): boolean | string => {\n switch (currentStep) {\n case 0:\n return validateMessageStep(formData);\n case 1:\n return validateRecipientsStep(categories);\n case 2:\n return validateDateStep(formData);\n case 3:\n return true;\n default: {\n const currentStepConfig = customSteps?.[currentStep - BASE_STEPS_COUNT]; // Ajusta para o índice correto dos custom steps\n return currentStepConfig?.validate\n ? currentStepConfig.validate(formData)\n : true;\n }\n }\n};\n\n/**\n * Avança para o próximo step\n */\nexport const advanceToNextStep = (\n currentStep: number,\n completedSteps: number[],\n setCompletedSteps?: (steps: number[]) => void,\n setCurrentStep?: (step: number) => void\n): void => {\n if (setCompletedSteps && !completedSteps.includes(currentStep)) {\n setCompletedSteps([...completedSteps, currentStep]);\n }\n\n if (setCurrentStep) {\n setCurrentStep(currentStep + 1);\n }\n};\n\n/**\n * Valida e avança para o próximo step\n */\nexport const handleNext = ({\n currentStep,\n steps,\n formData,\n categories,\n customSteps,\n completedSteps = [],\n setCompletedSteps,\n setCurrentStep,\n}: HandleNextParams): { success: boolean; error?: string } => {\n if (currentStep >= steps.length - 1) {\n return { success: false, error: 'Já está no último step' };\n }\n\n const validation = validateCurrentStep(\n currentStep,\n formData,\n categories,\n customSteps\n );\n\n if (validation !== true) {\n console.error('Validação falhou:', validation);\n const errorMessage =\n typeof validation === 'string'\n ? validation\n : 'Preencha todos os campos obrigatórios';\n\n return { success: false, error: errorMessage };\n }\n\n advanceToNextStep(\n currentStep,\n completedSteps,\n setCompletedSteps,\n setCurrentStep\n );\n return { success: true };\n};\n","import type { CategoryConfig } from '../../../CheckBoxGroup/CheckBoxGroup';\nimport { calculateFormattedItemsForAutoSelection } from '../../../CheckBoxGroup/CheckBoxGroup.helpers';\n\n/**\n * Apply the same \"single visible option\" auto-selection behavior that the CheckboxGroup\n * applies internally, but during initialization.\n *\n * This fixes the scenario where the first category (e.g. Escola) is rendered in compact\n * mode (single item) and therefore has no UI affordance to manually select it, leaving\n * dependent categories disabled.\n *\n * @param categories - Array of category configurations\n * @returns Array of category configurations with auto-selected items\n */\nexport function applyChainedAutoSelection(\n categories: CategoryConfig[]\n): CategoryConfig[] {\n let current = categories;\n let safety = 0;\n let changed = true;\n\n while (changed && safety < categories.length + 2) {\n safety += 1;\n changed = false;\n\n const next = current.map((category) => {\n const filteredItems = calculateFormattedItemsForAutoSelection(\n category,\n current\n );\n\n const hasNoSelection =\n !category.selectedIds || category.selectedIds.length === 0;\n\n if (filteredItems.length === 1 && hasNoSelection) {\n changed = true;\n return { ...category, selectedIds: [filteredItems[0].id] };\n }\n\n return category;\n });\n\n current = next;\n }\n\n return current;\n}\n","import { useCallback } from 'react';\nimport type { DateTimeChangeHandler } from '../types';\n\n/**\n * Props for useDateTimeHandlers hook\n */\nexport interface UseDateTimeHandlersProps<T> {\n /** Function to set form data */\n setFormData: (data: Partial<T>) => void;\n}\n\n/**\n * Return type for useDateTimeHandlers hook\n */\nexport interface UseDateTimeHandlersReturn {\n /** Handler for start date changes */\n handleStartDateChange: DateTimeChangeHandler;\n /** Handler for start time changes */\n handleStartTimeChange: DateTimeChangeHandler;\n /** Handler for final date changes */\n handleFinalDateChange: DateTimeChangeHandler;\n /** Handler for final time changes */\n handleFinalTimeChange: DateTimeChangeHandler;\n}\n\n/**\n * Hook that provides memoized date/time change handlers\n * Eliminates code duplication between SendLessonModal and SendActivityModal\n * @param props - Hook properties\n * @returns Object with date/time change handlers\n */\nexport function useDateTimeHandlers<\n T extends {\n startDate?: string;\n startTime?: string;\n finalDate?: string;\n finalTime?: string;\n },\n>({ setFormData }: UseDateTimeHandlersProps<T>): UseDateTimeHandlersReturn {\n const handleStartDateChange = useCallback(\n (value: string) => {\n setFormData({ startDate: value } as Partial<T>);\n },\n [setFormData]\n );\n\n const handleStartTimeChange = useCallback(\n (value: string) => {\n setFormData({ startTime: value } as Partial<T>);\n },\n [setFormData]\n );\n\n const handleFinalDateChange = useCallback(\n (value: string) => {\n setFormData({ finalDate: value } as Partial<T>);\n },\n [setFormData]\n );\n\n const handleFinalTimeChange = useCallback(\n (value: string) => {\n setFormData({ finalTime: value } as Partial<T>);\n },\n [setFormData]\n );\n\n return {\n handleStartDateChange,\n handleStartTimeChange,\n handleFinalDateChange,\n handleFinalTimeChange,\n };\n}\n","import { useEffect, useRef } from 'react';\nimport type { CategoryConfig } from '../../../CheckBoxGroup/CheckBoxGroup';\nimport { applyChainedAutoSelection } from '../utils/applyChainedAutoSelection';\n\nexport interface UseCategoryInitializationProps {\n isOpen: boolean;\n initialCategories: CategoryConfig[];\n setCategories: (categories: CategoryConfig[]) => void;\n onCategoriesChange?: (categories: CategoryConfig[]) => void;\n}\n\n/**\n * Hook to handle category initialization with auto-selection\n * when modal opens.\n *\n * This hook:\n * - Applies chained auto-selection when modal opens\n * - Triggers onCategoriesChange callback after auto-selection\n * - Resets initialization flag when modal closes\n *\n * @returns Object with categoriesInitialized ref for external access\n */\nexport function useCategoryInitialization({\n isOpen,\n initialCategories,\n setCategories,\n onCategoriesChange,\n}: UseCategoryInitializationProps) {\n const categoriesInitialized = useRef(false);\n\n /**\n * Initialize categories when modal opens\n */\n useEffect(() => {\n if (\n isOpen &&\n initialCategories.length > 0 &&\n !categoriesInitialized.current\n ) {\n const autoSelectedCategories =\n applyChainedAutoSelection(initialCategories);\n setCategories(autoSelectedCategories);\n // Trigger onCategoriesChange to allow parent to fetch students if needed\n // This is important when auto-selection happens (e.g., single school/series/class)\n if (onCategoriesChange) {\n onCategoriesChange(autoSelectedCategories);\n }\n categoriesInitialized.current = true;\n }\n }, [isOpen, initialCategories, setCategories, onCategoriesChange]);\n\n /**\n * Reset initialization flag when modal closes\n */\n useEffect(() => {\n if (!isOpen) {\n categoriesInitialized.current = false;\n }\n }, [isOpen]);\n\n return {\n categoriesInitializedRef: categoriesInitialized,\n };\n}\n","import { RefObject, useEffect } from 'react';\nimport type { CategoryConfig } from '../../../CheckBoxGroup/CheckBoxGroup';\n\nexport interface UseCategorySyncProps {\n isOpen: boolean;\n initialCategories: CategoryConfig[];\n storeCategories: CategoryConfig[];\n setCategories: (categories: CategoryConfig[]) => void;\n categoriesInitializedRef: RefObject<boolean>;\n}\n\n/** Categories whose itens are loaded dynamically (async) by the parent. */\nconst DYNAMIC_KEYS = ['turma', 'students'] as const;\n\n/** Signature of the dynamically-loaded categories' item ids, to detect changes\n * the store hasn't picked up yet (robust to same-length, different-content). */\nfunction dynamicItemsSignature(categories: CategoryConfig[]): string {\n return DYNAMIC_KEYS.map((key) => {\n const category = categories.find((c) => c.key === key);\n const ids = (category?.itens ?? []).map((item) => item.id).join(',');\n return `${key}:${ids}`;\n }).join('|');\n}\n\n/**\n * Hook to sync categories from parent when they change (e.g., after fetching\n * turmas or alunos). This ensures the store is updated when the parent updates\n * the dynamically-loaded categories.\n */\nexport function useCategorySync({\n isOpen,\n initialCategories,\n storeCategories,\n setCategories,\n categoriesInitializedRef,\n}: UseCategorySyncProps) {\n useEffect(() => {\n if (\n isOpen &&\n initialCategories.length > 0 &&\n categoriesInitializedRef.current\n ) {\n // Sync only when the dynamic categories (turma/students) actually differ\n // from the store, comparing item ids so same-length content changes count.\n if (\n dynamicItemsSignature(initialCategories) !==\n dynamicItemsSignature(storeCategories)\n ) {\n setCategories(initialCategories);\n }\n }\n }, [\n isOpen,\n initialCategories,\n storeCategories,\n setCategories,\n categoriesInitializedRef,\n ]);\n}\n","import React from 'react';\nimport { WarningCircleIcon } from '@phosphor-icons/react/dist/csr/WarningCircle';\nimport Text from '../../../Text/Text';\n\n/**\n * Props for SendModalError component\n */\nexport interface SendModalErrorProps {\n /** Error message to display */\n error?: string;\n /** Optional test ID */\n testId?: string;\n /** Optional className for additional styling */\n className?: string;\n}\n\n/**\n * Error display component for SendModal validation errors\n * Renders a text message with warning icon when error is present\n */\nexport const SendModalError: React.FC<SendModalErrorProps> = ({\n error,\n testId,\n className,\n}) => {\n if (!error) return null;\n\n return (\n <Text\n as=\"p\"\n size=\"sm\"\n color=\"text-error-600\"\n className={`flex items-center gap-1 mt-1 ${className || ''}`}\n data-testid={testId}\n aria-live=\"polite\"\n >\n <WarningCircleIcon size={16} />\n {error}\n </Text>\n );\n};\n\nexport default SendModalError;\n","import { useMemo, type FC } from 'react';\nimport { cn } from '../../../../utils/utils';\nimport Text from '../../../Text/Text';\nimport { CheckboxGroup } from '../../../CheckBoxGroup/CheckBoxGroup';\nimport { SendModalError } from './SendModalError';\nimport type { CategoryConfig, CategoriesChangeHandler } from '../types';\n\n/**\n * Props for RecipientStep component\n */\nexport interface RecipientStepProps {\n /** Categories to display for selection */\n categories: CategoryConfig[];\n /** Handler for categories change */\n onCategoriesChange: CategoriesChangeHandler;\n /** Entity name with article for the question text (e.g., \"a aula\", \"a atividade\") */\n entityNameWithArticle: string;\n /** Error message for students validation */\n studentsError?: string;\n /** Optional test ID prefix */\n testIdPrefix?: string;\n}\n\n/**\n * Recipient selection step component for SendModal\n * Displays a CheckboxGroup for selecting recipients (students/classes)\n */\nexport const RecipientStep: FC<RecipientStepProps> = ({\n categories,\n onCategoriesChange,\n entityNameWithArticle,\n studentsError,\n testIdPrefix,\n}) => {\n // Enable the name search input on the students list (typically large),\n // without touching the shorter categories (Escola/Série/Turma).\n const categoriesWithSearch = useMemo(\n () =>\n categories.map((category) =>\n category.key === 'students'\n ? { ...category, searchable: true }\n : category\n ),\n [categories]\n );\n\n return (\n <div\n className=\"flex flex-col gap-4 min-h-0 flex-1\"\n data-testid={testIdPrefix ? `${testIdPrefix}-recipient-step` : undefined}\n >\n <Text\n size=\"sm\"\n weight=\"medium\"\n color=\"text-text-700\"\n className=\"flex-shrink-0\"\n >\n Para quem você vai enviar {entityNameWithArticle}?\n </Text>\n\n <div\n className={cn(\n 'flex-1 min-h-0 overflow-y-auto',\n 'scrollbar-thin scrollbar-thumb-border-300 scrollbar-track-transparent'\n )}\n data-testid={\n testIdPrefix ? `${testIdPrefix}-scroll-container` : 'scroll-container'\n }\n >\n <CheckboxGroup\n categories={categoriesWithSearch}\n onCategoriesChange={onCategoriesChange}\n compactSingleItem={true}\n showDivider={true}\n />\n </div>\n\n <SendModalError\n error={studentsError}\n testId={testIdPrefix ? `${testIdPrefix}-students-error` : undefined}\n className=\"flex-shrink-0\"\n />\n </div>\n );\n};\n\nexport default RecipientStep;\n","import { ChangeEvent, useRef, useState } from 'react';\nimport { CalendarBlankIcon } from '@phosphor-icons/react/dist/csr/CalendarBlank';\nimport Input from '../Input/Input';\nimport Calendar from '../Calendar/Calendar';\nimport DropdownMenu, {\n DropdownMenuTrigger,\n DropdownMenuContent,\n} from '../DropdownMenu/DropdownMenu';\n\nexport interface DateTimeInputProps {\n /** Label for the input field */\n label: string;\n /** Date value in YYYY-MM-DD format */\n date: string;\n /** Time value in HH:MM format */\n time: string;\n /** Callback when date changes */\n onDateChange: (date: string) => void;\n /** Callback when time changes */\n onTimeChange: (time: string) => void;\n /** Error message to display */\n errorMessage?: string;\n /** Whether the input is disabled */\n disabled?: boolean;\n /** Default time when not set */\n defaultTime?: string;\n /** data-testid for testing */\n testId?: string;\n /** Label for time input */\n timeLabel?: string;\n /** Additional className for container */\n className?: string;\n}\n\n/**\n * Format Date object to YYYY-MM-DD string\n */\nconst formatDateToInput = (dateObj: Date): string => {\n const year = dateObj.getFullYear();\n const month = String(dateObj.getMonth() + 1).padStart(2, '0');\n const day = String(dateObj.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n};\n\n/**\n * DateTimeInput component that combines date and time selection\n * with a dropdown calendar picker\n */\nconst DateTimeInput = ({\n label,\n date,\n time,\n onDateChange,\n onTimeChange,\n errorMessage,\n disabled = false,\n defaultTime = '00:00',\n testId,\n timeLabel = 'Hora',\n className,\n}: DateTimeInputProps) => {\n const [isCalendarOpen, setIsCalendarOpen] = useState(false);\n const [selectedDate, setSelectedDate] = useState<Date | undefined>(\n date ? new Date(`${date}T12:00:00`) : undefined\n );\n const triggerRef = useRef<HTMLButtonElement>(null);\n\n /**\n * Handle date selection from calendar\n */\n const handleDateSelect = (dateObj: Date) => {\n setSelectedDate(dateObj);\n onDateChange(formatDateToInput(dateObj));\n\n if (!time) {\n onTimeChange(defaultTime);\n }\n };\n\n /**\n * Handle manual date input change\n */\n const handleDateInputChange = (e: ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value;\n\n if (value.includes('T')) {\n const [datePart, timePart] = value.split('T');\n onDateChange(datePart);\n if (timePart) {\n onTimeChange(timePart);\n }\n } else {\n onDateChange(value);\n }\n\n if (value) {\n const dateValue = value.includes('T') ? value : `${value}T12:00:00`;\n const dateObj = new Date(dateValue);\n if (!Number.isNaN(dateObj.getTime())) {\n setSelectedDate(dateObj);\n }\n }\n };\n\n /**\n * Handle time input change\n */\n const handleTimeChange = (e: ChangeEvent<HTMLInputElement>) => {\n onTimeChange(e.target.value);\n };\n\n const inputValue = date ? `${date}T${time || defaultTime}` : '';\n\n return (\n <DropdownMenu\n open={!disabled && isCalendarOpen}\n onOpenChange={(open) => {\n if (!disabled) {\n setIsCalendarOpen(open);\n }\n }}\n >\n <DropdownMenuTrigger\n className={className}\n ref={triggerRef}\n disabled={disabled}\n >\n <Input\n label={label}\n type=\"datetime-local\"\n placeholder=\"00/00/0000\"\n value={inputValue}\n onChange={handleDateInputChange}\n variant=\"rounded\"\n errorMessage={errorMessage}\n disabled={disabled}\n data-testid={testId ? `${testId}-input` : undefined}\n iconRight={<CalendarBlankIcon size={14} />}\n className=\"[&::-webkit-calendar-picker-indicator]:hidden\"\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n className=\"p-0 z-[100]\"\n portal\n triggerRef={triggerRef}\n >\n <Calendar\n variant=\"selection\"\n selectedDate={selectedDate}\n onDateSelect={handleDateSelect}\n showActivities={false}\n />\n <div className=\"p-3 border-t border-border-200\">\n <Input\n label={timeLabel}\n type=\"time\"\n value={time || defaultTime}\n onChange={handleTimeChange}\n variant=\"rounded\"\n data-testid={testId ? `${testId}-time` : undefined}\n />\n </div>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n};\n\nexport default DateTimeInput;\n","import type { FC, ReactNode } from 'react';\nimport DateTimeInput from '../../../DateTimeInput/DateTimeInput';\nimport type { DateTimeChangeHandler, BaseDateTimeErrors } from '../types';\n\n/**\n * Props for DeadlineStep component\n */\nexport interface DeadlineStepProps {\n /** Start date value */\n startDate: string;\n /** Start time value */\n startTime: string;\n /** Final date value */\n finalDate: string;\n /** Final time value */\n finalTime: string;\n /** Handler for start date changes */\n onStartDateChange: DateTimeChangeHandler;\n /** Handler for start time changes */\n onStartTimeChange: DateTimeChangeHandler;\n /** Handler for final date changes */\n onFinalDateChange: DateTimeChangeHandler;\n /** Handler for final time changes */\n onFinalTimeChange: DateTimeChangeHandler;\n /** Validation errors */\n errors: BaseDateTimeErrors;\n /** Optional test ID prefix */\n testIdPrefix?: string;\n /** Optional additional content to render after date/time inputs */\n children?: ReactNode;\n}\n\n/**\n * Deadline configuration step component for SendModal\n * Displays date/time inputs for start and end dates\n * Can render additional content via children prop (e.g., retry option)\n */\nexport const DeadlineStep: FC<DeadlineStepProps> = ({\n startDate,\n startTime,\n finalDate,\n finalTime,\n onStartDateChange,\n onStartTimeChange,\n onFinalDateChange,\n onFinalTimeChange,\n errors,\n testIdPrefix,\n children,\n}) => {\n return (\n <div\n className=\"flex flex-col gap-4 sm:gap-6 pt-6\"\n data-testid={testIdPrefix ? `${testIdPrefix}-deadline-step` : undefined}\n >\n {/* Date/Time Row - Side by Side */}\n <div className=\"flex flex-col gap-2 sm:grid sm:grid-cols-2\">\n <DateTimeInput\n label=\"Iniciar em*\"\n date={startDate}\n time={startTime}\n onDateChange={onStartDateChange}\n onTimeChange={onStartTimeChange}\n errorMessage={\n [errors.startDate, errors.startTime].filter(Boolean).join(' ') ||\n undefined\n }\n defaultTime=\"00:00\"\n testId={\n testIdPrefix ? `${testIdPrefix}-start-datetime` : 'start-datetime'\n }\n className=\"w-full\"\n />\n\n <DateTimeInput\n label=\"Finalizar até*\"\n date={finalDate}\n time={finalTime}\n onDateChange={onFinalDateChange}\n onTimeChange={onFinalTimeChange}\n errorMessage={\n [errors.finalDate, errors.finalTime].filter(Boolean).join(' ') ||\n undefined\n }\n defaultTime=\"23:59\"\n testId={\n testIdPrefix ? `${testIdPrefix}-final-datetime` : 'final-datetime'\n }\n className=\"w-full\"\n />\n </div>\n\n {/* Additional content (e.g., retry option for activities) */}\n {children}\n </div>\n );\n};\n\nexport default DeadlineStep;\n","import React from 'react';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { ArrowRightIcon } from '@phosphor-icons/react/dist/csr/ArrowRight';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport Button from '../../../Button/Button';\n\n/**\n * Props for SendModalFooter component\n */\nexport interface SendModalFooterProps {\n /** Current step number */\n currentStep: number;\n /** Maximum number of steps */\n maxSteps: number;\n /** Whether the submit button is in loading state */\n isLoading: boolean;\n /** Handler for cancel button */\n onCancel: () => void;\n /** Handler for previous step button */\n onPreviousStep: () => void;\n /** Handler for next step button */\n onNextStep: () => void;\n /** Handler for submit button */\n onSubmit: () => void;\n /** Entity name for submit button (e.g., \"aula\", \"atividade\") */\n entityName: string;\n /** Optional test ID prefix */\n testIdPrefix?: string;\n}\n\n/**\n * Footer component for SendModal with navigation and submit buttons\n * Displays Cancel, Previous, Next/Submit buttons based on current step\n */\nexport const SendModalFooter: React.FC<SendModalFooterProps> = ({\n currentStep,\n maxSteps,\n isLoading,\n onCancel,\n onPreviousStep,\n onNextStep,\n onSubmit,\n entityName,\n testIdPrefix,\n}) => {\n const isLastStep = currentStep >= maxSteps;\n const showPreviousButton = currentStep > 1;\n\n return (\n <div\n className=\"flex flex-col-reverse sm:flex-row items-center justify-between gap-3 w-full\"\n data-testid={testIdPrefix ? `${testIdPrefix}-footer` : undefined}\n >\n <Button\n variant=\"link\"\n action=\"primary\"\n onClick={onCancel}\n className=\"w-full sm:w-auto\"\n data-testid={testIdPrefix ? `${testIdPrefix}-cancel-button` : undefined}\n >\n Cancelar\n </Button>\n\n <div className=\"flex flex-col-reverse sm:flex-row items-center gap-2 sm:gap-3 w-full sm:w-auto\">\n {showPreviousButton && (\n <Button\n variant=\"outline\"\n action=\"primary\"\n onClick={onPreviousStep}\n iconLeft={<CaretLeftIcon size={16} />}\n className=\"w-full sm:w-auto\"\n data-testid={\n testIdPrefix ? `${testIdPrefix}-previous-button` : undefined\n }\n >\n Anterior\n </Button>\n )}\n\n {isLastStep ? (\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={onSubmit}\n disabled={isLoading}\n iconLeft={<PaperPlaneTiltIcon size={16} />}\n className=\"w-full sm:w-auto\"\n data-testid={\n testIdPrefix ? `${testIdPrefix}-submit-button` : undefined\n }\n >\n {isLoading ? 'Enviando...' : `Enviar ${entityName}`}\n </Button>\n ) : (\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={onNextStep}\n iconRight={<ArrowRightIcon size={16} />}\n className=\"w-full sm:w-auto\"\n data-testid={\n testIdPrefix ? `${testIdPrefix}-next-button` : undefined\n }\n >\n Próximo\n </Button>\n )}\n </div>\n </div>\n );\n};\n\nexport default SendModalFooter;\n","import { useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport type { BaseApiClient } from '../types/api';\nimport type { CategoryConfig } from '../components/CheckBoxGroup/CheckBoxGroup';\nimport type {\n KnowledgeItem,\n KnowledgeStructureState,\n Bank,\n BankYear,\n KnowledgeArea,\n} from '../types/activityFilters';\nimport { QUESTION_TYPE } from '../components/Quiz/useQuizStore';\nimport { mapQuestionTypeToEnum } from '../utils/questionTypeUtils';\n\n// ============================================================================\n// API Response Types\n// ============================================================================\n\n/**\n * API response format for knowledge endpoints\n */\ninterface KnowledgeApiResponse {\n message: string;\n data: Array<{ id: string; name: string }>;\n}\n\n/**\n * API response for vestibular banks endpoint\n */\ninterface VestibularBankItem {\n questionBankName: string;\n questionBankYearId: string;\n year: string;\n questionsCount: number;\n}\n\ninterface VestibularBanksApiResponse {\n message: string;\n data: VestibularBankItem[];\n}\n\n/**\n * API response for knowledge areas (subjects) endpoint\n */\ninterface KnowledgeAreasApiResponse {\n message: string;\n data: KnowledgeArea[];\n}\n\n/**\n * API response for question types endpoint\n */\ninterface QuestionTypesApiResponse {\n message: string;\n data: {\n questionTypes: string[];\n isFiltered: boolean;\n };\n}\n\n// ============================================================================\n// Internal State Types\n// ============================================================================\n\ninterface VestibularBanksState {\n banks: Bank[];\n bankYears: BankYear[];\n loading: boolean;\n error: string | null;\n}\n\ninterface KnowledgeAreasState {\n knowledgeAreas: KnowledgeArea[];\n loading: boolean;\n error: string | null;\n}\n\ninterface QuestionTypesState {\n questionTypes: QUESTION_TYPE[];\n loading: boolean;\n error: string | null;\n}\n\n// ============================================================================\n// Hook Configuration\n// ============================================================================\n\n/**\n * Configuration options for the useActivityFiltersData hook\n */\nexport interface UseActivityFiltersDataOptions {\n /**\n * Selected subjects for loading knowledge structure\n */\n selectedSubjects: string[];\n /**\n * Institution ID for loading question types (optional)\n */\n institutionId?: string | null;\n}\n\n// ============================================================================\n// Return Type\n// ============================================================================\n\n/**\n * Return type for the useActivityFiltersData hook\n */\nexport interface UseActivityFiltersDataReturn {\n // Vestibular Banks\n banks: Bank[];\n bankYears: BankYear[];\n loadingBanks: boolean;\n banksError: string | null;\n loadBanks: () => Promise<void>;\n\n // Knowledge Areas (Subjects)\n knowledgeAreas: KnowledgeArea[];\n loadingSubjects: boolean;\n subjectsError: string | null;\n loadKnowledgeAreas: () => Promise<void>;\n\n // Knowledge Structure (Topics, Subtopics, Contents)\n knowledgeStructure: KnowledgeStructureState;\n knowledgeCategories: CategoryConfig[];\n handleCategoriesChange: (updatedCategories: CategoryConfig[]) => void;\n selectedKnowledgeSummary: {\n topics: string[];\n subtopics: string[];\n contents: string[];\n };\n enableSummary: boolean;\n loadTopics: (subjectIds: string[]) => Promise<void>;\n loadSubtopics: (topicIds: string[]) => Promise<void>;\n loadContents: (subtopicIds: string[]) => Promise<void>;\n\n // Question Types\n questionTypes: QUESTION_TYPE[];\n loadingQuestionTypes: boolean;\n questionTypesError: string | null;\n loadQuestionTypes: () => Promise<void>;\n}\n\n// ============================================================================\n// Question Type Mapping\n// ============================================================================\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst areCategoriesSame = (\n prev: CategoryConfig[],\n current: CategoryConfig[]\n): boolean => {\n if (prev.length !== current.length) return false;\n\n return current.every((category) => {\n const prevCategory = prev.find((c) => c.key === category.key);\n if (!prevCategory) return false;\n\n const prevIds = (prevCategory.itens || []).map(\n (item: { id: string }) => item.id\n );\n const currentIds = (category.itens || []).map(\n (item: { id: string }) => item.id\n );\n\n if (prevIds.length !== currentIds.length) return false;\n return currentIds.every((id) => prevIds.includes(id));\n });\n};\n\nconst mergeCategoriesWithSelection = (\n prev: CategoryConfig[],\n current: CategoryConfig[]\n): CategoryConfig[] => {\n return current.map((category) => {\n const prevCategory = prev.find((c) => c.key === category.key);\n if (!prevCategory) {\n return category;\n }\n\n const validSelectedIds = (prevCategory.selectedIds || []).filter(\n (id: string) =>\n category.itens?.some((item: { id: string }) => item.id === id)\n );\n\n return {\n ...category,\n selectedIds: validSelectedIds,\n };\n });\n};\n\nconst mapSelectedNames = (ids: string[], items: KnowledgeItem[]): string[] => {\n return ids\n .map((id: string) => {\n const item = items.find((t) => t.id === id);\n return item ? item.name : null;\n })\n .filter((name): name is string => name !== null);\n};\n\n// ============================================================================\n// Main Hook Implementation\n// ============================================================================\n\nconst useActivityFiltersDataImpl = (\n apiClient: BaseApiClient,\n options: UseActivityFiltersDataOptions\n): UseActivityFiltersDataReturn => {\n const { selectedSubjects, institutionId } = options;\n\n // ========================================================================\n // Vestibular Banks State\n // ========================================================================\n\n const [banksState, setBanksState] = useState<VestibularBanksState>({\n banks: [],\n bankYears: [],\n loading: false,\n error: null,\n });\n\n /**\n * Load vestibular banks from API\n */\n const loadBanks = useCallback(async () => {\n setBanksState((prev) => ({ ...prev, loading: true, error: null }));\n\n try {\n const response = await apiClient.get<VestibularBanksApiResponse>(\n '/questions/exam-institutions'\n );\n\n // Agrupa bancas por questionBankName e coleta anos únicos\n const banksMap = new Map<\n string,\n Bank & { years: string[]; questionsCount: number }\n >();\n const bankYearsArray: BankYear[] = [];\n\n for (const item of response.data.data) {\n if (!item.questionBankName) continue;\n\n const existingBank = banksMap.get(item.questionBankName);\n\n if (existingBank) {\n if (!existingBank.years.includes(item.year)) {\n existingBank.years.push(item.year);\n }\n existingBank.questionsCount += item.questionsCount;\n } else {\n banksMap.set(item.questionBankName, {\n id: item.questionBankName,\n name: item.questionBankName,\n examInstitution: item.questionBankName,\n years: [item.year],\n questionsCount: item.questionsCount,\n });\n }\n\n // Adiciona ano da banca com formato: {questionBankYearId}-{questionBankName}\n bankYearsArray.push({\n id: `${item.questionBankYearId}-${item.questionBankName}`,\n name: item.year,\n bankId: item.questionBankName,\n });\n }\n\n const banks: Bank[] = Array.from(banksMap.values()).map((bank) => ({\n id: bank.id,\n name: bank.name,\n examInstitution: bank.examInstitution,\n }));\n\n setBanksState({\n banks,\n bankYears: bankYearsArray,\n loading: false,\n error: null,\n });\n } catch (error) {\n console.error('Erro ao carregar bancas de vestibular:', error);\n setBanksState((prev) => ({\n ...prev,\n loading: false,\n error: 'Erro ao carregar bancas de vestibular',\n }));\n }\n }, [apiClient]);\n\n // ========================================================================\n // Knowledge Areas (Subjects) State\n // ========================================================================\n\n const [areasState, setAreasState] = useState<KnowledgeAreasState>({\n knowledgeAreas: [],\n loading: false,\n error: null,\n });\n\n /**\n * Load knowledge areas from API\n */\n const loadKnowledgeAreas = useCallback(async () => {\n setAreasState((prev) => ({ ...prev, loading: true, error: null }));\n\n try {\n const response = await apiClient.get<KnowledgeAreasApiResponse>(\n '/knowledge/subjects'\n );\n\n setAreasState({\n knowledgeAreas: response.data.data,\n loading: false,\n error: null,\n });\n } catch (error) {\n console.error('Erro ao carregar áreas de conhecimento:', error);\n setAreasState((prev) => ({\n ...prev,\n loading: false,\n error: 'Erro ao carregar áreas de conhecimento',\n }));\n }\n }, [apiClient]);\n\n // ========================================================================\n // Question Types State\n // ========================================================================\n\n const [questionTypesState, setQuestionTypesState] =\n useState<QuestionTypesState>({\n questionTypes: [],\n loading: false,\n error: null,\n });\n\n /**\n * Load question types from API\n */\n const loadQuestionTypes = useCallback(async () => {\n if (!institutionId) {\n setQuestionTypesState((prev) => ({\n ...prev,\n questionTypes: [],\n loading: false,\n error: null,\n }));\n return;\n }\n\n setQuestionTypesState((prev) => ({\n ...prev,\n loading: true,\n error: null,\n }));\n\n try {\n const response = await apiClient.get<QuestionTypesApiResponse>(\n `/institutions/${institutionId}/question-types`\n );\n\n const mappedTypes = response.data.data.questionTypes\n .map((type) => mapQuestionTypeToEnum(type))\n .filter((type): type is QUESTION_TYPE => type !== null);\n\n setQuestionTypesState({\n questionTypes: mappedTypes,\n loading: false,\n error: null,\n });\n } catch (error) {\n console.error('Erro ao carregar tipos de questões:', error);\n setQuestionTypesState((prev) => ({\n ...prev,\n loading: false,\n error: 'Erro ao carregar tipos de questões',\n }));\n }\n }, [apiClient, institutionId]);\n\n // ========================================================================\n // Knowledge Structure State (Topics, Subtopics, Contents)\n // ========================================================================\n\n const [knowledgeStructure, setKnowledgeStructure] =\n useState<KnowledgeStructureState>({\n topics: [],\n subtopics: [],\n contents: [],\n loading: false,\n error: null,\n });\n\n const [knowledgeCategories, setKnowledgeCategories] = useState<\n CategoryConfig[]\n >([]);\n\n const previousSubjectsRef = useRef<string[] | null>(null);\n\n /**\n * Load topics for given subject IDs\n */\n const loadTopics = useCallback(\n async (subjectIds: string[]) => {\n if (subjectIds.length === 0) {\n setKnowledgeStructure({\n topics: [],\n subtopics: [],\n contents: [],\n loading: false,\n error: null,\n });\n setKnowledgeCategories([]);\n return;\n }\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n loading: true,\n error: null,\n }));\n\n try {\n const response = await apiClient.post<KnowledgeApiResponse>(\n '/knowledge/topics',\n { subjectIds }\n );\n\n const topics: KnowledgeItem[] = response.data.data.map((topic) => ({\n id: topic.id,\n name: topic.name,\n }));\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n topics,\n subtopics: [],\n contents: [],\n loading: false,\n error: null,\n }));\n } catch (error) {\n console.error('Erro ao carregar temas:', error);\n setKnowledgeStructure((prev) => ({\n ...prev,\n topics: [],\n subtopics: [],\n contents: [],\n loading: false,\n error: 'Erro ao carregar temas',\n }));\n }\n },\n [apiClient]\n );\n\n /**\n * Load subtopics for given topic IDs\n */\n const loadSubtopics = useCallback(\n async (topicIds: string[]) => {\n // With no topics there is nothing to fetch — POST /knowledge/subtopics\n // requires at least one topicId and would 400. Clear the dependent state\n // and bail out.\n if (topicIds.length === 0) {\n setKnowledgeStructure((prev) => ({\n ...prev,\n subtopics: [],\n contents: [],\n loading: false,\n error: null,\n }));\n return;\n }\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n loading: topicIds.length > 0,\n error: null,\n }));\n\n try {\n const response = await apiClient.post<KnowledgeApiResponse>(\n '/knowledge/subtopics',\n { topicIds }\n );\n\n // API now returns topicId; keep fallback to first requested id for safety\n const subtopics = response.data.data.map(\n (subtopic: KnowledgeItem & { topicId?: string }) => ({\n id: subtopic.id,\n name: subtopic.name,\n topicId: subtopic.topicId || topicIds[0],\n })\n );\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n subtopics,\n contents: [],\n loading: false,\n error: null,\n }));\n } catch (error) {\n console.error('Erro ao carregar subtemas:', error);\n setKnowledgeStructure((prev) => ({\n ...prev,\n subtopics: [],\n contents: [],\n loading: false,\n error: 'Erro ao carregar subtemas',\n }));\n }\n },\n [apiClient]\n );\n\n /**\n * Load contents for given subtopic IDs\n */\n const loadContents = useCallback(\n async (subtopicIds: string[]) => {\n if (subtopicIds.length === 0) {\n setKnowledgeStructure((prev) => ({\n ...prev,\n contents: [],\n loading: false,\n error: null,\n }));\n return;\n }\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n loading: true,\n error: null,\n }));\n\n try {\n const response = await apiClient.post<KnowledgeApiResponse>(\n '/knowledge/contents',\n { subtopicIds }\n );\n\n // API now returns subtopicId; keep fallback to first requested id for safety\n const contents = response.data.data.map(\n (content: KnowledgeItem & { subtopicId?: string }) => ({\n id: content.id,\n name: content.name,\n subtopicId: content.subtopicId || subtopicIds[0],\n })\n );\n\n setKnowledgeStructure((prev) => ({\n ...prev,\n contents,\n loading: false,\n error: null,\n }));\n } catch (error) {\n console.error('Erro ao carregar conteúdos:', error);\n setKnowledgeStructure((prev) => ({\n ...prev,\n contents: [],\n loading: false,\n error: 'Erro ao carregar conteúdos',\n }));\n }\n },\n [apiClient]\n );\n\n /**\n * Load topics when subjects change\n */\n useEffect(() => {\n const previousSubjects = previousSubjectsRef.current;\n const subjectsChanged =\n !previousSubjects ||\n previousSubjects.length !== selectedSubjects.length ||\n selectedSubjects.some((id, index) => id !== previousSubjects[index]);\n\n if (!subjectsChanged) return;\n\n previousSubjectsRef.current = selectedSubjects;\n\n if (selectedSubjects.length > 0) {\n loadTopics(selectedSubjects);\n return;\n }\n\n setKnowledgeStructure({\n topics: [],\n subtopics: [],\n contents: [],\n loading: false,\n error: null,\n });\n setKnowledgeCategories([]);\n }, [selectedSubjects, loadTopics]);\n\n /**\n * Handle CheckboxGroup category changes\n */\n const handleCategoriesChange = useCallback(\n (updatedCategories: CategoryConfig[]) => {\n const isFirstChange = knowledgeCategories.length === 0;\n const currentTemaCategory = knowledgeCategories.find(\n (c) => c.key === 'tema'\n );\n const currentSubtemaCategory = knowledgeCategories.find(\n (c) => c.key === 'subtema'\n );\n const currentSelectedTopicIds = currentTemaCategory?.selectedIds || [];\n const currentSelectedSubtopicIds =\n currentSubtemaCategory?.selectedIds || [];\n\n const temaCategory = updatedCategories.find((c) => c.key === 'tema');\n const selectedTopicIds = temaCategory?.selectedIds || [];\n\n const subtemaCategory = updatedCategories.find(\n (c) => c.key === 'subtema'\n );\n const selectedSubtopicIds = subtemaCategory?.selectedIds || [];\n\n setKnowledgeCategories(updatedCategories);\n\n const topicIdsChanged =\n isFirstChange ||\n currentSelectedTopicIds.length !== selectedTopicIds.length ||\n currentSelectedTopicIds.some(\n (id: string) => !selectedTopicIds.includes(id)\n ) ||\n selectedTopicIds.some(\n (id: string) => !currentSelectedTopicIds.includes(id)\n );\n\n if (topicIdsChanged) {\n loadSubtopics(selectedTopicIds);\n }\n\n const subtopicIdsChanged =\n isFirstChange ||\n currentSelectedSubtopicIds.length !== selectedSubtopicIds.length ||\n currentSelectedSubtopicIds.some(\n (id: string) => !selectedSubtopicIds.includes(id)\n ) ||\n selectedSubtopicIds.some(\n (id: string) => !currentSelectedSubtopicIds.includes(id)\n );\n\n if (subtopicIdsChanged) {\n if (selectedSubtopicIds.length > 0) {\n loadContents(selectedSubtopicIds);\n } else {\n loadContents([]);\n }\n }\n },\n [knowledgeCategories, loadSubtopics, loadContents]\n );\n\n /**\n * Update knowledge categories when structure changes\n */\n useEffect(() => {\n if (knowledgeStructure.topics.length === 0) {\n setKnowledgeCategories((prev) => {\n if (prev.length === 0) {\n return prev;\n }\n return [];\n });\n return;\n }\n\n const categories: CategoryConfig[] = [\n {\n key: 'tema',\n label: 'Tema',\n dependsOn: [],\n itens: knowledgeStructure.topics,\n selectedIds: [],\n },\n {\n key: 'subtema',\n label: 'Subtema',\n dependsOn: ['tema'],\n itens: knowledgeStructure.subtopics,\n filteredBy: [{ key: 'tema', internalField: 'topicId' }],\n selectedIds: [],\n },\n {\n key: 'assunto',\n label: 'Assunto',\n dependsOn: ['subtema'],\n itens: knowledgeStructure.contents,\n filteredBy: [{ key: 'subtema', internalField: 'subtopicId' }],\n selectedIds: [],\n },\n ];\n\n setKnowledgeCategories((prev) =>\n areCategoriesSame(prev, categories)\n ? prev\n : mergeCategoriesWithSelection(prev, categories)\n );\n }, [\n selectedSubjects,\n knowledgeStructure.topics,\n knowledgeStructure.subtopics,\n knowledgeStructure.contents,\n ]);\n\n /**\n * Get summary of selected knowledge items\n */\n const selectedKnowledgeSummary = useMemo(() => {\n const temaCategory = knowledgeCategories.find((c) => c.key === 'tema');\n const subtemaCategory = knowledgeCategories.find(\n (c) => c.key === 'subtema'\n );\n const assuntoCategory = knowledgeCategories.find(\n (c) => c.key === 'assunto'\n );\n\n const selectedTopics = mapSelectedNames(\n temaCategory?.selectedIds || [],\n knowledgeStructure.topics\n );\n\n const selectedSubtopics = mapSelectedNames(\n subtemaCategory?.selectedIds || [],\n knowledgeStructure.subtopics\n );\n\n const selectedContents = mapSelectedNames(\n assuntoCategory?.selectedIds || [],\n knowledgeStructure.contents\n );\n\n return {\n topics: selectedTopics,\n subtopics: selectedSubtopics,\n contents: selectedContents,\n };\n }, [knowledgeCategories, knowledgeStructure]);\n\n /**\n * Check if summary should be enabled (single item scenarios)\n */\n const enableSummary = useMemo(() => {\n return (\n knowledgeStructure.topics.length === 1 ||\n knowledgeStructure.subtopics.length === 1 ||\n knowledgeStructure.contents.length === 1\n );\n }, [knowledgeStructure]);\n\n // ========================================================================\n // Return\n // ========================================================================\n\n return {\n // Vestibular Banks\n banks: banksState.banks,\n bankYears: banksState.bankYears,\n loadingBanks: banksState.loading,\n banksError: banksState.error,\n loadBanks,\n\n // Knowledge Areas\n knowledgeAreas: areasState.knowledgeAreas,\n loadingSubjects: areasState.loading,\n subjectsError: areasState.error,\n loadKnowledgeAreas,\n\n // Knowledge Structure\n knowledgeStructure,\n knowledgeCategories,\n handleCategoriesChange,\n selectedKnowledgeSummary,\n enableSummary,\n loadTopics,\n loadSubtopics,\n loadContents,\n\n // Question Types\n questionTypes: questionTypesState.questionTypes,\n loadingQuestionTypes: questionTypesState.loading,\n questionTypesError: questionTypesState.error,\n loadQuestionTypes,\n };\n};\n\n/**\n * Create an activity filters data hook with API client injection.\n * @param apiClient - API client instance\n */\nexport const createUseActivityFiltersData = (apiClient: BaseApiClient) => {\n return (\n options: UseActivityFiltersDataOptions\n ): UseActivityFiltersDataReturn =>\n useActivityFiltersDataImpl(apiClient, options);\n};\n\n/**\n * Create a pre-configured activity filters data hook\n * This is a convenience function that returns a hook ready to use\n *\n * @param apiClient - API client instance\n * @returns Pre-configured useActivityFiltersData hook\n *\n * @example\n * // In your app setup\n * import { createActivityFiltersDataHook } from 'analytica-frontend-lib';\n * import api from './services/api';\n *\n * export const useActivityFiltersData = createActivityFiltersDataHook(api);\n *\n * // Then use directly in components\n * const { banks, knowledgeAreas, loadBanks } = useActivityFiltersData({\n * selectedSubjects: [],\n * institutionId: 'institution-id',\n * });\n */\nexport const createActivityFiltersDataHook = (apiClient: BaseApiClient) => {\n return createUseActivityFiltersData(apiClient);\n};\n","import { useCallback, useEffect, useMemo, useState, useRef } from 'react';\nimport { useTabletScreen } from '../../hooks/useScreen';\nimport { useSearchParams, useNavigate } from 'react-router-dom';\nimport {\n Button,\n SkeletonText,\n useQuestionFiltersStore,\n createUseActivityFiltersData,\n SendActivityModal,\n SaveActivityModelModal,\n CategoryConfig,\n useToastStore,\n Modal,\n Text,\n QUESTION_TYPE,\n} from '../..';\nimport type {\n ActivityFiltersData,\n BaseApiClient,\n PreviewQuestion,\n QuestionActivity as Question,\n QuestionFiltersState,\n SendActivityFormData,\n} from '../..';\nimport type { Lesson } from '../../types/lessons';\nimport { MonitorPlayIcon } from '@phosphor-icons/react/dist/csr/MonitorPlay';\nimport { areFiltersEqual } from '../../utils/activityFilters';\nimport type {\n ActivityDraftResponse,\n ActivityData,\n BackendFiltersFormat,\n ActivityPreFiltersInput,\n ActivityCreateResponse,\n RecommendedClassDraftResponse,\n} from './ActivityCreate.types';\nimport { ActivityType } from './ActivityCreate.types';\nimport type { CreateActivityPayload } from '../../types/sendActivity';\nimport {\n convertFiltersToBackendFormat,\n generateTitle,\n generateMultiSubjectTitle,\n convertQuestionToPreview,\n getTypeFromUrl,\n getTypeFromUrlString,\n buildSendActivityPayload,\n extractLessonsFromResponse,\n hasQuestionsChanged,\n shouldSkipAutoSave,\n extractDraftFromResponse,\n buildISODateTime,\n buildFinalDateTime,\n buildUrlWithParams,\n getRecommendedClassEndpoint,\n buildLessonDraftUpdatePayload,\n formatNavigatePath,\n getSubjectIdOrThrow,\n extractActivityIdFromResponse,\n validateSendActivityResponses,\n formatErrorMessage,\n buildActivityDataFromDraft,\n buildPartialActivityUpdate,\n buildPayloadWithTypeOverride,\n shouldUpdateUrl,\n resolvePreFilters,\n getInitialFiltersData,\n shouldTriggerSaveOnReorder,\n hasRequiredSubjectIds,\n shouldSaveDraftBeforeAddingToLesson,\n shouldUseCustomAddActivityCallback,\n shouldAddActivityToLessonDraft,\n} from './ActivityCreate.utils';\nimport { ActivityCreateSkeleton } from './components/ActivityCreateSkeleton';\nimport { ActivityCreateHeader } from './components/ActivityCreateHeader';\nimport {\n SmallScreenLayout,\n DesktopLayout,\n} from './components/ActivityCreateContent';\nimport { loadCategoriesData } from '../../utils/categoryDataUtils';\nimport { useDynamicStudentFetching } from '../../utils/useDynamicStudentFetching';\n\n/**\n * CreateActivity page component for creating new activities\n * This page does not use the standard Layout (header/sidebar)\n * @returns JSX element representing the create activity page\n */\nconst CreateActivity = ({\n apiClient,\n institutionId,\n isDark,\n onBack,\n onCreateActivity,\n onSaveModel,\n onAddActivityToLesson,\n enableExamMode = false,\n isInPersonExam = false,\n basePath = '/criar-atividade',\n activityCategory = 'ATIVIDADE',\n}: {\n apiClient: BaseApiClient;\n institutionId: string;\n isDark: boolean;\n onBack?: () => void;\n onCreateActivity?: (\n activityId: string,\n activityData: CreateActivityPayload\n ) => void;\n onSaveModel?: (response: ActivityDraftResponse) => void;\n onAddActivityToLesson?: (activityDraftId: string) => void;\n enableExamMode?: boolean;\n /** Force in-person exam mode: auto-selects PROVA subtype and PRESENCIAL mode */\n isInPersonExam?: boolean;\n /** Base path for URL navigation (default: '/criar-atividade') */\n basePath?: string;\n /** Activity category: 'ATIVIDADE' or 'PROVA' - sent in draft payloads */\n activityCategory?: 'ATIVIDADE' | 'PROVA';\n}) => {\n const [searchParams] = useSearchParams();\n const navigate = useNavigate();\n\n const typeParam = searchParams.get('type') || undefined;\n const idParam = searchParams.get('id') || undefined;\n const recommendedLessonId =\n searchParams.get('recommended-class') || undefined;\n const recommendedLessonDraftId =\n searchParams.get('recommended-class-draft') || undefined;\n const onFinishPath = searchParams.get('onFinish') || undefined;\n const classTypeParam = searchParams.get('classType') || undefined;\n\n /**\n * Build URL preserving existing query parameters\n * Used when updating URL after saving draft to maintain context for navigation\n */\n const buildActivityUrl = useCallback(\n (newType: string, newId: string) =>\n buildUrlWithParams({\n newType,\n newId,\n basePath,\n recommendedLessonDraftId,\n recommendedLessonId,\n classTypeParam,\n onFinishPath,\n }),\n [\n recommendedLessonDraftId,\n recommendedLessonId,\n classTypeParam,\n onFinishPath,\n basePath,\n ]\n );\n\n // Determine if we're in recommended lesson mode\n const isRecommendedLessonMode = !!(\n recommendedLessonId || recommendedLessonDraftId\n );\n\n const applyFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.applyFilters\n );\n const draftFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.draftFilters\n );\n const appliedFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.appliedFilters\n );\n\n const setDraftFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.setDraftFilters\n );\n\n const clearFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.clearFilters\n );\n\n const addToast = useToastStore((state) => state.addToast);\n\n // Responsive state for screen width <= 1200px\n const isSmallScreen = useTabletScreen();\n const [selectedView, setSelectedView] = useState<'questions' | 'preview'>(\n 'questions'\n );\n\n // Estados internos\n const [activity, setActivity] = useState<ActivityData | null>(null);\n const [preFilters, setPreFilters] = useState<ActivityPreFiltersInput | null>(\n null\n );\n const [loading, setLoading] = useState(false);\n const [questions, setQuestions] = useState<PreviewQuestion[]>([]);\n const [loadingInitialQuestions, setLoadingInitialQuestions] = useState(false);\n const [draftId, setDraftId] = useState<string | null>(idParam ?? null);\n const [activityType, setActivityType] = useState<ActivityType>(\n getTypeFromUrlString(typeParam)\n );\n const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n const [isSendModalOpen, setIsSendModalOpen] = useState(false);\n const [isSaveModelModalOpen, setIsSaveModelModalOpen] = useState(false);\n const [isSavingModel, setIsSavingModel] = useState(false);\n const [categories, setCategories] = useState<CategoryConfig[]>([]);\n const [isSendingActivity, setIsSendingActivity] = useState(false);\n const [filtersKey, setFiltersKey] = useState(0);\n\n const [isLessonPreviewModalOpen, setIsLessonPreviewModalOpen] =\n useState(false);\n const [previewLessons, setPreviewLessons] = useState<Lesson[]>([]);\n const [isLoadingPreviewLessons, setIsLoadingPreviewLessons] = useState(false);\n const hasFirstSaveBeenDone = useRef(false);\n const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastSavedFiltersRef = useRef<ActivityFiltersData | null>(null);\n const lastSavedQuestionsRef = useRef<PreviewQuestion[]>([]);\n const hasAppliedInitialFiltersRef = useRef(false);\n const lastFetchedActivityIdRef = useRef<string | null>(null);\n\n const handleFiltersChange = useCallback(\n (filters: ActivityFiltersData) => {\n setDraftFilters(filters);\n },\n [setDraftFilters]\n );\n\n const handleApplyFilters = useCallback(() => {\n if (!hasRequiredSubjectIds(draftFilters?.subjectIds)) {\n addToast({\n title: 'Selecione ao menos uma matéria para pesquisar',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n applyFilters();\n }, [applyFilters, draftFilters, addToast]);\n\n /**\n * Handle back button click - resets everything before calling onBack\n * If onFinishPath is provided, navigate to that path instead\n */\n const handleBack = useCallback(() => {\n clearFilters();\n\n if (onFinishPath) {\n navigate(formatNavigatePath(onFinishPath));\n return;\n }\n\n if (onBack) {\n onBack();\n }\n }, [clearFilters, onBack, onFinishPath, navigate]);\n\n /**\n * Handle lesson preview button click - fetches lessons from recommended-class-draft and opens modal\n */\n const handleLessonPreview = useCallback(async () => {\n setIsLessonPreviewModalOpen(true);\n\n const draftIdToFetch = recommendedLessonDraftId || recommendedLessonId;\n if (!draftIdToFetch) {\n return;\n }\n\n setIsLoadingPreviewLessons(true);\n try {\n const endpoint = recommendedLessonDraftId\n ? `/recommended-class/drafts/${draftIdToFetch}`\n : `/recommended-class/${draftIdToFetch}`;\n\n const response = await apiClient.get<{\n message?: string;\n data: Parameters<typeof extractLessonsFromResponse>[0];\n }>(endpoint);\n\n const responseData =\n 'data' in response.data ? response.data.data : response.data;\n const lessons = extractLessonsFromResponse(responseData);\n setPreviewLessons(lessons);\n } catch (error) {\n console.error('Error fetching lesson preview:', error);\n setPreviewLessons([]);\n } finally {\n setIsLoadingPreviewLessons(false);\n }\n }, [recommendedLessonDraftId, recommendedLessonId, apiClient]);\n\n const useActivityFiltersData = createUseActivityFiltersData(apiClient);\n const { knowledgeAreas, loadKnowledgeAreas } = useActivityFiltersData({\n selectedSubjects: [],\n institutionId,\n });\n\n const resolvedPreFilters = useMemo(\n () => resolvePreFilters(preFilters),\n [preFilters]\n );\n\n const initialFiltersData = useMemo(\n () => getInitialFiltersData(activity?.filters, resolvedPreFilters),\n [activity?.filters, resolvedPreFilters]\n );\n\n /**\n * Handle clear filters button click - clears all filters and forces re-render\n */\n const handleClearFilters = useCallback(() => {\n clearFilters();\n // Force re-render of ActivityFilters component by changing key\n setFiltersKey((prev) => prev + 1);\n }, [clearFilters]);\n\n useEffect(() => {\n hasAppliedInitialFiltersRef.current = false;\n }, [activity?.id, activity?.filters, resolvedPreFilters]);\n\n // Use unified /activity-drafts and /activities endpoints\n // For exams, we add activityType=PROVA query param instead of using separate endpoints\n const draftEndpoint = '/activity-drafts';\n const activityEndpoint = '/activities';\n\n /**\n * Busca o rascunho/modelo da atividade quando há um id na URL\n * Só faz a busca se o id mudou para um diferente do que está sendo trabalhado\n */\n useEffect(() => {\n const fetchActivityDraft = async () => {\n // Só busca se há um id e ele é diferente do último buscado\n // Remove a dependência de activity?.id para evitar refetch quando atualizamos activity via PATCH\n if (idParam && idParam !== lastFetchedActivityIdRef.current) {\n setLoading(true);\n try {\n const response = await apiClient.get<\n { data: ActivityData } | ActivityData\n >(`${draftEndpoint}/${idParam}`);\n const activityData =\n 'data' in response.data ? response.data.data : response.data;\n\n setActivity(activityData);\n setPreFilters(activityData.filters);\n setDraftId(activityData.id || null);\n setActivityType(activityData.type);\n if (activityData.updatedAt) {\n setLastSavedAt(new Date(activityData.updatedAt));\n }\n lastFetchedActivityIdRef.current = idParam;\n } catch (error) {\n console.error('Erro ao buscar rascunho da atividade:', error);\n addToast({\n title: 'Erro ao carregar atividade',\n description: formatErrorMessage(\n error,\n 'Ocorreu um erro ao carregar a atividade. Tente novamente.'\n ),\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setLoading(false);\n }\n }\n };\n\n fetchActivityDraft();\n }, [idParam, apiClient, addToast, draftEndpoint]);\n\n /**\n * Monitora activity.id e activity.type e atualiza a URL quando necessário\n */\n useEffect(() => {\n if (shouldUpdateUrl(activity?.id, activity?.type, typeParam, idParam)) {\n const urlType = getTypeFromUrl(activity!.type);\n navigate(buildActivityUrl(urlType, activity!.id!), {\n replace: true,\n });\n }\n }, [\n activity?.id,\n activity?.type,\n typeParam,\n idParam,\n navigate,\n buildActivityUrl,\n ]);\n\n /**\n * Validate if save conditions are met\n *\n * @returns true if save can proceed, false otherwise\n */\n const validateSaveConditions = useCallback((): boolean => {\n if (isSaving || !hasRequiredSubjectIds(appliedFilters?.subjectIds)) {\n return false;\n }\n return !shouldSkipAutoSave({\n loadingInitialQuestions,\n questionsCount: questions.length,\n hasFirstSaveBeenDone: hasFirstSaveBeenDone.current,\n appliedFilters,\n });\n }, [questions.length, appliedFilters, loadingInitialQuestions, isSaving]);\n\n /**\n * Create draft payload for API request\n *\n * @returns Draft payload object\n */\n const createDraftPayload = useCallback(() => {\n const subjectIds = appliedFilters?.subjectIds ?? [];\n const firstSubjectId = subjectIds[0];\n if (!firstSubjectId) {\n throw new Error('Subject ID não encontrado');\n }\n // A draft is bound to at most one subject. When several are selected we\n // omit subjectId: the backend only enforces \"all questions belong to the\n // draft's subject\" when subjectId is present, so omitting it lets a\n // multi-subject draft save (filters.subjects still carries every subject).\n const subjectId = subjectIds.length === 1 ? firstSubjectId : undefined;\n const title =\n subjectIds.length === 1\n ? generateTitle(activityType, firstSubjectId, knowledgeAreas)\n : generateMultiSubjectTitle(activityType);\n const filters = convertFiltersToBackendFormat(appliedFilters);\n const questionIds = questions.map((q) => q.id);\n\n return {\n type: activityType,\n activityType: activityCategory,\n title,\n subjectId,\n filters,\n questionIds,\n isDigital: !isInPersonExam,\n };\n }, [\n appliedFilters,\n activityType,\n knowledgeAreas,\n questions,\n isInPersonExam,\n activityCategory,\n ]);\n\n /**\n * Update existing draft via PATCH\n *\n * @param payload - Draft payload to send\n */\n const updateExistingDraft = useCallback(\n async (payload: {\n type: ActivityType;\n title: string;\n subjectId?: string;\n filters: BackendFiltersFormat;\n questionIds: string[];\n isDigital: boolean;\n }) => {\n const response = await apiClient.patch<ActivityDraftResponse>(\n `${draftEndpoint}/${draftId}`,\n payload\n );\n lastSavedQuestionsRef.current = questions;\n lastSavedFiltersRef.current = appliedFilters!;\n\n const savedDraft = response?.data?.data?.draft;\n setLastSavedAt(\n savedDraft?.updatedAt ? new Date(savedDraft.updatedAt) : new Date()\n );\n\n if (savedDraft) {\n const questionIds = questions.map((q) => q.id);\n setActivity((prevActivity) => {\n if (prevActivity?.id !== savedDraft.id) {\n return buildActivityDataFromDraft(savedDraft, questionIds);\n }\n return buildPartialActivityUpdate(\n prevActivity,\n savedDraft,\n questionIds\n );\n });\n setActivityType(savedDraft.type);\n if (savedDraft.id) {\n lastFetchedActivityIdRef.current = savedDraft.id;\n }\n }\n\n if (\n payload.type === ActivityType.MODELO &&\n onSaveModel &&\n response?.data\n ) {\n onSaveModel(response.data);\n }\n },\n [draftId, apiClient, questions, appliedFilters, onSaveModel, draftEndpoint]\n );\n\n /**\n * Update component state after successful draft save\n *\n * @param savedDraft - Saved draft object from API\n * @param fullResponse - Full API response (optional, for onSaveModel callback)\n * @param wasNewDraft - Whether this was a new draft creation (to update URL)\n */\n const updateStateAfterSave = useCallback(\n (\n savedDraft: ActivityDraftResponse['data']['draft'],\n fullResponse?: ActivityDraftResponse,\n wasNewDraft = false\n ) => {\n setDraftId(savedDraft.id);\n // Use updatedAt from savedDraft if available, otherwise use current time\n setLastSavedAt(\n savedDraft.updatedAt ? new Date(savedDraft.updatedAt) : new Date()\n );\n lastSavedQuestionsRef.current = questions;\n lastSavedFiltersRef.current = appliedFilters!;\n\n // Atualiza o estado interno da atividade\n const updatedActivity: ActivityData = {\n id: savedDraft.id,\n type: savedDraft.type,\n title: savedDraft.title,\n subjectId: savedDraft.subjectId,\n filters: savedDraft.filters,\n questionIds: questions.map((q) => q.id),\n updatedAt: savedDraft.updatedAt,\n };\n setActivity(updatedActivity);\n setActivityType(savedDraft.type);\n lastFetchedActivityIdRef.current = savedDraft.id;\n\n // Se foi um novo rascunho, atualiza a URL\n if (wasNewDraft && savedDraft.id) {\n const urlType = getTypeFromUrl(savedDraft.type);\n navigate(buildActivityUrl(urlType, savedDraft.id), {\n replace: true,\n });\n }\n\n // Call onSaveModel callback if type is MODELO and callback is provided\n if (\n savedDraft.type === ActivityType.MODELO &&\n onSaveModel &&\n fullResponse\n ) {\n onSaveModel(fullResponse);\n }\n },\n [questions, appliedFilters, onSaveModel, navigate, buildUrlWithParams]\n );\n\n /**\n * Save draft to backend\n * @param typeOverride - Override the activity type for this save\n * @param customTitle - Optional user-provided title; falls back to auto-generated when empty\n * @returns The persisted draft ID (existing or newly created) on success.\n * Returns undefined when no persistence happened — either because the save\n * failed or because validateSaveConditions skipped it. Callers triggering\n * an explicit override (e.g. \"save as MODELO\") rely on this to gate\n * success feedback so they don't show a toast for a save that never ran.\n */\n const saveDraft = useCallback(\n async (\n typeOverride?: ActivityType,\n customTitle?: string\n ): Promise<string | undefined> => {\n if (!validateSaveConditions()) {\n // For explicit override flows, skipped saves must not signal success.\n return typeOverride ? undefined : draftId || undefined;\n }\n\n setIsSaving(true);\n\n try {\n let payload = createDraftPayload();\n\n if (typeOverride) {\n const subjectId = getSubjectIdOrThrow(\n undefined,\n appliedFilters?.subjectIds\n );\n payload = buildPayloadWithTypeOverride(\n payload,\n typeOverride,\n customTitle,\n subjectId,\n knowledgeAreas\n );\n }\n\n if (draftId) {\n await updateExistingDraft(payload);\n return draftId;\n }\n\n const response = await apiClient.post<ActivityDraftResponse>(\n draftEndpoint,\n payload\n );\n hasFirstSaveBeenDone.current = true;\n\n const savedDraft = extractDraftFromResponse(response);\n updateStateAfterSave(savedDraft, response?.data, true);\n return savedDraft.id;\n } catch (error) {\n console.error('❌ Erro ao salvar rascunho:', error);\n addToast({\n title: 'Erro ao salvar rascunho',\n description: formatErrorMessage(\n error,\n 'Ocorreu um erro ao salvar o rascunho. Tente novamente.'\n ),\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return undefined;\n } finally {\n setIsSaving(false);\n }\n },\n [\n validateSaveConditions,\n createDraftPayload,\n draftId,\n updateExistingDraft,\n apiClient,\n updateStateAfterSave,\n addToast,\n appliedFilters,\n knowledgeAreas,\n ]\n );\n\n /**\n * Open the \"save as model\" modal so the user can provide a custom title\n */\n const handleSaveModel = useCallback(() => {\n setIsSaveModelModalOpen(true);\n }, []);\n\n /**\n * Persist the draft as MODELO using the title provided by the user.\n * activityType is updated by the save helpers (updateExistingDraft /\n * updateStateAfterSave) only after the backend confirms the save, keeping\n * UI state consistent with persisted state on failure.\n */\n const handleConfirmSaveModel = useCallback(\n async (title: string) => {\n setIsSavingModel(true);\n try {\n setIsSaveModelModalOpen(false);\n const savedId = await saveDraft(ActivityType.MODELO, title);\n if (savedId) {\n addToast({\n title: 'Modelo salvo com sucesso',\n description:\n 'O modelo da atividade está disponível para reutilização.',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n }\n } finally {\n setIsSavingModel(false);\n }\n },\n [saveDraft, addToast]\n );\n\n /**\n * Add activity to lesson draft via API\n */\n const addActivityToLessonDraft = useCallback(\n async (activityDraftId: string, lessonDraftId: string) => {\n const endpoint = getRecommendedClassEndpoint(\n lessonDraftId,\n classTypeParam\n );\n\n // Get current lesson draft data\n const response =\n await apiClient.get<RecommendedClassDraftResponse>(endpoint);\n const currentLesson = response.data.data;\n\n // Build update payload using utility function\n const updatePayload = buildLessonDraftUpdatePayload(\n currentLesson,\n activityDraftId\n );\n\n await apiClient.patch(endpoint, updatePayload);\n },\n [classTypeParam, apiClient]\n );\n\n /**\n * Navigate after adding activity\n */\n const navigateAfterAddActivity = useCallback(() => {\n clearFilters();\n\n if (onFinishPath) {\n navigate(formatNavigatePath(onFinishPath));\n } else if (onBack) {\n onBack();\n }\n }, [clearFilters, onFinishPath, navigate, onBack]);\n\n /**\n * Handle add activity to lesson - saves draft and navigates back or calls callback\n */\n const handleAddActivityToLesson = useCallback(async () => {\n let activityDraftId: string | null | undefined = draftId;\n\n if (\n shouldSaveDraftBeforeAddingToLesson(questions.length, activityDraftId)\n ) {\n activityDraftId = await saveDraft(ActivityType.MODELO);\n }\n\n setActivityType(ActivityType.MODELO);\n\n if (\n shouldUseCustomAddActivityCallback(onAddActivityToLesson, activityDraftId)\n ) {\n onAddActivityToLesson!(activityDraftId!);\n return;\n }\n\n if (\n shouldAddActivityToLessonDraft(activityDraftId, recommendedLessonDraftId)\n ) {\n try {\n await addActivityToLessonDraft(\n activityDraftId!,\n recommendedLessonDraftId!\n );\n addToast({\n title: 'Atividade adicionada à aula com sucesso',\n action: 'success',\n position: 'top-right',\n });\n } catch (error) {\n console.error('Error adding activity to lesson:', error);\n addToast({\n title: 'Erro ao adicionar atividade à aula',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n }\n\n navigateAfterAddActivity();\n }, [\n draftId,\n questions.length,\n saveDraft,\n onAddActivityToLesson,\n recommendedLessonDraftId,\n addActivityToLessonDraft,\n addToast,\n navigateAfterAddActivity,\n ]);\n\n /**\n * Load knowledge areas on mount\n */\n useEffect(() => {\n loadKnowledgeAreas();\n }, [loadKnowledgeAreas]);\n\n /**\n * Load initial questions\n * If activity has selectedQuestions, use them directly\n * Questions should come from ListBankQuestions or via activity.selectedQuestions prop\n */\n useEffect(() => {\n if (activity?.selectedQuestions && activity.selectedQuestions.length > 0) {\n setLoadingInitialQuestions(true);\n try {\n const previewQuestions = activity.selectedQuestions.map((q) =>\n convertQuestionToPreview(q)\n );\n setQuestions(previewQuestions);\n hasFirstSaveBeenDone.current = true;\n lastSavedQuestionsRef.current = previewQuestions;\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Ocorreu um erro ao carregar as questões. Tente novamente.';\n addToast({\n title: 'Erro ao carregar questões',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setLoadingInitialQuestions(false);\n }\n }\n }, [activity?.selectedQuestions, addToast]);\n\n /**\n * Initialize filters from activity (edit mode) or preFilters (create mode)\n */\n useEffect(() => {\n if (hasAppliedInitialFiltersRef.current) {\n return;\n }\n\n if (!initialFiltersData) {\n return;\n }\n\n setDraftFilters(initialFiltersData);\n applyFilters();\n lastSavedFiltersRef.current = initialFiltersData;\n hasAppliedInitialFiltersRef.current = true;\n }, [initialFiltersData, setDraftFilters, applyFilters]);\n\n /**\n * Force ALTERNATIVA question type filter for in-person exams\n * In-person exams only support multiple choice questions\n */\n useEffect(() => {\n if (isInPersonExam) {\n setDraftFilters({\n types: [QUESTION_TYPE.ALTERNATIVA],\n bankIds: draftFilters?.bankIds || [],\n yearIds: draftFilters?.yearIds || [],\n subjectIds: draftFilters?.subjectIds || [],\n topicIds: draftFilters?.topicIds || [],\n subtopicIds: draftFilters?.subtopicIds || [],\n contentIds: draftFilters?.contentIds || [],\n });\n applyFilters();\n }\n }, [isInPersonExam]);\n\n const saveDraftRef = useRef(saveDraft);\n useEffect(() => {\n saveDraftRef.current = saveDraft;\n }, [saveDraft]);\n\n /**\n * Auto-save draft when questions or filters change (with debounce)\n */\n useEffect(() => {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n\n const skipAutoSave = shouldSkipAutoSave({\n loadingInitialQuestions,\n questionsCount: questions.length,\n hasFirstSaveBeenDone: hasFirstSaveBeenDone.current,\n appliedFilters,\n });\n\n if (skipAutoSave) {\n return;\n }\n\n const questionsChanged = hasQuestionsChanged(\n questions,\n lastSavedQuestionsRef.current\n );\n const filtersChanged = !areFiltersEqual(\n lastSavedFiltersRef.current,\n appliedFilters\n );\n\n if (!questionsChanged && !filtersChanged && hasFirstSaveBeenDone.current) {\n return;\n }\n\n saveTimeoutRef.current = setTimeout(() => {\n saveDraftRef.current();\n }, 500);\n\n return () => {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n };\n }, [questions, appliedFilters, activityType, loadingInitialQuestions]);\n\n /**\n * Handle adding a question to the activity\n */\n const handleAddQuestion = useCallback((question: Question) => {\n setQuestions((prev) => {\n if (prev.some((q) => q.id === question.id)) {\n return prev;\n }\n\n try {\n const previewQuestion = convertQuestionToPreview(question);\n return [...prev, previewQuestion];\n } catch (error) {\n console.error('Erro ao converter questão para preview:', {\n questionId: question.id,\n questionType: question.questionType,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n // Retorna o estado anterior inalterado em caso de erro\n return prev;\n }\n });\n }, []);\n\n /**\n * Handle removing all questions\n */\n const handleRemoveAll = useCallback(() => {\n setQuestions([]);\n }, []);\n\n /**\n * Handle removing a single question\n */\n const handleRemoveQuestion = useCallback((questionId: string) => {\n setQuestions((prev) => prev.filter((q) => q.id !== questionId));\n }, []);\n\n /**\n * Handle reordering questions\n */\n const handleReorder = useCallback(\n (orderedQuestions: PreviewQuestion[]) => {\n setQuestions(orderedQuestions);\n const shouldSave = shouldTriggerSaveOnReorder({\n hasFirstSaveBeenDone: hasFirstSaveBeenDone.current,\n subjectIds: appliedFilters?.subjectIds,\n loadingInitialQuestions,\n isSaving,\n });\n if (shouldSave) {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n saveDraftRef.current();\n }\n },\n [appliedFilters, loadingInitialQuestions, isSaving]\n );\n\n /**\n * Load categories data from API and transform to CategoryConfig format\n */\n const handleLoadCategoriesData = useCallback(async () => {\n if (categories.length > 0) {\n return;\n }\n\n try {\n const transformedCategories = await loadCategoriesData(\n apiClient,\n categories\n );\n setCategories(transformedCategories);\n } catch (error) {\n console.error('Erro ao carregar categorias:', error);\n throw error;\n }\n }, [apiClient, categories]);\n\n /**\n * Handle categories change and fetch students dynamically\n */\n const { handleCategoriesChange } = useDynamicStudentFetching(setCategories, {\n apiClient,\n });\n\n /**\n * Handle opening the send activity modal\n */\n const handleOpenSendModal = useCallback(async () => {\n if (!hasRequiredSubjectIds(draftFilters?.subjectIds)) {\n addToast({\n title: 'Selecione ao menos uma matéria para pesquisar',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n try {\n if (categories.length === 0) {\n await handleLoadCategoriesData();\n }\n setIsSendModalOpen(true);\n } catch (error) {\n console.error('Erro ao abrir modal de envio:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Erro ao carregar dados. Por favor, tente novamente.';\n addToast({\n title: 'Erro ao carregar dados',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n }\n }, [draftFilters, categories.length, handleLoadCategoriesData, addToast]);\n\n /**\n * Handle sending activity to students\n */\n const handleSendActivity = useCallback(\n async (formData: SendActivityFormData) => {\n setIsSendingActivity(true);\n try {\n const subjectId = getSubjectIdOrThrow(\n activity?.subjectId,\n appliedFilters?.subjectIds\n );\n\n const startDateTime = buildISODateTime(\n formData.startDate,\n formData.startTime\n );\n const finalDateTime = buildFinalDateTime(\n formData.finalDate,\n formData.finalTime,\n enableExamMode || isInPersonExam\n );\n\n const activityPayload = buildSendActivityPayload(\n formData,\n subjectId,\n questions.map((q) => q.id),\n startDateTime,\n finalDateTime,\n activityCategory\n );\n\n // Create activity/exam\n const createActivityResponse =\n await apiClient.post<ActivityCreateResponse>(\n activityEndpoint,\n activityPayload\n );\n\n const activityId = extractActivityIdFromResponse(\n createActivityResponse\n );\n\n if (onCreateActivity) {\n onCreateActivity(activityId, activityPayload);\n }\n\n // Send activity to students\n const sendToStudentsPayload = {\n activityId,\n students: formData.students,\n };\n\n const sendToStudentsResponse = await apiClient.post<{\n message: string;\n data: unknown;\n }>(`${activityEndpoint}/send-to-students`, sendToStudentsPayload);\n\n validateSendActivityResponses(\n createActivityResponse,\n sendToStudentsResponse\n );\n\n setIsSendModalOpen(false);\n addToast({\n title: 'Atividade enviada com sucesso!',\n description:\n 'A atividade foi criada e enviada para os estudantes selecionados.',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n } catch (error) {\n console.error('Erro ao enviar atividade:', error);\n addToast({\n title: 'Erro ao enviar atividade',\n description: formatErrorMessage(\n error,\n 'Ocorreu um erro ao enviar a atividade. Tente novamente.'\n ),\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n throw error;\n } finally {\n setIsSendingActivity(false);\n }\n },\n [activity, appliedFilters, questions, apiClient, addToast, activityEndpoint]\n );\n\n const addedQuestionIds = useMemo(\n () => questions.map((q) => q.id),\n [questions]\n );\n\n // Render loading skeleton\n if (loading) {\n return <ActivityCreateSkeleton />;\n }\n\n return (\n <div\n data-testid=\"create-activity-page\"\n className=\"flex flex-col w-full h-full overflow-hidden p-5 bg-background\"\n >\n {/* Header Section */}\n <ActivityCreateHeader\n activity={activity || undefined}\n activityType={activityType}\n lastSavedAt={lastSavedAt}\n isSaving={isSaving}\n questionsCount={questions.length}\n onSaveModel={handleSaveModel}\n onSendActivity={handleOpenSendModal}\n onBack={handleBack}\n isRecommendedLessonMode={isRecommendedLessonMode}\n onLessonPreview={handleLessonPreview}\n onAddActivity={handleAddActivityToLesson}\n enableExamMode={enableExamMode || isInPersonExam}\n />\n\n {/* Main Content */}\n {isSmallScreen ? (\n <SmallScreenLayout\n apiClient={apiClient}\n institutionId={institutionId}\n isDark={isDark}\n selectedView={selectedView}\n onViewChange={setSelectedView}\n initialFiltersData={initialFiltersData}\n onFiltersChange={handleFiltersChange}\n onApplyFilters={handleApplyFilters}\n onClearFilters={handleClearFilters}\n onAddQuestion={handleAddQuestion}\n addedQuestionIds={addedQuestionIds}\n enableExamMode={enableExamMode || isInPersonExam}\n isInPersonExam={isInPersonExam}\n loadingInitialQuestions={loadingInitialQuestions}\n questions={questions}\n onRemoveAll={handleRemoveAll}\n onRemoveQuestion={handleRemoveQuestion}\n onReorder={handleReorder}\n filtersKey={filtersKey}\n />\n ) : (\n <DesktopLayout\n apiClient={apiClient}\n institutionId={institutionId}\n isDark={isDark}\n initialFiltersData={initialFiltersData}\n draftFilters={draftFilters}\n onFiltersChange={handleFiltersChange}\n onApplyFilters={handleApplyFilters}\n onClearFilters={handleClearFilters}\n onAddQuestion={handleAddQuestion}\n addedQuestionIds={addedQuestionIds}\n enableExamMode={enableExamMode || isInPersonExam}\n isInPersonExam={isInPersonExam}\n loadingInitialQuestions={loadingInitialQuestions}\n questions={questions}\n onRemoveAll={handleRemoveAll}\n onRemoveQuestion={handleRemoveQuestion}\n onReorder={handleReorder}\n filtersKey={filtersKey}\n />\n )}\n\n {/* Save Activity Model Modal */}\n <SaveActivityModelModal\n isOpen={isSaveModelModalOpen}\n onClose={() => setIsSaveModelModalOpen(false)}\n onConfirm={handleConfirmSaveModel}\n isLoading={isSavingModel}\n />\n\n {/* Send Activity Modal */}\n <SendActivityModal\n isOpen={isSendModalOpen}\n onClose={() => setIsSendModalOpen(false)}\n onSubmit={handleSendActivity}\n categories={categories}\n onCategoriesChange={handleCategoriesChange}\n isLoading={isSendingActivity}\n enableExamMode={enableExamMode || isInPersonExam}\n isInPersonExam={isInPersonExam}\n onError={(error) => {\n console.error('Erro ao enviar atividade:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Erro ao enviar atividade. Por favor, tente novamente.';\n addToast({\n title: 'Erro ao enviar atividade',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n }}\n />\n\n {/* Lesson Preview Modal */}\n <Modal\n isOpen={isLessonPreviewModalOpen}\n onClose={() => setIsLessonPreviewModalOpen(false)}\n title=\"Prévia da aula recomendada\"\n size=\"sm\"\n footer={\n <Button onClick={() => setIsLessonPreviewModalOpen(false)}>OK</Button>\n }\n >\n <div className=\"flex flex-col gap-3\">\n {isLoadingPreviewLessons ? (\n <div className=\"flex flex-col gap-3\">\n <SkeletonText className=\"h-4 w-32\" />\n <SkeletonText className=\"h-14 w-full\" />\n <SkeletonText className=\"h-14 w-full\" />\n </div>\n ) : (\n <>\n <Text size=\"sm\" className=\"text-text-500\">\n {previewLessons.length} aula\n {previewLessons.length === 1 ? '' : 's'} adicionada\n {previewLessons.length === 1 ? '' : 's'}\n </Text>\n <div className=\"flex flex-col gap-2\">\n {previewLessons.map((lesson) => (\n <div\n key={lesson.id}\n className=\"flex items-center justify-between p-4 border border-border-200 rounded-lg\"\n >\n <Text\n size=\"sm\"\n className=\"text-text-700 truncate flex-1 mr-2\"\n >\n {lesson.videoTitle || lesson.title || 'Aula sem título'}\n </Text>\n <MonitorPlayIcon\n size={20}\n className=\"text-text-400 flex-shrink-0\"\n />\n </div>\n ))}\n {previewLessons.length === 0 && (\n <Text size=\"sm\" className=\"text-text-500 text-center py-4\">\n Nenhuma aula adicionada ainda.\n </Text>\n )}\n </div>\n </>\n )}\n </div>\n </Modal>\n </div>\n );\n};\n\nexport { CreateActivity };\nexport { ActivityType, ActivityStatus } from './ActivityCreate.types';\nexport type {\n BackendFiltersFormat,\n ActivityDraftResponse,\n ActivityData,\n ActivityPreFiltersInput,\n ActivityCreatePayload,\n ActivityCreateResponse,\n School,\n SchoolYear,\n Class,\n Student,\n} from './ActivityCreate.types';\n","import { useState, useEffect } from 'react';\n\ntype ScreenSize = {\n width: number;\n height: number;\n};\n\ntype UseScreenSizeOptions = {\n width?: boolean;\n height?: boolean;\n};\n\ntype UseScreenSizeReturn = {\n width?: number;\n height?: number;\n screenSize: ScreenSize;\n};\n\n// Mobile width in pixels\nconst MOBILE_WIDTH = 931;\n// Small screen width in pixels (for ActivityCreate and RecommendedLessonCreate)\nconst SMALL_SCREEN_WIDTH = 1200;\n\n/**\n * Hook para capturar o tamanho da tela do usuário\n * @param options - Opções para escolher quais dimensões capturar\n * @returns Objeto com as dimensões solicitadas e o tamanho completo da tela\n */\nexport const useScreenSize = (\n options: UseScreenSizeOptions = {}\n): UseScreenSizeReturn => {\n const [screenSize, setScreenSize] = useState<ScreenSize>({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n\n useEffect(() => {\n const handleResize = () => {\n setScreenSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n };\n\n // Adicionar listener para mudanças de tamanho\n window.addEventListener('resize', handleResize);\n\n // Cleanup do listener\n return () => {\n window.removeEventListener('resize', handleResize);\n };\n }, []);\n\n // Retornar apenas as dimensões solicitadas\n const result: UseScreenSizeReturn = {\n screenSize,\n };\n\n if (options.width !== false) {\n result.width = screenSize.width;\n }\n\n if (options.height !== false) {\n result.height = screenSize.height;\n }\n\n return result;\n};\n\n/**\n * Hook para capturar apenas a largura da tela\n */\nexport const useScreenWidth = (): number => {\n const { width } = useScreenSize({ width: true, height: false });\n return width!;\n};\n\n/**\n * Hook para capturar apenas a altura da tela\n */\nexport const useScreenHeight = (): number => {\n const { height } = useScreenSize({ width: false, height: true });\n return height!;\n};\n\n/**\n * Hook para capturar o tamanho completo da tela\n */\nexport const useFullScreenSize = (): ScreenSize => {\n const { screenSize } = useScreenSize();\n return screenSize;\n};\n\n/**\n * Hook to detect screen size\n * @returns true if the screen is mobile, false otherwise\n */\nexport const useMobile = () => {\n const [isMobile, setIsMobile] = useState(false);\n\n useEffect(() => {\n const checkScreenSize = () => {\n setIsMobile(window.innerWidth < MOBILE_WIDTH);\n };\n\n checkScreenSize();\n\n window.addEventListener('resize', checkScreenSize);\n\n return () => window.removeEventListener('resize', checkScreenSize);\n }, []);\n\n return isMobile;\n};\n\n/**\n * Hook to detect small screen size (width <= 1200px)\n * Used by ActivityCreate and RecommendedLessonCreate components\n * @returns true if the screen width is <= 1200px, false otherwise\n */\nexport const useTabletScreen = () => {\n const [isSmallScreen, setIsSmallScreen] = useState(false);\n\n useEffect(() => {\n const checkScreenSize = () => {\n setIsSmallScreen(window.innerWidth <= SMALL_SCREEN_WIDTH);\n };\n\n checkScreenSize();\n\n window.addEventListener('resize', checkScreenSize);\n\n return () => window.removeEventListener('resize', checkScreenSize);\n }, []);\n\n return isSmallScreen;\n};\n","import type {\n ActivityFiltersData,\n PreviewQuestion,\n QuestionActivity as Question,\n SendActivityFormData,\n} from '../..';\nimport type { Lesson } from '../../types/lessons';\nimport { ActivityMode, ActivitySubtype } from '../SendActivityModal/types';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport type {\n BackendFiltersFormat,\n ActivityDraftResponse,\n} from './ActivityCreate.types';\nimport { ActivityType } from './ActivityCreate.types';\nimport type { CreateActivityPayload } from '../../types/sendActivity';\n\n/**\n * Response format for recommended class lesson data\n */\ninterface LessonResponseData {\n draft?: {\n selectedLessons?: Lesson[];\n lessons?: Array<{\n lessonId: string;\n sequence: number;\n lesson?: Lesson;\n }>;\n };\n selectedLessons?: Lesson[];\n lessons?: Array<{\n lessonId: string;\n sequence: number;\n lesson?: Lesson;\n }>;\n}\n\n/**\n * Extract lessons from recommended class API response\n * Handles multiple response formats for backward compatibility\n *\n * @param responseData - Response data from recommended class API\n * @returns Array of Lesson objects\n *\n * @example\n * ```ts\n * const lessons = extractLessonsFromResponse({\n * draft: { selectedLessons: [...] }\n * });\n * ```\n */\nexport function extractLessonsFromResponse(\n responseData: LessonResponseData\n): Lesson[] {\n const draft = responseData.draft;\n\n // Try draft.selectedLessons first\n if (draft?.selectedLessons && draft.selectedLessons.length > 0) {\n return draft.selectedLessons;\n }\n\n // Try responseData.selectedLessons\n if (responseData.selectedLessons && responseData.selectedLessons.length > 0) {\n return responseData.selectedLessons;\n }\n\n // Try draft.lessons (extract lesson property)\n if (draft?.lessons && draft.lessons.length > 0) {\n return draft.lessons\n .map((item) => {\n if (\n item &&\n typeof item === 'object' &&\n 'lesson' in item &&\n item.lesson\n ) {\n return item.lesson;\n }\n return null;\n })\n .filter((lesson): lesson is Lesson => lesson !== null);\n }\n\n // Try responseData.lessons (extract lesson property)\n if (responseData.lessons && responseData.lessons.length > 0) {\n return responseData.lessons\n .map((item) => {\n if (\n item &&\n typeof item === 'object' &&\n 'lesson' in item &&\n item.lesson\n ) {\n return item.lesson;\n }\n return null;\n })\n .filter((lesson): lesson is Lesson => lesson !== null);\n }\n\n return [];\n}\n\n/**\n * Set of valid QUESTION_TYPE enum values for runtime validation\n */\nconst VALID_QUESTION_TYPES = new Set(Object.values(QUESTION_TYPE));\n\n/**\n * Type guard to validate if a string is a valid QUESTION_TYPE\n * @param type - String to validate\n * @returns True if type is a valid QUESTION_TYPE enum value\n */\nconst isValidQuestionType = (type: string): type is QUESTION_TYPE => {\n return VALID_QUESTION_TYPES.has(type as QUESTION_TYPE);\n};\n\n/**\n * Knowledge area interface for subject lookup\n */\nexport interface KnowledgeArea {\n id: string;\n name: string;\n}\n\n/**\n * Convert ActivityFiltersData to backend format\n *\n * @param filters - ActivityFiltersData to convert, can be null\n * @returns BackendFiltersFormat object with converted filter data\n *\n * @example\n * ```ts\n * const backendFilters = convertFiltersToBackendFormat({\n * types: ['ALTERNATIVA'],\n * subjectIds: ['math'],\n * // ...\n * });\n * ```\n */\nexport function convertFiltersToBackendFormat(\n filters: ActivityFiltersData | null\n): BackendFiltersFormat {\n if (!filters) {\n return {\n questionTypes: [],\n questionBanks: [],\n subjects: [],\n topics: [],\n subtopics: [],\n contents: [],\n };\n }\n\n return {\n questionTypes: filters.types,\n questionBanks: filters.bankIds,\n subjects: filters.subjectIds,\n topics: filters.topicIds,\n subtopics: filters.subtopicIds,\n contents: filters.contentIds,\n };\n}\n\n/**\n * Convert backend filters format to ActivityFiltersData\n *\n * @param backendFilters - BackendFiltersFormat to convert, can be null\n * @returns ActivityFiltersData object with converted filter data, or null if input is null\n *\n * @example\n * ```ts\n * const activityFilters = convertBackendFiltersToActivityFiltersData({\n * questionTypes: ['ALTERNATIVA'],\n * subjects: ['math'],\n * // ...\n * });\n * ```\n */\nexport function convertBackendFiltersToActivityFiltersData(\n backendFilters: BackendFiltersFormat | null\n): ActivityFiltersData | null {\n if (!backendFilters) {\n return null;\n }\n\n return {\n types: (backendFilters.questionTypes || []).filter(isValidQuestionType),\n bankIds: backendFilters.questionBanks || [],\n subjectIds: backendFilters.subjects || [],\n topicIds: backendFilters.topics || [],\n subtopicIds: backendFilters.subtopics || [],\n contentIds: backendFilters.contents || [],\n yearIds: [],\n };\n}\n\n/**\n * Get subject name from subjectId by searching in knowledge areas\n *\n * @param subjectId - The subject ID to look up\n * @param knowledgeAreas - Array of knowledge areas to search in\n * @returns The subject name if found, null otherwise\n *\n * @example\n * ```ts\n * const subjectName = getSubjectName('math', [\n * { id: 'math', name: 'Matemática' }\n * ]);\n * // Returns: 'Matemática'\n * ```\n */\nexport function getSubjectName(\n subjectId: string | null,\n knowledgeAreas: KnowledgeArea[]\n): string | null {\n if (!subjectId || !knowledgeAreas.length) {\n return null;\n }\n const subject = knowledgeAreas.find((area) => area.id === subjectId);\n return subject?.name || null;\n}\n\n/**\n * Get activity type label using object literal mapping\n *\n * @param type - Activity type (ActivityType enum)\n * @returns Label string for the activity type\n *\n * @example\n * ```ts\n * const label = getActivityTypeLabel(ActivityType.RASCUNHO);\n * // Returns: 'Rascunho'\n * ```\n */\nexport function getActivityTypeLabel(type: ActivityType): string {\n const activityTypeLabels: Record<ActivityType, string> = {\n [ActivityType.RASCUNHO]: 'Rascunho',\n [ActivityType.MODELO]: 'Modelo',\n [ActivityType.ATIVIDADE]: 'Atividade',\n };\n return activityTypeLabels[type];\n}\n\n/**\n * Generate activity title based on type and subject\n *\n * @param type - Activity type (ActivityType enum)\n * @param subjectId - Subject ID to get name from\n * @param knowledgeAreas - Array of knowledge areas for subject lookup\n * @returns Generated title string\n *\n * @example\n * ```ts\n * const title = generateTitle(ActivityType.RASCUNHO, 'math', [\n * { id: 'math', name: 'Matemática' }\n * ]);\n * // Returns: 'Rascunho - Matemática'\n * ```\n */\nexport function generateTitle(\n type: ActivityType,\n subjectId: string | null,\n knowledgeAreas: KnowledgeArea[]\n): string {\n const typeLabel = getActivityTypeLabel(type);\n const subjectName = getSubjectName(subjectId, knowledgeAreas);\n return subjectName ? `${typeLabel} - ${subjectName}` : typeLabel;\n}\n\n/**\n * Builds a draft title when several subjects are selected and no single subject\n * name applies (e.g. \"Rascunho - Diversas matérias\").\n *\n * @param type - Activity type used for the label prefix\n * @returns Title string for a multi-subject draft\n */\nexport function generateMultiSubjectTitle(type: ActivityType): string {\n return `${getActivityTypeLabel(type)} - Diversas matérias`;\n}\n\n/**\n * Converte ActivityType para o formato usado na URL\n *\n * @param type - ActivityType enum value\n * @returns String representation for URL (rascunho or modelo)\n *\n * @example\n * ```ts\n * const urlType = getTypeFromUrl(ActivityType.RASCUNHO);\n * // Returns: 'rascunho'\n * ```\n */\nexport function getTypeFromUrl(type: ActivityType): string {\n switch (type) {\n case ActivityType.RASCUNHO:\n return 'rascunho';\n case ActivityType.MODELO:\n return 'modelo';\n default:\n return 'rascunho';\n }\n}\n\n/**\n * Converte string da URL para ActivityType\n *\n * @param type - String from URL (rascunho or modelo)\n * @returns ActivityType enum value\n *\n * @example\n * ```ts\n * const activityType = getTypeFromUrlString('rascunho');\n * // Returns: ActivityType.RASCUNHO\n * ```\n */\nexport function getTypeFromUrlString(type: string | undefined): ActivityType {\n switch (type) {\n case 'rascunho':\n return ActivityType.RASCUNHO;\n case 'modelo':\n return ActivityType.MODELO;\n default:\n return ActivityType.RASCUNHO;\n }\n}\n\n/**\n * Convert Question to PreviewQuestion format\n *\n * @param question - Question object to convert\n * @returns PreviewQuestion object with converted data\n *\n * @example\n * ```ts\n * const previewQuestion = convertQuestionToPreview({\n * id: 'q1',\n * statement: 'Test question',\n * questionType: 'ALTERNATIVA',\n * options: [{ id: 'opt1', option: 'Option 1' }],\n * knowledgeMatrix: [{\n * subject: { name: 'Math', color: '#000', icon: 'Math' }\n * }]\n * });\n * ```\n */\nexport function convertQuestionToPreview(question: Question): PreviewQuestion {\n const subjectInfo =\n question.knowledgeMatrix && question.knowledgeMatrix.length > 0\n ? {\n subjectName: question.knowledgeMatrix[0].subject?.name || undefined,\n subjectColor: question.knowledgeMatrix[0].subject?.color || undefined,\n iconName: question.knowledgeMatrix[0].subject?.icon || undefined,\n }\n : {};\n\n return {\n id: question.id,\n statement: question.statement,\n solutionExplanation: question.solutionExplanation ?? null,\n bank: question.questionBankYear?.questionBank?.name || undefined,\n year: question.questionBankYear?.year || undefined,\n questionType: question.questionType,\n question: question.options\n ? {\n options: question.options.map(\n (opt: { id: string; option: string; isCorrect?: boolean }) => ({\n id: opt.id,\n option: opt.option,\n isCorrect: opt.isCorrect,\n })\n ),\n correctOptionIds: question.options\n .filter((opt: { isCorrect?: boolean }) => opt.isCorrect === true)\n .map((opt: { id: string }) => opt.id),\n }\n : undefined,\n ...subjectInfo,\n };\n}\n\n/**\n * Build ISO date time string from date and time parts\n *\n * @param date - Date string in YYYY-MM-DD format\n * @param time - Time string in HH:mm format\n * @returns ISO date time string\n */\nexport function buildISODateTime(date: string, time: string): string {\n return new Date(`${date}T${time}`).toISOString();\n}\n\n/**\n * Build final date time based on exam mode\n *\n * @param formData - Form data with finalDate and finalTime\n * @param isExamMode - Whether exam mode is enabled\n * @returns ISO date time string or null for exam mode\n */\nexport function buildFinalDateTime(\n finalDate: string,\n finalTime: string,\n isExamMode: boolean\n): string | null {\n if (isExamMode) {\n return null;\n }\n return buildISODateTime(finalDate, finalTime);\n}\n\nexport function buildSendActivityPayload(\n formData: SendActivityFormData,\n subjectId: string,\n questionIds: string[],\n startDateTime: string,\n finalDateTime: string | null,\n activityType?: 'ATIVIDADE' | 'PROVA'\n): CreateActivityPayload {\n return {\n title: formData.title,\n subjectId,\n questionIds,\n subtype: formData.subtype,\n type: activityType,\n notification: formData.notification || '',\n startDate: startDateTime,\n finalDate: finalDateTime,\n canRetry: formData.canRetry,\n isDigital:\n formData.subtype === ActivitySubtype.PROVA\n ? formData.mode !== ActivityMode.PRESENCIAL\n : true,\n };\n}\n\n/**\n * Check if questions have changed compared to last saved state\n *\n * @param currentQuestions - Current questions array\n * @param lastSavedQuestions - Last saved questions array\n * @returns True if questions have changed\n */\nexport function hasQuestionsChanged(\n currentQuestions: Array<{ id: string }>,\n lastSavedQuestions: Array<{ id: string }>\n): boolean {\n const currentIds = currentQuestions.map((q) => q.id).join(',');\n const lastSavedIds = lastSavedQuestions.map((q) => q.id).join(',');\n return currentIds !== lastSavedIds;\n}\n\n/**\n * Parameters for shouldSkipAutoSave check\n */\ninterface AutoSaveCheckParams {\n loadingInitialQuestions: boolean;\n questionsCount: number;\n hasFirstSaveBeenDone: boolean;\n appliedFilters: ActivityFiltersData | null;\n}\n\n/**\n * Check if auto-save should be skipped based on current state\n *\n * @param params - Parameters to check\n * @returns True if auto-save should be skipped\n */\n/**\n * Parameters for building URL with preserved query params\n */\ninterface BuildUrlParams {\n newType: string;\n newId: string;\n basePath: string;\n recommendedLessonDraftId?: string;\n recommendedLessonId?: string;\n classTypeParam?: string;\n onFinishPath?: string;\n}\n\n/**\n * Build URL preserving existing query parameters\n * Used when updating URL after saving draft to maintain context for navigation\n *\n * @param params - URL building parameters\n * @returns Complete URL string with query parameters\n */\nexport function buildUrlWithParams(params: BuildUrlParams): string {\n const {\n newType,\n newId,\n basePath,\n recommendedLessonDraftId,\n recommendedLessonId,\n classTypeParam,\n onFinishPath,\n } = params;\n\n const urlParams = new URLSearchParams();\n urlParams.set('type', newType);\n urlParams.set('id', newId);\n\n if (recommendedLessonDraftId) {\n urlParams.set('recommended-class-draft', recommendedLessonDraftId);\n }\n if (recommendedLessonId) {\n urlParams.set('recommended-class', recommendedLessonId);\n }\n if (classTypeParam) {\n urlParams.set('classType', classTypeParam);\n }\n if (onFinishPath) {\n urlParams.set('onFinish', onFinishPath);\n }\n\n return `${basePath}?${urlParams.toString()}`;\n}\n\nexport function shouldSkipAutoSave(params: AutoSaveCheckParams): boolean {\n const {\n loadingInitialQuestions,\n questionsCount,\n hasFirstSaveBeenDone,\n appliedFilters,\n } = params;\n\n if (loadingInitialQuestions) {\n return true;\n }\n if (questionsCount === 0 && !hasFirstSaveBeenDone) {\n return true;\n }\n if (!appliedFilters) {\n return true;\n }\n return false;\n}\n\n/**\n * Try to extract draft from standard response format\n */\nfunction tryExtractDraftFromStandardFormat(\n responseData: ActivityDraftResponse\n): ActivityDraftResponse['data']['draft'] | undefined {\n if (responseData.data?.draft) {\n return responseData.data.draft;\n }\n if ('draft' in responseData && typeof responseData === 'object') {\n return (\n responseData as unknown as {\n draft: ActivityDraftResponse['data']['draft'];\n }\n )?.draft;\n }\n return undefined;\n}\n\n/**\n * Log error details for draft extraction failure\n */\nfunction logDraftExtractionError(response: { data: ActivityDraftResponse }) {\n console.error('❌ Resposta inválida da API ao criar rascunho:', {\n response,\n responseData: response?.data,\n responseDataData: response?.data?.data,\n responseKeys: response?.data ? Object.keys(response.data) : [],\n responseDataKeys: response?.data?.data\n ? Object.keys(response.data.data)\n : [],\n });\n}\n\n/**\n * Extract draft from API response\n * Handles multiple response formats for backward compatibility\n *\n * @param response - API response object\n * @returns Extracted draft object\n * @throws Error if draft cannot be extracted\n */\nexport function extractDraftFromResponse(response: {\n data: ActivityDraftResponse;\n}): ActivityDraftResponse['data']['draft'] {\n if (!response?.data) {\n console.error('❌ Resposta vazia da API ao criar rascunho:', response);\n throw new Error('Invalid response: empty response from API');\n }\n\n const savedDraft = tryExtractDraftFromStandardFormat(response.data);\n\n if (!savedDraft?.id) {\n logDraftExtractionError(response);\n throw new Error(\n 'Invalid response: draft data is missing. Expected structure: response.data.data.draft'\n );\n }\n\n return savedDraft;\n}\n\n/**\n * Get the recommended class endpoint URL based on class type\n *\n * @param lessonDraftId - The lesson draft ID\n * @param classTypeParam - The class type parameter (modelo or undefined)\n * @returns The endpoint URL for the recommended class\n */\nexport function getRecommendedClassEndpoint(\n lessonDraftId: string,\n classTypeParam?: string\n): string {\n const baseUrl =\n classTypeParam === 'modelo'\n ? '/recommended-class/models'\n : '/recommended-class/drafts';\n return `${baseUrl}/${lessonDraftId}`;\n}\n\n/**\n * Activity draft item for lesson draft update\n */\ninterface ActivityDraftItem {\n activityDraftId: string;\n sequence: number;\n}\n\n/**\n * Lesson item for lesson draft update\n */\ninterface LessonItem {\n lessonId: string;\n sequence: number;\n}\n\n/**\n * Current lesson data from API\n */\ninterface CurrentLessonData {\n type: string;\n title: string;\n subjectId: string;\n filters: BackendFiltersFormat;\n activityDrafts?: ActivityDraftItem[];\n lessons?: LessonItem[];\n}\n\n/**\n * Build activity draft IDs array for lesson draft update\n *\n * @param existingActivities - Existing activity drafts from the lesson\n * @param newActivityDraftId - New activity draft ID to add\n * @returns Array of activity draft items with sequences\n */\nexport function buildActivityDraftIds(\n existingActivities: ActivityDraftItem[] | undefined,\n newActivityDraftId: string\n): ActivityDraftItem[] {\n const activities = existingActivities || [];\n return [\n ...activities.map((a) => ({\n activityDraftId: a.activityDraftId,\n sequence: a.sequence,\n })),\n {\n activityDraftId: newActivityDraftId,\n sequence: activities.length + 1,\n },\n ];\n}\n\n/**\n * Build lesson IDs array from current lesson data\n *\n * @param lessons - Lessons from the current lesson data\n * @returns Array of lesson items with sequences\n */\nexport function buildLessonIds(\n lessons: LessonItem[] | undefined\n): LessonItem[] {\n return (\n lessons?.map((l) => ({\n lessonId: l.lessonId,\n sequence: l.sequence,\n })) || []\n );\n}\n\n/**\n * Build update payload for lesson draft\n *\n * @param currentLesson - Current lesson data from API\n * @param newActivityDraftId - New activity draft ID to add\n * @returns Update payload for PATCH request\n */\nexport function buildLessonDraftUpdatePayload(\n currentLesson: CurrentLessonData,\n newActivityDraftId: string\n): {\n type: string;\n title: string;\n subjectId: string;\n filters: BackendFiltersFormat;\n lessonIds: LessonItem[];\n activityDraftIds: ActivityDraftItem[];\n} {\n return {\n type: currentLesson.type,\n title: currentLesson.title,\n subjectId: currentLesson.subjectId,\n filters: currentLesson.filters,\n lessonIds: buildLessonIds(currentLesson.lessons),\n activityDraftIds: buildActivityDraftIds(\n currentLesson.activityDrafts,\n newActivityDraftId\n ),\n };\n}\n\n/**\n * Format a path to ensure it starts with /\n *\n * @param path - The path to format\n * @returns Path starting with /\n */\nexport function formatNavigatePath(path: string): string {\n return path.startsWith('/') ? path : `/${path}`;\n}\n\n/**\n * Get subject ID from activity or applied filters\n *\n * @param activitySubjectId - Subject ID from activity\n * @param appliedFiltersSubjectIds - Subject IDs from applied filters\n * @returns Subject ID or throws error if not found\n */\nexport function getSubjectIdOrThrow(\n activitySubjectId: string | undefined,\n appliedFiltersSubjectIds: string[] | undefined\n): string {\n const subjectId = activitySubjectId || appliedFiltersSubjectIds?.[0];\n if (!subjectId) {\n throw new Error('Subject ID não encontrado');\n }\n return subjectId;\n}\n\n/**\n * Activity create response data structure\n */\ninterface ActivityCreateResponseData {\n data?: {\n data?: {\n id?: string;\n };\n };\n}\n\n/**\n * Extract activity ID from create activity response\n *\n * @param response - Response from create activity API\n * @returns Activity ID or throws error if not found\n */\nexport function extractActivityIdFromResponse(\n response: ActivityCreateResponseData\n): string {\n const activityId = response?.data?.data?.id;\n if (!activityId) {\n throw new Error('ID da atividade não retornado pela API');\n }\n return activityId;\n}\n\n/**\n * Build payload for sending activity to students\n *\n * @param activityId - Activity ID\n * @param students - Array of student IDs\n * @returns Send to students payload\n */\nexport function buildSendToStudentsPayload(\n activityId: string,\n students: string[]\n): { activityId: string; students: string[] } {\n return {\n activityId,\n students,\n };\n}\n\n/**\n * Validate send activity responses\n *\n * @param createResponse - Response from create activity API\n * @param sendResponse - Response from send to students API\n * @throws Error if any response is invalid\n */\nexport function validateSendActivityResponses(\n createResponse: { data?: unknown } | undefined,\n sendResponse: { data?: unknown } | undefined\n): void {\n if (!createResponse?.data) {\n throw new Error('Resposta inválida ao criar atividade');\n }\n if (!sendResponse?.data) {\n throw new Error('Resposta inválida ao enviar atividade para estudantes');\n }\n}\n\n/**\n * Format error message from unknown error\n *\n * @param error - Error object\n * @param defaultMessage - Default message if error is not an Error instance\n * @returns Formatted error message\n */\nexport function formatErrorMessage(\n error: unknown,\n defaultMessage: string\n): string {\n return error instanceof Error ? error.message : defaultMessage;\n}\n\n/**\n * Draft data from API response\n */\ninterface DraftData {\n id: string;\n type: ActivityType;\n title: string;\n subjectId: string;\n filters: BackendFiltersFormat;\n updatedAt?: string;\n}\n\n/**\n * Build ActivityData from draft response\n *\n * @param draft - Draft data from API\n * @param questionIds - Current question IDs\n * @returns ActivityData object\n */\nexport function buildActivityDataFromDraft(\n draft: DraftData,\n questionIds: string[]\n): {\n id: string;\n type: ActivityType;\n title: string;\n subjectId: string;\n filters: BackendFiltersFormat;\n questionIds: string[];\n updatedAt?: string;\n} {\n return {\n id: draft.id,\n type: draft.type,\n title: draft.title,\n subjectId: draft.subjectId,\n filters: draft.filters,\n questionIds,\n updatedAt: draft.updatedAt,\n };\n}\n\n/**\n * Build partial activity update (preserves filters)\n *\n * @param prevActivity - Previous activity state\n * @param draft - New draft data\n * @param questionIds - Current question IDs\n * @returns Updated activity data\n */\nexport function buildPartialActivityUpdate(\n prevActivity: { filters: BackendFiltersFormat },\n draft: DraftData,\n questionIds: string[]\n): {\n id: string;\n type: ActivityType;\n title: string;\n subjectId: string;\n filters: BackendFiltersFormat;\n questionIds: string[];\n updatedAt?: string;\n} {\n return {\n id: draft.id,\n type: draft.type,\n title: draft.title,\n subjectId: draft.subjectId,\n filters: prevActivity.filters,\n questionIds,\n updatedAt: draft.updatedAt,\n };\n}\n\n/**\n * Build draft payload with type override\n *\n * @param basePayload - Base payload\n * @param typeOverride - Type to override\n * @param customTitle - Custom title (optional)\n * @param subjectId - Subject ID for title generation\n * @param knowledgeAreas - Knowledge areas for title generation\n * @returns Updated payload\n */\nexport function buildPayloadWithTypeOverride<\n T extends { type: ActivityType; title: string },\n>(\n basePayload: T,\n typeOverride: ActivityType,\n customTitle: string | undefined,\n subjectId: string,\n knowledgeAreas: KnowledgeArea[]\n): T {\n const trimmedCustomTitle = customTitle?.trim();\n const title =\n trimmedCustomTitle && trimmedCustomTitle.length > 0\n ? trimmedCustomTitle\n : generateTitle(typeOverride, subjectId, knowledgeAreas);\n\n return {\n ...basePayload,\n type: typeOverride,\n title,\n };\n}\n\n/**\n * Check if URL needs to be updated based on activity state\n *\n * @param activityId - Current activity ID\n * @param activityType - Current activity type\n * @param currentUrlType - Current URL type param\n * @param currentUrlId - Current URL id param\n * @returns True if URL needs update\n */\nexport function shouldUpdateUrl(\n activityId: string | undefined,\n activityType: ActivityType | undefined,\n currentUrlType: string | undefined,\n currentUrlId: string | undefined\n): boolean {\n if (!activityId || !activityType) {\n return false;\n }\n const urlType = getTypeFromUrl(activityType);\n return (\n !currentUrlType ||\n !currentUrlId ||\n currentUrlId !== activityId ||\n currentUrlType !== urlType\n );\n}\n\n/**\n * Pre-filters input type\n */\ntype PreFiltersInput =\n | BackendFiltersFormat\n | { filters?: BackendFiltersFormat | null }\n | null;\n\n/**\n * Resolve pre-filters from various input formats\n *\n * @param preFilters - Pre-filters in various formats\n * @returns Resolved BackendFiltersFormat or null\n */\nexport function resolvePreFilters(\n preFilters: PreFiltersInput\n): BackendFiltersFormat | null | undefined {\n if (!preFilters) {\n return null;\n }\n if (\n typeof preFilters === 'object' &&\n preFilters !== null &&\n 'filters' in preFilters\n ) {\n return preFilters.filters;\n }\n return preFilters as BackendFiltersFormat;\n}\n\n/**\n * Get initial filters data from activity or pre-filters\n *\n * @param activityFilters - Activity filters\n * @param resolvedPreFilters - Resolved pre-filters\n * @returns ActivityFiltersData or null\n */\nexport function getInitialFiltersData(\n activityFilters: BackendFiltersFormat | undefined,\n resolvedPreFilters: BackendFiltersFormat | null | undefined\n): ActivityFiltersData | null {\n if (activityFilters) {\n return convertBackendFiltersToActivityFiltersData(activityFilters);\n }\n if (resolvedPreFilters) {\n return convertBackendFiltersToActivityFiltersData(resolvedPreFilters);\n }\n return null;\n}\n\n/**\n * Parameters for checking if save should trigger on reorder\n */\ninterface ReorderSaveCheckParams {\n hasFirstSaveBeenDone: boolean;\n subjectIds: string[] | undefined;\n loadingInitialQuestions: boolean;\n isSaving: boolean;\n}\n\n/**\n * Check if save should be triggered after reordering questions\n *\n * @param params - Check parameters\n * @returns True if save should be triggered\n */\nexport function shouldTriggerSaveOnReorder(\n params: ReorderSaveCheckParams\n): boolean {\n const {\n hasFirstSaveBeenDone,\n subjectIds,\n loadingInitialQuestions,\n isSaving,\n } = params;\n const hasSubjectIds = Array.isArray(subjectIds) && subjectIds.length > 0;\n return (\n hasFirstSaveBeenDone &&\n hasSubjectIds &&\n !loadingInitialQuestions &&\n !isSaving\n );\n}\n\n/**\n * Check if filters have required subject IDs\n *\n * @param subjectIds - Subject IDs from filters\n * @returns True if at least one subject ID exists\n */\nexport function hasRequiredSubjectIds(\n subjectIds: string[] | undefined\n): boolean {\n return Array.isArray(subjectIds) && subjectIds.length > 0;\n}\n\n/**\n * Check if should save draft before adding to lesson\n *\n * @param questionsCount - Number of questions\n * @param activityDraftId - Current draft ID\n * @returns True if draft should be saved\n */\nexport function shouldSaveDraftBeforeAddingToLesson(\n questionsCount: number,\n activityDraftId: string | null | undefined\n): boolean {\n return questionsCount > 0 || !!activityDraftId;\n}\n\n/**\n * Check if should use custom callback for adding activity\n *\n * @param onAddActivityToLesson - Callback function\n * @param activityDraftId - Activity draft ID\n * @returns True if custom callback should be used\n */\nexport function shouldUseCustomAddActivityCallback(\n onAddActivityToLesson: ((id: string) => void) | undefined,\n activityDraftId: string | null | undefined\n): boolean {\n return !!(onAddActivityToLesson && activityDraftId);\n}\n\n/**\n * Check if should add activity to lesson draft automatically\n *\n * @param activityDraftId - Activity draft ID\n * @param recommendedLessonDraftId - Recommended lesson draft ID\n * @returns True if should add automatically\n */\nexport function shouldAddActivityToLessonDraft(\n activityDraftId: string | null | undefined,\n recommendedLessonDraftId: string | undefined\n): boolean {\n return !!(activityDraftId && recommendedLessonDraftId);\n}\n","import { SkeletonText, Skeleton, SkeletonCard } from '../../..';\n\n/**\n * Loading skeleton component for ActivityCreate page\n *\n * @returns Skeleton JSX element\n */\nexport const ActivityCreateSkeleton = () => {\n return (\n <div\n data-testid=\"create-activity-page\"\n className=\"flex flex-col w-full h-screen overflow-hidden p-5 bg-background\"\n >\n {/* Header Section Skeleton */}\n <div className=\"w-full h-[80px] flex flex-row items-center justify-between px-6 gap-3 flex-shrink-0\">\n <section className=\"text-text-950\">\n <Skeleton variant=\"rectangular\" width={32} height={32} />\n </section>\n\n <section className=\"flex flex-col gap-0.5 w-full\">\n <div className=\"flex flex-row items-center justify-between w-full text-text-950\">\n <SkeletonText width={180} height={24} />\n <div className=\"flex flex-row gap-4 items-center\">\n <SkeletonText width={150} height={16} />\n <Skeleton variant=\"rounded\" width={120} height={32} />\n <Skeleton variant=\"rounded\" width={140} height={32} />\n </div>\n </div>\n <SkeletonText width={400} height={16} />\n </section>\n </div>\n\n {/* Main Content with 3 columns - Skeleton */}\n <div className=\"flex flex-row w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* First Column - Filters Skeleton */}\n <div className=\"flex flex-col gap-3 overflow-hidden h-full min-h-0 max-h-full relative w-[400px] flex-shrink-0 p-4 bg-background\">\n <SkeletonText width={150} height={20} />\n <div className=\"flex flex-col gap-4 mt-4\">\n <div className=\"flex flex-col gap-3\">\n <SkeletonText width={120} height={16} />\n <div className=\"grid grid-cols-2 gap-2\">\n {[1, 2, 3, 4].map((i) => (\n <Skeleton\n variant=\"rounded\"\n key={i}\n width=\"100%\"\n height={32}\n />\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-3\">\n <SkeletonText width={140} height={16} />\n <div className=\"flex flex-col gap-2\">\n {[1, 2].map((i) => (\n <Skeleton\n variant=\"rounded\"\n key={i}\n width=\"100%\"\n height={40}\n />\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-3\">\n <SkeletonText width={80} height={16} />\n <div className=\"grid grid-cols-3 gap-3\">\n {[1, 2].map((i) => (\n <Skeleton\n variant=\"rounded\"\n key={i}\n width=\"100%\"\n height={60}\n />\n ))}\n </div>\n </div>\n </div>\n <div className=\"mt-auto\">\n <Skeleton variant=\"rounded\" width=\"100%\" height={40} />\n </div>\n </div>\n\n {/* Second Column - Question List Skeleton */}\n <div className=\"flex-1 min-w-0 overflow-hidden h-full p-4\">\n <div className=\"flex flex-col gap-4 h-full\">\n <div className=\"flex flex-row items-center justify-between\">\n <div className=\"flex flex-row items-center gap-2\">\n <Skeleton variant=\"rectangular\" width={24} height={24} />\n <SkeletonText width={150} height={20} />\n </div>\n <Skeleton variant=\"rounded\" width={180} height={32} />\n </div>\n <div className=\"flex flex-col gap-3 flex-1 overflow-auto\">\n {[1, 2, 3, 4, 5].map((i) => (\n <SkeletonCard key={i} lines={3} />\n ))}\n </div>\n </div>\n </div>\n\n {/* Third Column - Activity Preview Skeleton */}\n <div className=\"w-[470px] flex-shrink-0 overflow-hidden h-full min-h-0 p-4\">\n <div className=\"flex flex-col gap-4 h-full\">\n <div className=\"flex flex-col gap-2\">\n <SkeletonText width={200} height={20} />\n <SkeletonText width={150} height={16} />\n </div>\n <div className=\"flex flex-col gap-3 flex-1 overflow-auto\">\n {[1, 2, 3, 4].map((i) => (\n <div\n key={i}\n className=\"p-4 border border-border-200 rounded-lg\"\n >\n <SkeletonText lines={2} spacing=\"small\" />\n <div className=\"mt-3 flex gap-2\">\n <Skeleton variant=\"rounded\" width={60} height={24} />\n <Skeleton variant=\"rounded\" width={80} height={24} />\n </div>\n </div>\n ))}\n </div>\n <Skeleton variant=\"rounded\" width=\"100%\" height={36} />\n </div>\n </div>\n </div>\n </div>\n );\n};\n","import { Button, Text } from '../../..';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport { EyeIcon } from '@phosphor-icons/react/dist/csr/Eye';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport type { ActivityData } from '../ActivityCreate.types';\nimport { ActivityType } from '../ActivityCreate.types';\nimport { getActivityTypeLabel } from '../ActivityCreate.utils';\nimport { formatTime } from '../../../utils/categoryDataUtils';\n\n/**\n * Header component for ActivityCreate page\n * Displays title, save status, and action buttons\n *\n * @param props - Component props\n * @returns Header JSX element\n */\nexport const ActivityCreateHeader = ({\n activity,\n activityType,\n lastSavedAt,\n isSaving,\n questionsCount,\n onSaveModel,\n onSendActivity,\n onBack,\n isRecommendedLessonMode,\n onLessonPreview,\n onAddActivity,\n enableExamMode = false,\n}: {\n activity?: ActivityData;\n activityType: ActivityType;\n lastSavedAt: Date | null;\n isSaving: boolean;\n questionsCount: number;\n onSaveModel: () => void;\n onSendActivity: () => void;\n onBack?: () => void;\n isRecommendedLessonMode?: boolean;\n onLessonPreview?: () => void;\n onAddActivity?: () => void;\n /** Enable exam mode - changes text labels from 'atividade' to 'prova' */\n enableExamMode?: boolean;\n}) => {\n const activityTypeLabel = getActivityTypeLabel(activityType);\n\n // Text labels based on exam mode\n const labels = enableExamMode\n ? {\n create: 'Criar prova',\n edit: 'Editar prova',\n send: 'Enviar prova',\n description:\n 'Crie uma prova customizada adicionando questões manualmente ou automaticamente.',\n }\n : {\n create: 'Criar atividade',\n edit: 'Editar atividade',\n send: 'Enviar atividade',\n description:\n 'Crie uma atividade customizada adicionando questões manualmente ou automaticamente.',\n };\n\n return (\n <div className=\"w-full h-[80px] flex flex-row items-center justify-between px-4 gap-3 flex-shrink-0\">\n <Button\n onClick={onBack}\n aria-label=\"Voltar\"\n type=\"button\"\n variant=\"link\"\n data-testid=\"back-button\"\n className=\"px-0\"\n >\n <CaretLeftIcon size={32} />\n </Button>\n\n <section className=\"flex flex-col gap-0.5 w-full\">\n <div className=\"flex flex-row items-center justify-between w-full text-text-950\">\n <Text size=\"lg\" weight=\"bold\">\n {activity ? labels.edit : labels.create}\n </Text>\n\n <div className=\"flex flex-row gap-4 items-center\">\n {lastSavedAt ? (\n <Text size=\"sm\">\n {activityTypeLabel} salvo às {formatTime(lastSavedAt)}\n </Text>\n ) : (\n <Text size=\"sm\">\n {isSaving ? 'Salvando...' : 'Nenhum rascunho salvo'}\n </Text>\n )}\n <Button\n size=\"small\"\n variant=\"outline\"\n onClick={onSaveModel}\n disabled={!activity || isSaving}\n >\n Salvar modelo\n </Button>\n {isRecommendedLessonMode ? (\n <>\n <Button\n size=\"small\"\n iconLeft={<EyeIcon />}\n onClick={onLessonPreview}\n variant=\"outline\"\n >\n Prévia da aula\n </Button>\n <Button\n size=\"small\"\n iconLeft={<PlusIcon />}\n onClick={onAddActivity}\n disabled={questionsCount === 0}\n >\n Adicionar atividade\n </Button>\n </>\n ) : (\n <Button\n size=\"small\"\n iconLeft={<PaperPlaneTiltIcon />}\n onClick={onSendActivity}\n disabled={questionsCount === 0}\n >\n {labels.send}\n </Button>\n )}\n </div>\n </div>\n\n <Text size=\"sm\">{labels.description}</Text>\n </section>\n </div>\n );\n};\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { NotebookIcon } from '@phosphor-icons/react/dist/csr/Notebook';\nimport {\n ActivityCardQuestionBanks,\n Button,\n EmptyState,\n Input,\n Modal,\n Search,\n Text,\n useTheme,\n SkeletonText,\n BaseApiClient,\n} from '../..';\nimport {\n useQuestionFiltersStore,\n type QuestionFiltersState,\n createUseQuestionsList,\n type QuestionActivity as Question,\n} from '../..';\nimport { convertActivityFiltersToQuestionsFilter } from '../../utils/questionFiltersConverter';\nimport { mapQuestionTypeToEnumRequired } from '../../utils/questionTypeUtils';\nimport { areFiltersEqual } from '../../utils/activityFilters';\nimport { normalizeText, highlightSearchTerm } from '../../utils/stringUtils';\nimport Activities from '../../assets/icons/Activities';\n\ninterface ActivityListQuestionsProps {\n apiClient: BaseApiClient;\n onAddQuestion?: (question: Question) => void;\n addedQuestionIds?: string[];\n className?: string;\n /** Enable exam mode - changes text labels from 'atividade' to 'prova' */\n enableExamMode?: boolean;\n}\n\n/**\n * Component that displays the list of questions from the API\n * Fetches and displays questions based on applied filters\n * Uses ActivityCardQuestionBanks for displaying questions from the bank\n */\nexport const ActivityListQuestions = ({\n apiClient,\n onAddQuestion,\n addedQuestionIds = [],\n className,\n enableExamMode = false,\n}: ActivityListQuestionsProps) => {\n const { isDark } = useTheme();\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [questionCount, setQuestionCount] = useState<number>(1);\n const [searchTerm, setSearchTerm] = useState('');\n const [isPrefetchingAll, setIsPrefetchingAll] = useState(false);\n const prefetchDoneRef = useRef(false);\n const appliedFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.appliedFilters\n );\n const cachedQuestions = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.cachedQuestions\n );\n const cachedPagination = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.cachedPagination\n );\n const cachedFilters = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.cachedFilters\n );\n const setCachedQuestions = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.setCachedQuestions\n );\n const clearCachedQuestions = useQuestionFiltersStore(\n (state: QuestionFiltersState) => state.clearCachedQuestions\n );\n const addedQuestionIdsRef = useRef(addedQuestionIds);\n\n // Update ref when addedQuestionIds changes to capture latest value\n useEffect(() => {\n addedQuestionIdsRef.current = addedQuestionIds;\n }, [addedQuestionIds]);\n\n // Memoize the hook factory to prevent recreation on every render\n const useQuestionsList = useMemo(\n () => createUseQuestionsList(apiClient),\n [apiClient]\n );\n\n const {\n questions: allQuestions,\n pagination,\n loading,\n loadingMore,\n error,\n fetchQuestions,\n fetchRandomQuestions,\n loadMore,\n reset,\n } = useQuestionsList();\n\n /**\n * Check if we already have a valid cache result for current filters\n * This is true even if the result is empty (0 questions)\n */\n const hasValidCacheResult = useMemo(() => {\n if (!appliedFilters || !cachedFilters) return false;\n return areFiltersEqual(appliedFilters, cachedFilters);\n }, [appliedFilters, cachedFilters]); // cachedPagination removed: only used as null guard, cachedFilters already guards\n\n /**\n * Check if cached questions match current filters AND have data\n * Used for displaying cached data while loading or for pagination\n */\n const filtersMatchCache = useMemo(() => {\n if (!hasValidCacheResult) return false;\n return cachedQuestions.length > 0;\n }, [hasValidCacheResult, cachedQuestions]);\n\n const questions = useMemo(() => {\n let sourceQuestions: typeof allQuestions;\n\n const hasFreshData = !loading && pagination !== null;\n\n if (hasFreshData) {\n const shouldUseCacheForPagination =\n filtersMatchCache &&\n cachedQuestions.length > allQuestions.length &&\n pagination.page === 1 &&\n allQuestions.length > 0;\n\n sourceQuestions = shouldUseCacheForPagination\n ? cachedQuestions\n : allQuestions;\n } else if (loading && filtersMatchCache && cachedQuestions.length > 0) {\n // While loading, show cached data only if filters match\n sourceQuestions = cachedQuestions;\n } else if (filtersMatchCache && cachedQuestions.length > 0) {\n sourceQuestions = cachedQuestions;\n } else {\n sourceQuestions = allQuestions;\n }\n\n return sourceQuestions.filter(\n (question) => !addedQuestionIds.includes(question.id)\n );\n }, [\n allQuestions,\n cachedQuestions,\n filtersMatchCache,\n addedQuestionIds,\n loading,\n pagination,\n ]);\n\n // Use hook's pagination if it has more pages loaded, otherwise use cached\n // This ensures we track progress when loading more pages via infinite scroll\n const effectivePagination = useMemo(() => {\n if (filtersMatchCache && pagination && cachedPagination) {\n // Only compare pages when cache is valid for current filters\n // Prefer hook's pagination if it has loaded more pages\n return pagination.page >= cachedPagination.page\n ? pagination\n : cachedPagination;\n }\n if (filtersMatchCache && cachedPagination) {\n return cachedPagination;\n }\n return pagination;\n }, [pagination, cachedPagination, filtersMatchCache]);\n\n const effectivePaginationRef = useRef(effectivePagination);\n useEffect(() => {\n effectivePaginationRef.current = effectivePagination;\n }, [effectivePagination]);\n\n const scrollContainerRef = useRef<HTMLDivElement>(null);\n const lastLoadedPageRef = useRef<number>(1);\n\n /**\n * Convert question options to the format expected by ActivityCardQuestionBanks\n */\n const formatQuestionOptions = (\n questionOptions: {\n id: string;\n option: string;\n isCorrect?: boolean;\n correctValue?: string | null;\n }[]\n ) => {\n return questionOptions.map((opt) => ({\n id: opt.id,\n option: opt.option,\n isCorrect: opt.isCorrect,\n correctValue: opt.correctValue,\n }));\n };\n\n /**\n * Get subject info from knowledge matrix\n */\n const getSubjectInfo = (question: Question) => {\n if (!question.knowledgeMatrix || question.knowledgeMatrix.length === 0) {\n return { content: 'Sem assunto', color: '#6B7280', icon: 'BookOpen' };\n }\n\n const matrix = question.knowledgeMatrix[0];\n const subject = matrix.subject;\n const topic = matrix.topic;\n const subtopic = matrix.subtopic;\n if (!subject) {\n return { content: 'Sem assunto', color: '#6B7280', icon: 'BookOpen' };\n }\n\n const parts = [subject.name];\n if (topic?.name) parts.push(topic.name);\n if (subtopic?.name) parts.push(subtopic.name);\n\n return {\n content: parts.join(' - '),\n color: subject.color || '#6B7280',\n icon: subject.icon || 'BookOpen',\n };\n };\n\n /**\n * Filtered questions based on in-memory search term\n */\n const displayedQuestions = useMemo(() => {\n if (!searchTerm) return questions;\n const term = normalizeText(searchTerm);\n return questions.filter((q) => {\n const subjectText = getSubjectInfo(q).content;\n const bankName = q.questionBankYear?.questionBank?.name ?? '';\n const year = String(q.questionBankYear?.year ?? '');\n return (\n normalizeText(subjectText).includes(term) ||\n normalizeText(q.statement ?? '').includes(term) ||\n normalizeText(bankName).includes(term) ||\n normalizeText(year).includes(term)\n );\n });\n }, [questions, searchTerm]);\n\n /**\n * Initialize from cache if available and filters match\n * Only fetch if cache is invalid or filters changed\n */\n useEffect(() => {\n // Reset page tracking when filters change\n lastLoadedPageRef.current = 1;\n // Reset search state when filters change\n setSearchTerm('');\n prefetchDoneRef.current = false;\n\n if (appliedFilters) {\n if (hasValidCacheResult) {\n // Update page ref to match cached pagination\n if (cachedPagination?.page) {\n lastLoadedPageRef.current = cachedPagination.page;\n }\n return;\n }\n\n const apiFilters = {\n ...convertActivityFiltersToQuestionsFilter(appliedFilters),\n ...(addedQuestionIdsRef.current.length > 0 && {\n selectedQuestionsIds: addedQuestionIdsRef.current,\n }),\n };\n fetchQuestions(apiFilters, false);\n } else {\n reset();\n clearCachedQuestions();\n }\n }, [\n appliedFilters,\n fetchQuestions,\n reset,\n hasValidCacheResult,\n clearCachedQuestions,\n ]); // cachedPagination intentionally excluded: it changes every page load and would wipe searchTerm on each scroll\n\n useEffect(() => {\n if (appliedFilters && pagination) {\n setCachedQuestions(allQuestions, pagination, appliedFilters);\n lastLoadedPageRef.current = pagination.page;\n }\n }, [allQuestions, pagination, appliedFilters, setCachedQuestions]);\n\n /**\n * Update lastLoadedPageRef when pagination page changes (confirms successful load)\n * This allows retry if a load fails, since we only update the ref on success\n */\n useEffect(() => {\n if (effectivePagination?.page) {\n lastLoadedPageRef.current = effectivePagination.page;\n }\n }, [effectivePagination?.page]);\n\n /**\n * Pre-fetch all remaining pages when user starts typing a search term.\n * Only triggers once per set of applied filters (tracked by prefetchDoneRef).\n * If all pages are already loaded (single page), marks done immediately.\n */\n useEffect(() => {\n if (!searchTerm || !appliedFilters || prefetchDoneRef.current) return;\n\n const pag = effectivePaginationRef.current;\n const total = pag?.total ?? 0;\n const pageSize = pag?.pageSize ?? 10;\n\n // Only prefetch if there are multiple pages\n if (total <= pageSize || !pag?.hasNext) {\n prefetchDoneRef.current = true;\n return;\n }\n\n let cancelled = false;\n\n const prefetchAll = async () => {\n setIsPrefetchingAll(true);\n prefetchDoneRef.current = true; // mark immediately to avoid re-entry\n\n try {\n const apiFilters = appliedFilters\n ? convertActivityFiltersToQuestionsFilter(appliedFilters)\n : undefined;\n\n const totalPages = pag?.totalPages ?? 1;\n const currentPage = pag?.page ?? 1;\n\n for (let page = currentPage + 1; page <= totalPages; page++) {\n if (cancelled) break;\n await fetchQuestions({ ...apiFilters, page }, true);\n }\n } finally {\n if (!cancelled) setIsPrefetchingAll(false);\n }\n };\n\n prefetchAll();\n return () => {\n // Reset so a cancelled prefetch never freezes the UI or blocks the next search\n cancelled = true;\n prefetchDoneRef.current = false;\n setIsPrefetchingAll(false);\n };\n }, [searchTerm, appliedFilters, fetchQuestions]); // effectivePagination intentionally excluded: read via ref to avoid cancelling the loop on each page load\n\n /**\n * Calculate progressive scroll threshold based on current page\n * - Pages 1-4: 80%, 85%, 90%, 95% (increase by 5%)\n * - Pages 5-8: 96%, 97%, 98%, 99% (increase by 1%)\n * - Pages 9+: 99.1%, 99.2%, ... (increase by 0.1%, max 99.9%)\n */\n const calculateScrollThreshold = useCallback((page: number): number => {\n if (page <= 4) {\n // Pages 1-4: 80%, 85%, 90%, 95%\n return 0.8 + (page - 1) * 0.05;\n } else if (page <= 8) {\n // Pages 5-8: 96%, 97%, 98%, 99%\n return 0.95 + (page - 4) * 0.01;\n } else {\n // Pages 9+: 99.1%, 99.2%, ... max 99.9%\n return Math.min(0.99 + (page - 8) * 0.001, 0.999);\n }\n }, []);\n\n /**\n * Scroll event listener for infinite scroll\n * Loads more questions when user scrolls close to the end\n */\n useEffect(() => {\n const container = scrollContainerRef.current;\n if (!container) return;\n\n const handleScroll = () => {\n const { scrollTop, scrollHeight, clientHeight } = container;\n const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;\n\n const currentPage = effectivePagination?.page ?? 1;\n const nextPage = currentPage + 1;\n\n const scrollThreshold = calculateScrollThreshold(currentPage);\n\n if (\n scrollPercentage >= scrollThreshold &&\n !loading &&\n !loadingMore &&\n effectivePagination?.hasNext &&\n lastLoadedPageRef.current < nextPage\n ) {\n const apiFilters = appliedFilters\n ? convertActivityFiltersToQuestionsFilter(appliedFilters)\n : undefined;\n loadMore(apiFilters, effectivePagination ?? undefined);\n }\n };\n\n container.addEventListener('scroll', handleScroll);\n\n return () => {\n container.removeEventListener('scroll', handleScroll);\n };\n }, [loading, loadingMore, effectivePagination, loadMore, appliedFilters]);\n\n const totalQuestions = effectivePagination?.total || 0;\n const displayedCount = searchTerm\n ? displayedQuestions.length\n : totalQuestions;\n const uniqueQuestion = (count = displayedCount) =>\n count === 1 ? 'questão' : 'questões';\n\n const getStatusText = () => {\n if (loading && !isPrefetchingAll && displayedQuestions.length === 0) {\n return 'Carregando...';\n }\n if (isPrefetchingAll) {\n const agreement = displayedCount === 1 ? 'encontrada' : 'encontradas';\n return `Buscando... (${displayedCount} ${uniqueQuestion(displayedCount)} ${agreement})`;\n }\n return `${displayedCount} ${uniqueQuestion(displayedCount)} total`;\n };\n\n /**\n * Handle adding questions automatically using random search\n */\n const handleAddAutomatically = async () => {\n if (questionCount <= 0) return;\n\n try {\n // Get current filters or empty filters\n const baseFilters = appliedFilters\n ? convertActivityFiltersToQuestionsFilter(appliedFilters)\n : {};\n\n // Fetch random questions excluding already added ones\n const randomQuestions = await fetchRandomQuestions(questionCount, {\n ...baseFilters,\n ...(addedQuestionIds.length > 0 && {\n selectedQuestionsIds: addedQuestionIds,\n }),\n });\n\n // Add each question to the activity\n randomQuestions.forEach((question) => {\n if (onAddQuestion) {\n onAddQuestion(question);\n }\n });\n\n setIsModalOpen(false);\n setQuestionCount(1);\n } catch (error) {\n console.error('Erro ao adicionar questões automaticamente:', error);\n }\n };\n\n /**\n * Handle modal close\n */\n const handleCloseModal = () => {\n setIsModalOpen(false);\n setQuestionCount(1);\n };\n\n /**\n * Renders the appropriate content based on loading, error, and questions state\n */\n const renderQuestionsContent = () => {\n // During prefetch, if no matches found yet in loaded pages, show skeleton instead of blank\n if (isPrefetchingAll && displayedQuestions.length === 0) {\n return (\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n );\n }\n\n if (loading && displayedQuestions.length === 0 && !searchTerm) {\n return (\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <Text size=\"md\" className=\"text-text-600\">\n Erro ao carregar questões: {error}\n </Text>\n </div>\n );\n }\n\n if (displayedQuestions.length === 0 && !isPrefetchingAll) {\n if (searchTerm) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <Text size=\"md\" className=\"text-text-600\">\n Nenhuma questão encontrada para "{searchTerm}".\n </Text>\n </div>\n );\n }\n return (\n <EmptyState\n image={<Activities />}\n title=\"Nenhum resultado encontrado\"\n description=\"Utilize o filtro ao lado para encontrar questões.\"\n size=\"compact\"\n />\n );\n }\n\n return (\n <>\n {displayedQuestions.map((question) => {\n const subjectInfo = getSubjectInfo(question);\n const questionType = mapQuestionTypeToEnumRequired(\n question.questionType\n );\n\n return (\n <ActivityCardQuestionBanks\n key={question.id}\n question={\n question.options\n ? {\n options: formatQuestionOptions(\n question.options as {\n id: string;\n option: string;\n isCorrect?: boolean;\n correctValue?: string | null;\n }[]\n ),\n correctOptionIds: [],\n }\n : undefined\n }\n questionType={questionType}\n iconName={subjectInfo.icon}\n subjectColor={subjectInfo.color}\n isDark={isDark}\n content={subjectInfo.content}\n bank={question.questionBankYear?.questionBank?.name}\n year={question.questionBankYear?.year}\n statement={\n searchTerm\n ? highlightSearchTerm(question.statement ?? '', searchTerm)\n : question.statement\n }\n additionalContent={question.additionalContent}\n onAddToActivity={() => {\n if (onAddQuestion) {\n onAddQuestion(question);\n }\n }}\n enableExamMode={enableExamMode}\n />\n );\n })}\n {loadingMore && (\n <div className=\"flex flex-col gap-2 py-4\">\n {[1, 2].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n )}\n </>\n );\n };\n\n return (\n <div\n className={`w-full flex flex-col p-4 gap-2 overflow-hidden h-full min-h-0 ${className || ''}`}\n >\n <div className=\"flex flex-col gap-2 flex-shrink-0\">\n <section className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 text-text-950\">\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <NotebookIcon size={24} />\n <Text size=\"lg\" weight=\"bold\">\n Banco de questões\n </Text>\n </div>\n {appliedFilters && (\n <Search\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n onSearch={(val) => setSearchTerm(val)}\n onClear={() => setSearchTerm('')}\n options={[]}\n showDropdown={false}\n placeholder=\"Buscar questão\"\n debounceMs={300}\n containerClassName=\"w-full sm:w-64 sm:max-w-xs max-w-full\"\n />\n )}\n </section>\n\n <section className=\"flex flex-row justify-between items-center\">\n <Text size=\"sm\" className=\"text-text-800\">\n {getStatusText()}\n </Text>\n\n <Button\n size=\"small\"\n onClick={() => setIsModalOpen(true)}\n disabled={totalQuestions === 0}\n >\n Adicionar automaticamente\n </Button>\n </section>\n </div>\n\n <div\n ref={scrollContainerRef}\n className=\"flex flex-col gap-3 overflow-auto flex-1 min-h-0\"\n >\n {renderQuestionsContent()}\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={handleCloseModal}\n title=\"Adicionar automaticamente\"\n size=\"md\"\n hideCloseButton={true}\n contentClassName=\"p-0\"\n footer={\n <div className=\"flex gap-3\">\n <Button variant=\"outline\" onClick={handleCloseModal}>\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n onClick={handleAddAutomatically}\n disabled={questionCount <= 0 || !appliedFilters}\n >\n Adicionar\n </Button>\n </div>\n }\n >\n <div className=\"flex flex-col\">\n <div className=\"px-6 py-6 flex flex-col gap-4\">\n <Text size=\"sm\" className=\"text-text-600\">\n Defina a quantidade de questões que você quer que o sistema\n adicione automaticamente na sua{' '}\n {enableExamMode ? 'prova' : 'atividade'}\n </Text>\n\n <div className=\"flex flex-col gap-2\">\n <Input\n type=\"number\"\n min={1}\n value={questionCount > 0 ? questionCount : ''}\n onChange={(e) => {\n const inputValue = e.target.value;\n if (inputValue === '') {\n setQuestionCount(0);\n return;\n }\n const numValue = Number.parseInt(inputValue, 10);\n if (!Number.isNaN(numValue) && numValue > 0) {\n setQuestionCount(numValue);\n }\n }}\n placeholder=\"Insira o número\"\n variant=\"outlined\"\n />\n </div>\n </div>\n </div>\n </Modal>\n </div>\n );\n};\n\nexport type { ActivityListQuestionsProps };\n","import type { ActivityFiltersData } from '../types/activityFilters';\nimport type { QuestionsFilterBody } from '../types/questions';\n\n/**\n * Converts ActivityFiltersData to QuestionsFilterBody format\n * Maps all filter fields from the activity filters format to the questions API format\n *\n * @param filters - ActivityFiltersData to convert\n * @returns QuestionsFilterBody compatible with the questions API\n */\nexport const convertActivityFiltersToQuestionsFilter = (\n filters: ActivityFiltersData\n): QuestionsFilterBody => {\n return {\n questionType: filters.types,\n questionBankYearId: filters.yearIds.map((yearId) => yearId.slice(0, 36)),\n subjectId: filters.subjectIds,\n topicId: filters.topicIds,\n subtopicId: filters.subtopicIds,\n contentId: filters.contentIds,\n };\n};\n","import { SVGProps } from 'react';\n\n/**\n * Activities icon component\n * Used as empty state illustration for activities/lessons\n * Colors adapt dynamically based on the active theme/institution\n */\nconst Activities = (props: SVGProps<SVGSVGElement>) => {\n return (\n <svg\n width=\"170\"\n height=\"149\"\n viewBox=\"0 0 170 149\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M89.3667 9.473C89.3667 9.473 77.1619 41.3982 39.8478 36.3167C2.53371 31.2352 -16.8803 82.6742 19.0483 99.0364C54.9769 115.399 31.9755 137.772 55.8546 147.217C82.9951 157.953 90.5613 116.584 110.263 119.29C153.745 125.264 179.543 109.433 166.691 90.7764C144.132 58.0272 129.208 60.1097 134.069 37.8797C138.93 15.6497 102.625 -16.0411 89.3667 9.473Z\"\n style={{ fill: 'var(--color-primary-50)' }}\n />\n <path\n d=\"M113.265 59.0178L94.8904 40.6428C94.6464 40.3991 94.3568 40.2059 94.0382 40.0741C93.7195 39.9424 93.378 39.8747 93.0332 39.875H61.5332C60.1408 39.875 58.8055 40.4281 57.8209 41.4127C56.8363 42.3973 56.2832 43.7326 56.2832 45.125V102.875C56.2832 104.267 56.8363 105.603 57.8209 106.587C58.8055 107.572 60.1408 108.125 61.5332 108.125H108.783C110.176 108.125 111.511 107.572 112.496 106.587C113.48 105.603 114.033 104.267 114.033 102.875V60.875C114.033 60.5302 113.966 60.1887 113.834 59.87C113.702 59.5514 113.509 59.2618 113.265 59.0178ZM95.6582 48.8361L105.072 58.25H95.6582V48.8361ZM108.783 102.875H61.5332V45.125H90.4082V60.875C90.4082 61.5712 90.6848 62.2389 91.177 62.7312C91.6693 63.2234 92.337 63.5 93.0332 63.5H108.783V102.875Z\"\n style={{ fill: 'var(--color-primary-950)' }}\n />\n <path\n d=\"M63.7845 35.9952C63.314 35.9419 62.8821 35.7043 62.5797 35.3324C62.2773 34.9605 62.128 34.4834 62.1633 34.0014L63.1381 15.8225C63.1962 14.7366 64.0944 13.9116 65.1137 14.0076L66.5339 14.1419C67.5532 14.2379 68.2829 15.2159 68.1404 16.2942L65.7449 34.3388C65.6898 34.8195 65.4553 35.2599 65.0901 35.5685C64.7249 35.877 64.2573 36.0298 63.7845 35.9952Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n <path\n d=\"M106.261 113.005C106.81 113.058 107.314 113.296 107.666 113.668C108.019 114.039 108.193 114.517 108.152 114.999L107.015 133.177C106.947 134.263 105.899 135.088 104.71 134.992L103.053 134.858C101.864 134.762 101.013 133.784 101.179 132.706L103.974 114.661C104.038 114.181 104.312 113.74 104.738 113.432C105.164 113.123 105.709 112.97 106.261 113.005Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n <path\n d=\"M54.271 37.9522C54.1208 37.9958 53.9635 38.0098 53.8079 37.9933C53.6522 37.9767 53.5014 37.9301 53.3641 37.8559C53.2267 37.7818 53.1055 37.6816 53.0072 37.5611C52.909 37.4406 52.8358 37.3022 52.7917 37.1537L49.2197 25.9169C49.1158 25.5973 49.1444 25.25 49.2992 24.9513C49.4539 24.6525 49.7222 24.4267 50.0452 24.3233L50.9701 24.0474C51.1288 24.0016 51.2953 23.9888 51.4592 24.0099C51.6232 24.0309 51.7809 24.0854 51.9224 24.1697C52.064 24.2541 52.1864 24.3666 52.2817 24.5001C52.3771 24.6335 52.4435 24.7851 52.4766 24.9452L55.1241 36.4581C55.1979 36.7676 55.151 37.0932 54.993 37.3701C54.8349 37.6469 54.5771 37.8548 54.271 37.9522Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n <path\n d=\"M115.045 111.048C115.196 111.004 115.353 110.99 115.509 111.007C115.664 111.023 115.815 111.07 115.952 111.144C116.09 111.218 116.211 111.318 116.309 111.439C116.407 111.559 116.481 111.698 116.525 111.846L120.097 123.083C120.201 123.403 120.172 123.75 120.017 124.049C119.862 124.347 119.594 124.573 119.271 124.677L118.346 124.953C118.188 124.998 118.021 125.011 117.857 124.99C117.693 124.969 117.536 124.915 117.394 124.83C117.252 124.746 117.13 124.633 117.035 124.5C116.939 124.366 116.873 124.215 116.84 124.055L114.192 112.542C114.119 112.232 114.165 111.907 114.323 111.63C114.482 111.353 114.739 111.145 115.045 111.048Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n <path\n d=\"M49.7801 44.4344C49.1084 45.1964 47.7573 45.1874 46.7173 44.4137L28.2521 30.6786C27.1492 29.8581 26.8237 28.5001 27.5365 27.6922L28.5297 26.5656C29.2425 25.7574 30.7063 25.8236 31.75 26.7114L49.2218 41.5733C50.2058 42.4102 50.4519 43.6723 49.7801 44.4344Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n <path\n d=\"M119.536 104.566C120.208 103.804 121.559 103.813 122.599 104.586L141.064 118.321C142.167 119.142 142.493 120.5 141.78 121.308L140.787 122.434C140.074 123.243 138.61 123.176 137.566 122.289L120.095 107.427C119.111 106.59 118.864 105.328 119.536 104.566Z\"\n style={{ fill: 'var(--color-primary-600)' }}\n />\n </svg>\n );\n};\n\nexport default Activities;\n","import {\n ActivityFilters,\n ActivityFiltersPopover,\n ActivityPreview,\n Button,\n SkeletonText,\n QUESTION_TYPE,\n Divider,\n} from '../../..';\nimport Menu, { MenuContent, MenuItem } from '../../Menu/Menu';\nimport { ActivityListQuestions } from '../../ActivityListQuestions/ActivityListQuestions';\nimport type {\n ActivityFiltersData,\n BaseApiClient,\n PreviewQuestion,\n QuestionActivity as Question,\n} from '../../..';\n\ninterface LoadingSkeletonProps {\n className?: string;\n}\n\n/**\n * Loading skeleton for activity preview\n */\nexport const LoadingSkeleton = ({ className }: LoadingSkeletonProps) => (\n <div className={`flex flex-col gap-4 p-4 ${className || ''}`}>\n <div className=\"flex flex-col gap-2\">\n <SkeletonText lines={1} width={200} />\n <SkeletonText lines={1} width={150} />\n </div>\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n </div>\n);\n\ninterface SmallScreenLayoutProps {\n apiClient: BaseApiClient;\n institutionId: string;\n isDark: boolean;\n selectedView: 'questions' | 'preview';\n onViewChange: (view: 'questions' | 'preview') => void;\n initialFiltersData: ActivityFiltersData | null;\n onFiltersChange: (filters: ActivityFiltersData) => void;\n onApplyFilters: () => void;\n onClearFilters: () => void;\n onAddQuestion: (question: Question) => void;\n addedQuestionIds: string[];\n enableExamMode: boolean;\n isInPersonExam: boolean;\n loadingInitialQuestions: boolean;\n questions: PreviewQuestion[];\n onRemoveAll: () => void;\n onRemoveQuestion: (questionId: string) => void;\n onReorder: (questions: PreviewQuestion[]) => void;\n filtersKey?: number;\n}\n\n/**\n * Small screen layout for activity creation (<= 1200px)\n */\nexport const SmallScreenLayout = ({\n apiClient,\n institutionId,\n isDark,\n selectedView,\n onViewChange,\n initialFiltersData,\n onFiltersChange,\n onApplyFilters,\n onClearFilters,\n onAddQuestion,\n addedQuestionIds,\n enableExamMode,\n isInPersonExam,\n loadingInitialQuestions,\n questions,\n onRemoveAll,\n onRemoveQuestion,\n onReorder,\n filtersKey = 0,\n}: SmallScreenLayoutProps) => (\n <div className=\"flex flex-col w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* Filters and Menu Row */}\n <div className=\"flex flex-row items-center justify-between gap-4 flex-shrink-0\">\n <ActivityFiltersPopover\n key={filtersKey}\n apiClient={apiClient}\n institutionId={institutionId}\n onFiltersChange={onFiltersChange}\n initialFilters={initialFiltersData || undefined}\n triggerLabel=\"Filtro de questões\"\n onApplyFilters={onApplyFilters}\n onClearFilters={onClearFilters}\n allowedQuestionTypes={\n isInPersonExam ? [QUESTION_TYPE.ALTERNATIVA] : undefined\n }\n />\n <div className=\"flex-shrink-0\">\n <Menu\n defaultValue=\"questions\"\n value={selectedView}\n onValueChange={(value) =>\n onViewChange(value as 'questions' | 'preview')\n }\n variant=\"breadcrumb\"\n >\n <MenuContent variant=\"breadcrumb\">\n <MenuItem value=\"questions\" variant=\"breadcrumb\">\n Banco de questões\n </MenuItem>\n <MenuItem value=\"preview\" variant=\"breadcrumb\">\n {enableExamMode ? 'Prévia da prova' : 'Prévia da atividade'}\n </MenuItem>\n </MenuContent>\n </Menu>\n </div>\n </div>\n\n {/* Content Area - Single Column */}\n <div className=\"flex-1 min-w-0 relative\">\n {selectedView === 'questions' ? (\n <div className=\"absolute inset-0 overflow-hidden\">\n <ActivityListQuestions\n apiClient={apiClient}\n onAddQuestion={onAddQuestion}\n addedQuestionIds={addedQuestionIds}\n enableExamMode={enableExamMode}\n />\n </div>\n ) : (\n <div className=\"absolute inset-0 overflow-hidden\">\n {loadingInitialQuestions ? (\n <LoadingSkeleton />\n ) : (\n <ActivityPreview\n questions={questions}\n onRemoveAll={onRemoveAll}\n onRemoveQuestion={onRemoveQuestion}\n onReorder={onReorder}\n isDark={isDark}\n className=\"h-full overflow-y-auto\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n);\n\ninterface DesktopLayoutProps {\n apiClient: BaseApiClient;\n institutionId: string;\n isDark: boolean;\n initialFiltersData: ActivityFiltersData | null;\n draftFilters: ActivityFiltersData | null;\n onFiltersChange: (filters: ActivityFiltersData) => void;\n onApplyFilters: () => void;\n onClearFilters: () => void;\n onAddQuestion: (question: Question) => void;\n addedQuestionIds: string[];\n enableExamMode: boolean;\n isInPersonExam: boolean;\n loadingInitialQuestions: boolean;\n questions: PreviewQuestion[];\n onRemoveAll: () => void;\n onRemoveQuestion: (questionId: string) => void;\n onReorder: (questions: PreviewQuestion[]) => void;\n filtersKey?: number;\n}\n\n/**\n * Desktop layout for activity creation (> 1200px)\n */\nexport const DesktopLayout = ({\n apiClient,\n institutionId,\n isDark,\n initialFiltersData,\n draftFilters,\n onFiltersChange,\n onApplyFilters,\n onClearFilters,\n onAddQuestion,\n addedQuestionIds,\n enableExamMode,\n isInPersonExam,\n loadingInitialQuestions,\n questions,\n onRemoveAll,\n onRemoveQuestion,\n onReorder,\n filtersKey = 0,\n}: DesktopLayoutProps) => (\n <div className=\"flex flex-row w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* First Column - Filters */}\n {/*\n overflow-clip (not overflow-hidden): an overflow-hidden box is still a\n scroll container, so when a descendant (accordion/checkbox) receives focus\n while the content transiently overflows, the browser scrolls this box via\n scrollIntoView. With no scrollbar the user can't scroll back and the filter\n appears blank until a zoom forces a reflow. overflow-clip clips identically\n but never becomes scrollable, so the offset can't get stuck.\n */}\n <div className=\"flex flex-col gap-3 overflow-clip h-full min-h-0 max-h-full relative w-[400px] flex-shrink-0\">\n <div className=\"flex flex-col overflow-y-auto overflow-x-hidden flex-1 min-h-0 max-h-full\">\n <ActivityFilters\n key={filtersKey}\n apiClient={apiClient}\n institutionId={institutionId}\n variant={'default'}\n onFiltersChange={onFiltersChange}\n initialFilters={initialFiltersData || undefined}\n allowedQuestionTypes={\n isInPersonExam ? [QUESTION_TYPE.ALTERNATIVA] : undefined\n }\n />\n </div>\n <div className=\"flex-shrink-0 grid grid-cols-2 gap-2\">\n <Button size=\"medium\" variant=\"link\" onClick={onClearFilters}>\n Limpar filtros\n </Button>\n <Button\n size=\"medium\"\n variant=\"outline\"\n onClick={onApplyFilters}\n disabled={!draftFilters}\n >\n Filtrar\n </Button>\n </div>\n </div>\n\n <Divider orientation=\"vertical\" />\n\n {/* Second Column - Center, fills remaining space */}\n <div className=\"flex-1 min-w-0 relative\">\n <div className=\"absolute inset-0 overflow-hidden\">\n <ActivityListQuestions\n apiClient={apiClient}\n onAddQuestion={onAddQuestion}\n addedQuestionIds={addedQuestionIds}\n enableExamMode={enableExamMode}\n />\n </div>\n </div>\n\n <Divider orientation=\"vertical\" />\n\n {/* Third Column - Activity Preview */}\n <div className=\"w-[400px] flex-shrink-0 overflow-hidden h-full min-h-0\">\n {loadingInitialQuestions ? (\n <LoadingSkeleton />\n ) : (\n <ActivityPreview\n questions={questions}\n onRemoveAll={onRemoveAll}\n onRemoveQuestion={onRemoveQuestion}\n onReorder={onReorder}\n isDark={isDark}\n className=\"h-full overflow-y-auto\"\n />\n )}\n </div>\n </div>\n);\n","import { useCallback, useEffect, useMemo, useState, useRef } from 'react';\nimport { useTabletScreen } from '../../hooks/useScreen';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Button,\n SkeletonText,\n CategoryConfig,\n useToastStore,\n SendLessonModal,\n SaveActivityModelModal,\n Divider,\n} from '../..';\nimport type { ActivityModelTableItem } from '../../types/activitiesHistory';\nimport { ActivityType } from '../ActivityCreate/ActivityCreate.types';\nimport { MenuItem, MenuOverflow } from '../Menu/Menu';\nimport type { BaseApiClient } from '../..';\nimport type { LessonFiltersData } from '../../types/lessonFilters';\nimport type { Lesson } from '../../types/lessons';\nimport type { SendLessonFormData } from '../SendLessonModal';\nimport { LessonFilters } from '../LessonFilters/LessonFilters';\nimport {\n LessonBank,\n type LessonFilters as LessonBankFilters,\n} from '../LessonBank/LessonBank';\nimport {\n LessonPreview,\n type PreviewLesson,\n} from '../LessonPreview/LessonPreview';\nimport {\n useLessonFiltersStore,\n type LessonFiltersState,\n} from '../../store/lessonFiltersStore';\nimport { areLessonFiltersEqual } from '../../utils/lessonFilters';\nimport type {\n RecommendedLessonDraftResponse,\n RecommendedLessonData,\n LessonBackendFiltersFormat,\n RecommendedLessonPreFiltersInput,\n RecommendedLessonCreatePayload,\n RecommendedLessonCreateResponse,\n} from './RecommendedLessonCreate.types';\nimport { RecommendedClassDraftType } from './RecommendedLessonCreate.types';\nimport {\n convertFiltersToBackendFormat,\n convertBackendFiltersToLessonFiltersData,\n generateTitle,\n getTypeFromUrl,\n getTypeFromUrlString,\n convertLessonToPreview,\n} from './RecommendedLessonCreate.utils';\nimport { RecommendedLessonCreateSkeleton } from './components/RecommendedLessonCreateSkeleton';\nimport { RecommendedLessonCreateHeader } from './components/RecommendedLessonCreateHeader';\nimport { loadCategoriesData } from '../../utils/categoryDataUtils';\nimport { useDynamicStudentFetching } from '../../utils/useDynamicStudentFetching';\n\n/**\n * RecommendedLessonCreate page component for creating new recommended lessons\n * This page does not use the standard Layout (header/sidebar)\n * @returns JSX element representing the create recommended lesson page\n */\nconst RecommendedLessonCreate = ({\n apiClient,\n institutionId,\n onBack,\n onCreateRecommendedLesson,\n onSaveModel,\n preFilters: preFiltersProp,\n onRedirectToActivity,\n onCreateNewActivity,\n}: {\n apiClient: BaseApiClient;\n institutionId: string;\n onBack?: () => void;\n onCreateRecommendedLesson?: (\n recommendedLessonId: string,\n recommendedLessonData: RecommendedLessonCreatePayload\n ) => void;\n onSaveModel?: (response: RecommendedLessonDraftResponse) => void;\n /** Pre-filters to apply when creating a new recommended lesson */\n preFilters?: RecommendedLessonPreFiltersInput | null;\n onRedirectToActivity?: ({\n activityId,\n activityType,\n lessonId,\n lessonType,\n }: {\n activityId: string;\n activityType: ActivityType;\n lessonId: string;\n lessonType: RecommendedClassDraftType;\n }) => void;\n /** Callback when create new activity is clicked (without existing activity) */\n onCreateNewActivity?: ({\n lessonId,\n lessonType,\n }: {\n lessonId: string;\n lessonType: RecommendedClassDraftType;\n }) => void;\n}) => {\n const [searchParams, setSearchParams] = useSearchParams();\n\n const typeParam = searchParams.get('type') || undefined;\n const idParam = searchParams.get('id') || undefined;\n\n const applyFilters = useLessonFiltersStore(\n (state: LessonFiltersState) => state.applyFilters\n );\n const draftFilters = useLessonFiltersStore(\n (state: LessonFiltersState) => state.draftFilters\n );\n const appliedFilters = useLessonFiltersStore(\n (state: LessonFiltersState) => state.appliedFilters\n );\n const setDraftFilters = useLessonFiltersStore(\n (state: LessonFiltersState) => state.setDraftFilters\n );\n const clearFilters = useLessonFiltersStore(\n (state: LessonFiltersState) => state.clearFilters\n );\n\n const addToast = useToastStore((state) => state.addToast);\n\n // Responsive state for screen width <= 1200px\n const isSmallScreen = useTabletScreen();\n const [selectedView, setSelectedView] = useState<\n 'filters' | 'lessons' | 'preview'\n >('filters');\n\n // Internal states\n const [recommendedLesson, setRecommendedLesson] =\n useState<RecommendedLessonData | null>(null);\n const [preFilters, setPreFilters] =\n useState<RecommendedLessonPreFiltersInput | null>(preFiltersProp ?? null);\n const [loading, setLoading] = useState(false);\n const [lessons, setLessons] = useState<PreviewLesson[]>([]);\n const [loadingInitialLessons, setLoadingInitialLessons] = useState(false);\n const [draftId, setDraftId] = useState<string | null>(idParam ?? null);\n const [draftType, setDraftType] = useState<RecommendedClassDraftType>(\n getTypeFromUrlString(typeParam)\n );\n const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n const [isSendModalOpen, setIsSendModalOpen] = useState(false);\n const [isSaveModelModalOpen, setIsSaveModelModalOpen] = useState(false);\n const [isSavingModel, setIsSavingModel] = useState(false);\n const [categories, setCategories] = useState<CategoryConfig[]>([]);\n const [isSendingLesson, setIsSendingLesson] = useState(false);\n const [filtersKey, setFiltersKey] = useState(0);\n const hasFirstSaveBeenDone = useRef(false);\n const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastSavedFiltersRef = useRef<LessonFiltersData | null>(null);\n const lastSavedLessonsRef = useRef<PreviewLesson[]>([]);\n const hasAppliedInitialFiltersRef = useRef(false);\n const lastFetchedLessonIdRef = useRef<string | null>(null);\n\n // Knowledge areas for title generation\n const [knowledgeAreas, setKnowledgeAreas] = useState<\n { id: string; name: string }[]\n >([]);\n\n const handleFiltersChange = useCallback(\n (filters: LessonFiltersData) => {\n setDraftFilters(filters);\n },\n [setDraftFilters]\n );\n\n const handleApplyFilters = useCallback(() => {\n applyFilters();\n addToast({\n title: 'Filtros aplicados',\n description: 'Banco de aulas atualizado',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n }, [applyFilters, addToast]);\n\n const handleClearFilters = useCallback(() => {\n clearFilters();\n // Force re-render of LessonFilters component by changing key\n setFiltersKey((prev) => prev + 1);\n }, [clearFilters]);\n\n /**\n * Handle back button click - resets everything before calling onBack\n */\n const handleBack = useCallback(() => {\n // Clear filters from store\n clearFilters();\n\n // Call original onBack if provided\n if (onBack) {\n onBack();\n }\n }, [clearFilters, onBack]);\n\n const handleRedirectToActivity = (activity: ActivityModelTableItem) => {\n if (onRedirectToActivity) {\n if (!recommendedLesson?.id || !recommendedLesson?.type) {\n addToast({\n title: 'Erro ao redirecionar para a atividade',\n description: 'A aula recomendada não foi encontrada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n if (!activity.id || !activity.type) {\n addToast({\n title: 'Erro ao redirecionar para a atividade',\n description: 'A atividade não foi encontrada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n onRedirectToActivity({\n activityId: activity.id,\n activityType: activity.type,\n lessonId: recommendedLesson?.id,\n lessonType: recommendedLesson?.type,\n });\n }\n };\n\n const handleActivitySelected = useCallback(\n async (activity: ActivityModelTableItem) => {\n if (!recommendedLesson?.id || !activity.id) {\n addToast({\n title: 'Erro ao adicionar atividade',\n description: 'A aula recomendada ou atividade não foi encontrada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n try {\n setIsSaving(true);\n\n // Build the payload with current data + new activity\n const lessonIds = lessons.map((l, index) => ({\n lessonId: l.id,\n sequence: index + 1,\n }));\n\n const activityDraftIds = [\n {\n activityDraftId: activity.id,\n sequence: 1,\n },\n ];\n\n const payload = {\n type: draftType,\n title: recommendedLesson.title,\n subjectId: recommendedLesson.subjectId,\n filters: recommendedLesson.filters,\n lessonIds,\n activityDraftIds,\n };\n\n // Update the draft\n await apiClient.patch(\n `/recommended-class/drafts/${recommendedLesson.id}`,\n payload\n );\n\n // Update local state\n setRecommendedLesson((prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n activityDraftIds: [activity.id],\n activityDrafts: [\n {\n activityDraftId: activity.id,\n sequence: 1,\n title: activity.title,\n },\n ],\n };\n });\n\n setLastSavedAt(new Date());\n\n addToast({\n title: 'Atividade adicionada',\n description: 'A atividade foi adicionada à aula recomendada',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n } catch (error) {\n console.error('Error adding activity to recommended lesson:', error);\n addToast({\n title: 'Erro ao adicionar atividade',\n description:\n 'Não foi possível adicionar a atividade à aula recomendada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setIsSaving(false);\n }\n },\n [recommendedLesson, lessons, draftType, apiClient, addToast]\n );\n\n const handleRemoveActivity = useCallback(async () => {\n if (!recommendedLesson?.id) {\n addToast({\n title: 'Erro ao remover atividade',\n description: 'A aula recomendada não foi encontrada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n try {\n setIsSaving(true);\n\n // Build the payload with current data + empty activityDraftIds\n const lessonIds = lessons.map((l, index) => ({\n lessonId: l.id,\n sequence: index + 1,\n }));\n\n const payload = {\n type: draftType,\n title: recommendedLesson.title,\n subjectId: recommendedLesson.subjectId,\n filters: recommendedLesson.filters,\n lessonIds,\n activityDraftIds: [], // Empty array to signal removal\n };\n\n // Update the draft\n await apiClient.patch(\n `/recommended-class/drafts/${recommendedLesson.id}`,\n payload\n );\n\n // Update local state\n setRecommendedLesson((prev) => {\n if (!prev) return prev;\n return {\n ...prev,\n activityDraftIds: [],\n activityDrafts: [],\n };\n });\n\n setLastSavedAt(new Date());\n\n addToast({\n title: 'Atividade removida',\n description: 'A atividade foi removida da aula recomendada',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n } catch (error) {\n console.error('Error removing activity from recommended lesson:', error);\n addToast({\n title: 'Erro ao remover atividade',\n description: 'Não foi possível remover a atividade da aula recomendada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setIsSaving(false);\n }\n }, [recommendedLesson, lessons, draftType, apiClient, addToast]);\n\n const handleCreateNewActivity = () => {\n if (onCreateNewActivity) {\n if (!recommendedLesson?.id || !recommendedLesson?.type) {\n addToast({\n title: 'Erro ao criar nova atividade',\n description: 'A aula recomendada não foi encontrada',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return;\n }\n\n onCreateNewActivity({\n lessonId: recommendedLesson.id,\n lessonType: recommendedLesson.type,\n });\n }\n };\n\n const resolvedPreFilters = useMemo(() => {\n if (!preFilters) {\n return null;\n }\n if (\n typeof preFilters === 'object' &&\n preFilters !== null &&\n 'filters' in preFilters\n ) {\n return (preFilters as { filters?: LessonBackendFiltersFormat | null })\n .filters;\n }\n return preFilters as LessonBackendFiltersFormat;\n }, [preFilters]);\n\n const initialFiltersData = useMemo(() => {\n if (recommendedLesson?.filters) {\n return convertBackendFiltersToLessonFiltersData(\n recommendedLesson.filters\n );\n }\n if (resolvedPreFilters) {\n return convertBackendFiltersToLessonFiltersData(resolvedPreFilters);\n }\n return null;\n }, [recommendedLesson?.filters, resolvedPreFilters]);\n\n useEffect(() => {\n hasAppliedInitialFiltersRef.current = false;\n }, [recommendedLesson?.id, recommendedLesson?.filters, resolvedPreFilters]);\n\n /**\n * Update preFilters when prop changes\n */\n useEffect(() => {\n if (preFiltersProp) {\n setPreFilters(preFiltersProp);\n }\n }, [preFiltersProp]);\n\n /**\n * Load knowledge areas on mount\n */\n useEffect(() => {\n const loadKnowledgeAreas = async () => {\n try {\n const response = await apiClient.get<{\n message: string;\n data: { subjects: { id: string; name: string }[] };\n }>('knowledge/subjects');\n setKnowledgeAreas(response.data.data.subjects || []);\n } catch (error) {\n console.error('Error loading knowledge areas:', error);\n }\n };\n loadKnowledgeAreas();\n }, [apiClient]);\n\n /**\n * Fetch the recommended lesson draft when there's an id in the URL\n * Only fetches if the id changed to a different one than the current\n */\n useEffect(() => {\n const fetchRecommendedLessonDraft = async () => {\n if (idParam && idParam !== lastFetchedLessonIdRef.current) {\n setLoading(true);\n try {\n const response = await apiClient.get<RecommendedLessonDraftResponse>(\n `/recommended-class/drafts/${idParam}`\n );\n const draftData = response.data.data;\n\n // Extract lessons with full data if available\n const selectedLessons =\n draftData.lessons\n ?.map((item) => {\n if ('lesson' in item) {\n return item.lesson;\n }\n return null;\n })\n .filter((lesson): lesson is Lesson => lesson !== null) || [];\n\n const lessonData: RecommendedLessonData = {\n id: draftData.id,\n type: draftData.type,\n title: draftData.title,\n description: draftData.description,\n subjectId: draftData.subjectId,\n filters: draftData.filters,\n lessonIds: draftData.lessons?.map((l) => l.lessonId) || [],\n selectedLessons,\n activityDraftIds:\n draftData.activityDrafts?.map((a) => a.activityDraftId) || [],\n activityDrafts: draftData.activityDrafts || [],\n updatedAt: draftData.updatedAt,\n startDate: draftData.startDate,\n finalDate: draftData.finalDate,\n };\n\n setRecommendedLesson(lessonData);\n setPreFilters(lessonData.filters);\n setDraftId(lessonData.id || null);\n setDraftType(lessonData.type);\n if (lessonData.updatedAt) {\n setLastSavedAt(new Date(lessonData.updatedAt));\n }\n lastFetchedLessonIdRef.current = idParam;\n } catch (error) {\n console.error('Error fetching recommended lesson draft:', error);\n addToast({\n title: 'Erro ao carregar aula recomendada',\n description:\n error instanceof Error\n ? error.message\n : 'Ocorreu um erro ao carregar a aula recomendada. Tente novamente.',\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setLoading(false);\n }\n }\n };\n\n fetchRecommendedLessonDraft();\n }, [idParam, apiClient, addToast]);\n\n /**\n * Monitor recommendedLesson.id and recommendedLesson.type and update URL when necessary\n */\n useEffect(() => {\n if (recommendedLesson?.id && recommendedLesson?.type) {\n const urlType = getTypeFromUrl(recommendedLesson.type);\n const currentUrlType = typeParam;\n const currentUrlId = idParam;\n\n if (\n !currentUrlType ||\n !currentUrlId ||\n currentUrlId !== recommendedLesson.id ||\n currentUrlType !== urlType\n ) {\n setSearchParams(\n {\n type: urlType,\n id: recommendedLesson.id,\n },\n { replace: true }\n );\n }\n }\n }, [\n recommendedLesson?.id,\n recommendedLesson?.type,\n typeParam,\n idParam,\n setSearchParams,\n ]);\n\n /**\n * Validate if save conditions are met\n */\n const validateSaveConditions = useCallback((): boolean => {\n if (lessons.length === 0 && !hasFirstSaveBeenDone.current) {\n return false;\n }\n if (!appliedFilters?.subjectIds?.length) {\n return false;\n }\n if (loadingInitialLessons || isSaving) {\n return false;\n }\n return true;\n }, [lessons.length, appliedFilters, loadingInitialLessons, isSaving]);\n\n /**\n * Create draft payload for API request\n */\n const createDraftPayload = useCallback(() => {\n const subjectId = appliedFilters?.subjectIds?.[0];\n if (!subjectId) {\n throw new Error('Subject ID não encontrado');\n }\n const title = generateTitle(draftType, subjectId, knowledgeAreas, lessons);\n const filters = convertFiltersToBackendFormat(appliedFilters);\n const lessonIds = lessons.map((l, index) => ({\n lessonId: l.id,\n sequence: index + 1,\n }));\n\n const activityDraftIds = recommendedLesson?.activityDraftIds\n ? recommendedLesson.activityDraftIds.map((id, index) => ({\n activityDraftId: id,\n sequence: index + 1,\n }))\n : undefined;\n\n return {\n type: draftType,\n title,\n subjectId,\n filters,\n lessonIds,\n ...(activityDraftIds && { activityDraftIds }),\n };\n }, [appliedFilters, draftType, knowledgeAreas, lessons, recommendedLesson]);\n\n /**\n * Update existing draft via PATCH\n */\n const updateExistingDraft = useCallback(\n async (payload: {\n type: RecommendedClassDraftType;\n title: string;\n subjectId: string;\n filters: LessonBackendFiltersFormat;\n lessonIds: { lessonId: string; sequence: number }[];\n }) => {\n const response = await apiClient.patch<RecommendedLessonDraftResponse>(\n `/recommended-class/drafts/${draftId}`,\n payload\n );\n lastSavedLessonsRef.current = lessons;\n if (appliedFilters) {\n lastSavedFiltersRef.current = appliedFilters;\n }\n const savedDraft = response?.data?.data;\n setLastSavedAt(\n savedDraft?.updatedAt ? new Date(savedDraft.updatedAt) : new Date()\n );\n\n if (savedDraft) {\n setRecommendedLesson((prevLesson) => {\n if (prevLesson?.id !== savedDraft.id) {\n return {\n id: savedDraft.id,\n type: savedDraft.type,\n title: savedDraft.title,\n subjectId: savedDraft.subjectId,\n filters: savedDraft.filters,\n lessonIds: lessons.map((l) => l.id),\n updatedAt: savedDraft.updatedAt,\n };\n }\n return {\n ...prevLesson,\n type: savedDraft.type,\n title: savedDraft.title,\n subjectId: savedDraft.subjectId,\n lessonIds: lessons.map((l) => l.id),\n updatedAt: savedDraft.updatedAt,\n };\n });\n setDraftType(savedDraft.type);\n if (savedDraft.id) {\n lastFetchedLessonIdRef.current = savedDraft.id;\n }\n }\n\n if (\n payload.type === RecommendedClassDraftType.MODELO &&\n onSaveModel &&\n response?.data\n ) {\n onSaveModel(response.data);\n }\n },\n [draftId, apiClient, lessons, appliedFilters, onSaveModel]\n );\n\n /**\n * Extract draft from API response\n */\n const extractDraftFromResponse = useCallback(\n (response: {\n data: RecommendedLessonDraftResponse;\n }): RecommendedLessonDraftResponse['data'] => {\n if (!response?.data) {\n console.error('Empty response from API when creating draft:', response);\n throw new Error('Invalid response: empty response from API');\n }\n\n let savedDraft: RecommendedLessonDraftResponse['data'] | undefined;\n\n if (response.data.data) {\n savedDraft = response.data.data;\n } else if (\n response.data &&\n 'id' in response.data &&\n typeof response.data === 'object'\n ) {\n savedDraft =\n response.data as unknown as RecommendedLessonDraftResponse['data'];\n }\n\n if (!savedDraft?.id) {\n console.error('Invalid API response when creating draft:', {\n response,\n responseData: response?.data,\n responseDataKeys: response?.data ? Object.keys(response.data) : [],\n });\n throw new Error(\n 'Invalid response: draft data is missing. Expected structure: response.data.data or response.data'\n );\n }\n\n return savedDraft;\n },\n []\n );\n\n /**\n * Update component state after successful draft save\n */\n const updateStateAfterSave = useCallback(\n (\n savedDraft: RecommendedLessonDraftResponse['data'],\n fullResponse?: RecommendedLessonDraftResponse,\n wasNewDraft = false\n ) => {\n setDraftId(savedDraft.id);\n setLastSavedAt(\n savedDraft.updatedAt ? new Date(savedDraft.updatedAt) : new Date()\n );\n lastSavedLessonsRef.current = lessons;\n if (appliedFilters) {\n lastSavedFiltersRef.current = appliedFilters;\n }\n\n const updatedLesson: RecommendedLessonData = {\n id: savedDraft.id,\n type: savedDraft.type,\n title: savedDraft.title,\n subjectId: savedDraft.subjectId,\n filters: savedDraft.filters,\n lessonIds: lessons.map((l) => l.id),\n updatedAt: savedDraft.updatedAt,\n };\n setRecommendedLesson(updatedLesson);\n setDraftType(savedDraft.type);\n lastFetchedLessonIdRef.current = savedDraft.id;\n\n if (wasNewDraft && savedDraft.id) {\n const urlType = getTypeFromUrl(savedDraft.type);\n setSearchParams(\n {\n type: urlType,\n id: savedDraft.id,\n },\n { replace: true }\n );\n }\n\n if (\n savedDraft.type === RecommendedClassDraftType.MODELO &&\n onSaveModel &&\n fullResponse\n ) {\n onSaveModel(fullResponse);\n }\n },\n [lessons, appliedFilters, onSaveModel, setSearchParams]\n );\n\n /**\n * Save draft to backend\n * @param typeOverride - Override the draft type for this save\n * @param customTitle - Optional user-provided title; falls back to auto-generated when empty\n * @returns The persisted draft ID (existing or newly created) on success.\n * Returns undefined when no persistence happened — either because the save\n * failed or because validateSaveConditions skipped it. Callers triggering\n * an explicit override (e.g. \"save as MODELO\") rely on this to gate\n * success feedback so they don't show a toast for a save that never ran.\n */\n const saveDraft = useCallback(\n async (\n typeOverride?: RecommendedClassDraftType,\n customTitle?: string\n ): Promise<string | undefined> => {\n if (!validateSaveConditions()) {\n // For explicit override flows, skipped saves must not signal success.\n return typeOverride ? undefined : draftId || undefined;\n }\n\n setIsSaving(true);\n\n try {\n let payload = createDraftPayload();\n\n // Override type if provided\n if (typeOverride) {\n const subjectId = appliedFilters?.subjectIds?.[0];\n if (!subjectId) {\n throw new Error('Subject ID não encontrado');\n }\n const trimmedCustomTitle = customTitle?.trim();\n const title =\n trimmedCustomTitle && trimmedCustomTitle.length > 0\n ? trimmedCustomTitle\n : generateTitle(typeOverride, subjectId, knowledgeAreas, lessons);\n payload = {\n ...payload,\n type: typeOverride,\n title,\n };\n }\n\n if (draftId) {\n await updateExistingDraft(payload);\n return draftId;\n }\n\n const response = await apiClient.post<RecommendedLessonDraftResponse>(\n '/recommended-class/drafts',\n payload\n );\n hasFirstSaveBeenDone.current = true;\n\n const savedDraft = extractDraftFromResponse(response);\n updateStateAfterSave(savedDraft, response?.data, true);\n return savedDraft.id;\n } catch (error) {\n console.error('Error saving draft:', error);\n\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Ocorreu um erro ao salvar o rascunho. Tente novamente.';\n\n addToast({\n title: 'Erro ao salvar rascunho',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n return undefined;\n } finally {\n setIsSaving(false);\n }\n },\n [\n validateSaveConditions,\n createDraftPayload,\n draftId,\n updateExistingDraft,\n apiClient,\n extractDraftFromResponse,\n updateStateAfterSave,\n addToast,\n appliedFilters,\n knowledgeAreas,\n lessons,\n ]\n );\n\n /**\n * Open the \"save as model\" modal so the user can provide a custom title\n */\n const handleSaveModel = useCallback(() => {\n setIsSaveModelModalOpen(true);\n }, []);\n\n /**\n * Persist the draft as MODELO using the title provided by the user.\n * draftType is updated by the save helpers (updateExistingDraft /\n * updateStateAfterSave) only after the backend confirms the save, keeping\n * UI state consistent with persisted state on failure.\n */\n const handleConfirmSaveModel = useCallback(\n async (title: string) => {\n setIsSavingModel(true);\n try {\n setIsSaveModelModalOpen(false);\n const savedId = await saveDraft(\n RecommendedClassDraftType.MODELO,\n title\n );\n if (savedId) {\n addToast({\n title: 'Modelo salvo com sucesso',\n description: 'O modelo da aula está disponível para reutilização.',\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n }\n } finally {\n setIsSavingModel(false);\n }\n },\n [saveDraft, addToast]\n );\n\n /**\n * Load initial lessons\n */\n useEffect(() => {\n if (\n recommendedLesson?.selectedLessons &&\n recommendedLesson.selectedLessons.length > 0\n ) {\n setLoadingInitialLessons(true);\n try {\n const previewLessons = recommendedLesson.selectedLessons.map((l) =>\n convertLessonToPreview(l)\n );\n setLessons(previewLessons);\n hasFirstSaveBeenDone.current = true;\n lastSavedLessonsRef.current = previewLessons;\n } catch (error) {\n console.error('Error converting lessons:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Ocorreu um erro ao carregar as aulas. Tente novamente.';\n addToast({\n title: 'Erro ao carregar aulas',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setLoadingInitialLessons(false);\n }\n }\n }, [recommendedLesson?.selectedLessons, addToast]);\n\n /**\n * Initialize filters from recommended lesson (edit mode) or preFilters (create mode)\n */\n useEffect(() => {\n if (hasAppliedInitialFiltersRef.current) {\n return;\n }\n\n if (!initialFiltersData) {\n return;\n }\n\n setDraftFilters(initialFiltersData);\n applyFilters();\n lastSavedFiltersRef.current = initialFiltersData;\n hasAppliedInitialFiltersRef.current = true;\n }, [initialFiltersData, setDraftFilters, applyFilters]);\n\n const saveDraftRef = useRef(saveDraft);\n useEffect(() => {\n saveDraftRef.current = saveDraft;\n }, [saveDraft]);\n\n /**\n * Auto-save draft when lessons or filters change (with debounce)\n */\n useEffect(() => {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n\n if (loadingInitialLessons) {\n return;\n }\n\n if (lessons.length === 0 && !hasFirstSaveBeenDone.current) {\n return;\n }\n\n if (!appliedFilters) {\n return;\n }\n\n const lessonIds = lessons.map((l) => l.id).join(',');\n const lastSavedLessonIds = lastSavedLessonsRef.current\n .map((l) => l.id)\n .join(',');\n const lessonsChanged = lessonIds !== lastSavedLessonIds;\n\n const filtersChanged = !areLessonFiltersEqual(\n lastSavedFiltersRef.current,\n appliedFilters\n );\n\n if (!lessonsChanged && !filtersChanged && hasFirstSaveBeenDone.current) {\n return;\n }\n\n saveTimeoutRef.current = setTimeout(() => {\n saveDraftRef.current();\n }, 500);\n\n return () => {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n };\n }, [lessons, appliedFilters, draftType, loadingInitialLessons]);\n\n /**\n * Handle adding a lesson to the recommended lesson\n */\n const handleAddLesson = useCallback((lesson: Lesson) => {\n setLessons((prev) => {\n if (prev.some((l) => l.id === lesson.id)) {\n return prev;\n }\n\n try {\n const previewLesson = convertLessonToPreview(lesson);\n return [...prev, previewLesson];\n } catch (error) {\n console.error('Error converting lesson to preview:', {\n lessonId: lesson.id,\n error: error instanceof Error ? error.message : String(error),\n });\n return prev;\n }\n });\n }, []);\n\n /**\n * Handle removing all lessons\n */\n const handleRemoveAll = useCallback(() => {\n setLessons([]);\n }, []);\n\n /**\n * Handle removing a single lesson\n */\n const handleRemoveLesson = useCallback((lessonId: string) => {\n setLessons((prev) => prev.filter((l) => l.id !== lessonId));\n }, []);\n\n /**\n * Handle reordering lessons\n */\n const handleReorder = useCallback(\n (orderedLessons: PreviewLesson[]) => {\n setLessons(orderedLessons);\n const hasSubjectIds =\n Array.isArray(appliedFilters?.subjectIds) &&\n appliedFilters.subjectIds.length > 0;\n if (\n hasFirstSaveBeenDone.current &&\n hasSubjectIds &&\n !loadingInitialLessons &&\n !isSaving\n ) {\n if (saveTimeoutRef.current) {\n clearTimeout(saveTimeoutRef.current);\n }\n saveDraftRef.current();\n }\n },\n [appliedFilters, loadingInitialLessons, isSaving]\n );\n\n /**\n * Load categories data from API\n */\n const handleLoadCategoriesData = useCallback(async () => {\n if (categories.length > 0) {\n return;\n }\n\n try {\n const transformedCategories = await loadCategoriesData(\n apiClient,\n categories\n );\n setCategories(transformedCategories);\n } catch (error) {\n console.error('Error loading categories:', error);\n throw error;\n }\n }, [apiClient, categories]);\n\n /**\n * Handle categories change and fetch students dynamically\n */\n const { handleCategoriesChange } = useDynamicStudentFetching(setCategories, {\n apiClient,\n });\n\n /**\n * Handle opening the send lesson modal\n */\n const handleOpenSendModal = useCallback(async () => {\n try {\n if (categories.length === 0) {\n await handleLoadCategoriesData();\n }\n setIsSendModalOpen(true);\n } catch (error) {\n console.error('Error opening send modal:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Erro ao carregar dados. Por favor, tente novamente.';\n addToast({\n title: 'Erro ao carregar dados',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n }\n }, [categories.length, handleLoadCategoriesData, addToast]);\n\n /**\n * Handle sending recommended lesson to students\n */\n const handleSendLesson = useCallback(\n async (formData: SendLessonFormData) => {\n setIsSendingLesson(true);\n try {\n const subjectId =\n recommendedLesson?.subjectId || appliedFilters?.subjectIds?.[0];\n\n const startDateTime = new Date(\n `${formData.startDate}T${formData.startTime}`\n ).toISOString();\n const finalDateTime = new Date(\n `${formData.finalDate}T${formData.finalTime}`\n ).toISOString();\n\n const activityDraftIds = recommendedLesson?.activityDraftIds\n ? recommendedLesson.activityDraftIds.map((id, index) => ({\n activityDraftId: id,\n sequence: index + 1,\n }))\n : undefined;\n\n const hasAttachedActivities =\n !!activityDraftIds && activityDraftIds.length > 0;\n\n const lessonPayload: RecommendedLessonCreatePayload = {\n title:\n formData.title || recommendedLesson?.title || 'Aula Recomendada',\n subjectId: subjectId || null,\n lessonIds: lessons.map((l, index) => ({\n lessonId: l.id,\n sequence: index + 1,\n })),\n ...(activityDraftIds && { activityDraftIds }),\n startDate: startDateTime,\n finalDate: finalDateTime,\n targetStudentIds: formData.students.map((s) => s.userInstitutionId),\n // Send notification as separate field (backend expects \"notification\")\n ...(formData.notification && { notification: formData.notification }),\n // Forward retry flag only when activities are actually attached\n // — backend ignores it otherwise and it would just be noise.\n ...(hasAttachedActivities && {\n canRetry: formData.canRetry ?? false,\n }),\n };\n\n // POST: Create recommended lesson\n const createResponse =\n await apiClient.post<RecommendedLessonCreateResponse>(\n '/recommended-class',\n lessonPayload\n );\n\n const lessonId =\n createResponse?.data?.data?.id ||\n createResponse?.data?.data?.recommendedClass?.id;\n if (!lessonId) {\n throw new Error('ID da aula recomendada não retornado pela API');\n }\n\n addToast({\n title: 'Aula enviada com sucesso!',\n description: `Alunos afetados: ${formData.students.length}`,\n variant: 'solid',\n action: 'success',\n position: 'top-right',\n });\n // Call callback if provided\n if (onCreateRecommendedLesson) {\n onCreateRecommendedLesson(lessonId, lessonPayload);\n }\n\n // Success: close modal\n setIsSendModalOpen(false);\n } catch (error) {\n console.error('Error sending recommended lesson:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Ocorreu um erro ao enviar a aula. Tente novamente.';\n addToast({\n title: 'Erro ao enviar aula',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n throw error;\n } finally {\n setIsSendingLesson(false);\n }\n },\n [\n recommendedLesson,\n appliedFilters,\n lessons,\n apiClient,\n addToast,\n onCreateRecommendedLesson,\n handleBack,\n ]\n );\n\n const addedLessonIds = useMemo(() => lessons.map((l) => l.id), [lessons]);\n\n // Convert activityDrafts to ActivityModelTableItem format for LessonPreview\n const selectedActivityForPreview =\n useMemo((): ActivityModelTableItem | null => {\n const activityDraft = recommendedLesson?.activityDrafts?.[0];\n if (!activityDraft) return null;\n\n return {\n id: activityDraft.activityDraftId,\n title: activityDraft.title || 'Atividade sem título',\n type: ActivityType.MODELO,\n savedAt: new Date().toISOString(),\n subject: null,\n subjectId: null,\n };\n }, [recommendedLesson?.activityDrafts]);\n\n // Convert appliedFilters to LessonBankFilters format\n const lessonBankFilters: LessonBankFilters | undefined = useMemo(() => {\n if (!appliedFilters) return undefined;\n return {\n subjectId: appliedFilters.subjectIds,\n topicIds: appliedFilters.topicIds,\n subtopicIds: appliedFilters.subtopicIds,\n contentIds: appliedFilters.contentIds,\n selectedIds: addedLessonIds,\n };\n }, [appliedFilters, addedLessonIds]);\n\n // Render loading skeleton\n if (loading) {\n return <RecommendedLessonCreateSkeleton />;\n }\n\n return (\n <div\n data-testid=\"create-recommended-class-page\"\n className=\"flex flex-col w-full h-screen overflow-hidden p-5 bg-background\"\n >\n {/* Header Section */}\n <RecommendedLessonCreateHeader\n recommendedLesson={recommendedLesson || undefined}\n draftType={draftType}\n lastSavedAt={lastSavedAt}\n isSaving={isSaving}\n lessonsCount={lessons.length}\n onSaveModel={handleSaveModel}\n onSendLesson={handleOpenSendModal}\n onBack={handleBack}\n />\n\n {/* Main Content */}\n {isSmallScreen ? (\n /* Small Screen Layout (<= 1200px) */\n <div className=\"flex flex-col w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* Tabs row */}\n <div className=\"flex flex-row items-center justify-between gap-4 flex-shrink-0 min-w-0\">\n <div className=\"flex-1 min-w-0\">\n <MenuOverflow\n defaultValue=\"filters\"\n value={selectedView}\n onValueChange={(value: string) =>\n setSelectedView(value as 'filters' | 'lessons' | 'preview')\n }\n >\n <MenuItem value=\"filters\" variant=\"menu2\">\n Filtros\n </MenuItem>\n <MenuItem value=\"lessons\" variant=\"menu2\">\n Banco de aulas\n </MenuItem>\n <MenuItem value=\"preview\" variant=\"menu2\">\n Prévia da aula\n </MenuItem>\n </MenuOverflow>\n </div>\n </div>\n\n {/* Content Area - Single Column */}\n <div className=\"flex-1 min-w-0 overflow-hidden h-full\">\n {selectedView === 'filters' && (\n <div className=\"flex flex-col gap-3 overflow-hidden h-full min-h-0\">\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n <LessonFilters\n key={filtersKey}\n apiClient={apiClient}\n institutionId={institutionId}\n variant={'default'}\n onFiltersChange={handleFiltersChange}\n initialFilters={initialFiltersData || undefined}\n onClearFilters={handleClearFilters}\n onApplyFilters={handleApplyFilters}\n />\n </div>\n <div className=\"flex-shrink-0 grid grid-cols-2 gap-2\">\n <Button\n size=\"medium\"\n variant=\"link\"\n onClick={handleClearFilters}\n >\n Limpar filtros\n </Button>\n <Button\n size=\"medium\"\n variant=\"outline\"\n onClick={handleApplyFilters}\n disabled={!draftFilters}\n >\n Filtrar\n </Button>\n </div>\n </div>\n )}\n {selectedView === 'lessons' && (\n <LessonBank\n apiClient={apiClient}\n onAddLesson={handleAddLesson}\n addedLessonIds={addedLessonIds}\n filters={lessonBankFilters}\n />\n )}\n {selectedView === 'preview' && (\n <div className=\"w-full h-full overflow-hidden min-h-0\">\n {loadingInitialLessons ? (\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <SkeletonText lines={1} width={200} />\n <SkeletonText lines={1} width={150} />\n </div>\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n </div>\n ) : (\n <LessonPreview\n lessons={lessons}\n onRemoveAll={handleRemoveAll}\n onRemoveLesson={handleRemoveLesson}\n onReorder={handleReorder}\n apiClient={apiClient}\n selectedActivity={selectedActivityForPreview}\n onActivitySelected={handleActivitySelected}\n onRemoveActivity={handleRemoveActivity}\n onEditActivity={handleRedirectToActivity}\n onCreateNewActivity={handleCreateNewActivity}\n className=\"h-full overflow-y-auto\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n ) : (\n /* Desktop Layout (> 1200px) - 3 columns */\n <div className=\"flex flex-row w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* First Column - Filters */}\n <div className=\"flex flex-col gap-3 overflow-hidden h-full min-h-0 max-h-full relative w-[400px] flex-shrink-0\">\n <div className=\"flex flex-col overflow-y-auto overflow-x-hidden flex-1 min-h-0 max-h-full\">\n <LessonFilters\n key={filtersKey}\n apiClient={apiClient}\n institutionId={institutionId}\n variant={'default'}\n onFiltersChange={handleFiltersChange}\n initialFilters={initialFiltersData || undefined}\n onClearFilters={handleClearFilters}\n onApplyFilters={handleApplyFilters}\n />\n </div>\n <div className=\"flex-shrink-0 grid grid-cols-2 gap-2\">\n <Button size=\"medium\" variant=\"link\" onClick={handleClearFilters}>\n Limpar filtros\n </Button>\n <Button\n size=\"medium\"\n variant=\"outline\"\n onClick={handleApplyFilters}\n disabled={!draftFilters}\n >\n Filtrar\n </Button>\n </div>\n </div>\n\n <Divider orientation=\"vertical\" />\n\n {/* Second Column - Center, fills remaining space */}\n <div className=\"flex-1 min-w-0 overflow-hidden h-full\">\n <LessonBank\n apiClient={apiClient}\n onAddLesson={handleAddLesson}\n addedLessonIds={addedLessonIds}\n filters={lessonBankFilters}\n />\n </div>\n\n <Divider orientation=\"vertical\" />\n\n {/* Third Column - Lesson Preview */}\n <div className=\"w-[400px] flex-shrink-0 overflow-hidden h-full min-h-0\">\n {loadingInitialLessons ? (\n <div className=\"flex flex-col gap-4 p-4\">\n <div className=\"flex flex-col gap-2\">\n <SkeletonText lines={1} width={200} />\n <SkeletonText lines={1} width={150} />\n </div>\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n </div>\n ) : (\n <LessonPreview\n lessons={lessons}\n onRemoveAll={handleRemoveAll}\n onRemoveLesson={handleRemoveLesson}\n onReorder={handleReorder}\n apiClient={apiClient}\n selectedActivity={selectedActivityForPreview}\n onActivitySelected={handleActivitySelected}\n onRemoveActivity={handleRemoveActivity}\n onEditActivity={handleRedirectToActivity}\n onCreateNewActivity={handleCreateNewActivity}\n className=\"h-full overflow-y-auto\"\n />\n )}\n </div>\n </div>\n )}\n\n {/* Save Lesson Model Modal */}\n <SaveActivityModelModal\n isOpen={isSaveModelModalOpen}\n onClose={() => setIsSaveModelModalOpen(false)}\n onConfirm={handleConfirmSaveModel}\n isLoading={isSavingModel}\n />\n\n {/* Send Lesson Modal */}\n <SendLessonModal\n isOpen={isSendModalOpen}\n onClose={() => setIsSendModalOpen(false)}\n onSubmit={handleSendLesson}\n categories={categories}\n onCategoriesChange={handleCategoriesChange}\n isLoading={isSendingLesson}\n hasAttachedActivities={\n (recommendedLesson?.activityDraftIds?.length ?? 0) > 0\n }\n onError={(error) => {\n console.error('Error sending lesson:', error);\n const errorMessage =\n error instanceof Error\n ? error.message\n : 'Erro ao enviar aula. Por favor, tente novamente.';\n addToast({\n title: 'Erro ao enviar aula',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n }}\n />\n </div>\n );\n};\n\nexport { RecommendedLessonCreate };\nexport { RecommendedClassDraftType } from './RecommendedLessonCreate.types';\nexport type {\n LessonBackendFiltersFormat,\n RecommendedLessonDraftResponse,\n RecommendedLessonData,\n RecommendedLessonPreFiltersInput,\n RecommendedLessonCreatePayload,\n RecommendedLessonCreateResponse,\n School,\n SchoolYear,\n Class,\n Student,\n} from './RecommendedLessonCreate.types';\n","import { useState, useEffect, useMemo, useCallback, useRef } from 'react';\nimport Text from '../Text/Text';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseActivityFiltersData } from '../../hooks/useActivityFiltersData';\nimport type { LessonFiltersData } from '../../types/lessonFilters';\nimport {\n getSelectedIdsFromCategories,\n toggleSingleValue,\n} from '../../utils/activityFilters';\nimport { areLessonFiltersEqual } from '../../utils/lessonFilters';\nimport {\n SubjectsFilter,\n KnowledgeStructureFilter,\n FilterActions,\n} from '../ActivityFilters/components';\nimport {\n useInitialFiltersLoader,\n useKnowledgeStructureInitialFilters,\n} from '../ActivityFilters/utils';\n\nexport interface LessonFiltersProps {\n apiClient: BaseApiClient;\n onFiltersChange: (filters: LessonFiltersData) => void;\n variant?: 'default' | 'popover';\n institutionId?: string | null;\n initialFilters?: LessonFiltersData | null;\n onClearFilters?: () => void;\n onApplyFilters?: () => void;\n}\n\n/**\n * LessonFilters component for filtering lessons\n * Manages subjects and knowledge structure selections\n */\nexport const LessonFilters = ({\n apiClient,\n onFiltersChange,\n variant = 'default',\n institutionId = null,\n initialFilters = null,\n onClearFilters,\n onApplyFilters,\n}: LessonFiltersProps) => {\n const useActivityFiltersData = createUseActivityFiltersData(apiClient);\n\n const [selectedSubject, setSelectedSubject] = useState<string | null>(null);\n\n const {\n knowledgeAreas,\n loadingSubjects,\n subjectsError,\n knowledgeStructure,\n knowledgeCategories,\n handleCategoriesChange,\n loadKnowledgeAreas,\n loadTopics,\n loadSubtopics,\n loadContents,\n } = useActivityFiltersData({\n selectedSubjects: selectedSubject ? [selectedSubject] : [],\n institutionId,\n });\n\n const hasAppliedBasicInitialFiltersRef = useRef(false);\n\n // Use shared hook for loading topics/subtopics/contents\n useInitialFiltersLoader({\n initialFilters,\n loadTopics,\n loadSubtopics,\n loadContents,\n });\n\n // Use shared hook for knowledge structure initial filters\n useKnowledgeStructureInitialFilters({\n initialFilters,\n knowledgeCategories,\n handleCategoriesChange,\n });\n\n useEffect(() => {\n hasAppliedBasicInitialFiltersRef.current = false;\n }, [initialFilters]);\n\n useEffect(() => {\n if (!initialFilters || hasAppliedBasicInitialFiltersRef.current) {\n return;\n }\n\n if (initialFilters.subjectIds && initialFilters.subjectIds.length > 0) {\n setSelectedSubject(initialFilters.subjectIds[0]);\n }\n\n hasAppliedBasicInitialFiltersRef.current = true;\n }, [initialFilters]);\n\n useEffect(() => {\n if (loadKnowledgeAreas) {\n loadKnowledgeAreas();\n }\n }, [loadKnowledgeAreas, institutionId]);\n\n const handleSubjectChange = (subjectId: string) => {\n setSelectedSubject(toggleSingleValue(selectedSubject, subjectId));\n };\n\n const selectedSubjects = useMemo(\n () => (selectedSubject ? [selectedSubject] : []),\n [selectedSubject]\n );\n\n const getSelectedKnowledgeIds = useCallback(() => {\n return getSelectedIdsFromCategories(knowledgeCategories, {\n topicIds: 'tema',\n subtopicIds: 'subtema',\n contentIds: 'assunto',\n });\n }, [knowledgeCategories]);\n\n const onFiltersChangeRef = useRef(onFiltersChange);\n useEffect(() => {\n onFiltersChangeRef.current = onFiltersChange;\n }, [onFiltersChange]);\n\n const prevFiltersRef = useRef<LessonFiltersData | null>(null);\n\n useEffect(() => {\n const knowledgeIds = getSelectedKnowledgeIds();\n const filters: LessonFiltersData = {\n subjectIds: selectedSubjects,\n topicIds: knowledgeIds.topicIds,\n subtopicIds: knowledgeIds.subtopicIds,\n contentIds: knowledgeIds.contentIds,\n };\n\n if (!areLessonFiltersEqual(prevFiltersRef.current, filters)) {\n prevFiltersRef.current = filters;\n onFiltersChangeRef.current(filters);\n }\n }, [selectedSubjects, knowledgeCategories, getSelectedKnowledgeIds]);\n\n const containerClassName =\n variant === 'popover'\n ? 'w-full bg-background'\n : 'w-[400px] flex-shrink-0 p-4 bg-background';\n\n const contentClassName = variant === 'popover' ? 'p-4' : '';\n\n return (\n <div className={containerClassName}>\n {variant === 'default' && (\n <section className=\"flex flex-row items-center gap-2 text-text-950 mb-4\">\n <Text size=\"lg\" weight=\"bold\">\n Filtro de aulas\n </Text>\n </section>\n )}\n\n <div className={contentClassName}>\n <section className=\"flex flex-col gap-4\">\n <div>\n <div className=\"flex flex-row justify-between items-center mb-3\">\n <Text size=\"sm\" weight=\"bold\">\n Matéria\n </Text>\n </div>\n <SubjectsFilter\n knowledgeAreas={knowledgeAreas}\n selectedSubject={selectedSubject}\n onSubjectChange={handleSubjectChange}\n loading={loadingSubjects}\n error={subjectsError}\n />\n </div>\n\n <KnowledgeStructureFilter\n knowledgeStructure={knowledgeStructure}\n knowledgeCategories={knowledgeCategories}\n handleCategoriesChange={handleCategoriesChange}\n />\n\n {variant === 'popover' && (\n <FilterActions\n onClearFilters={onClearFilters}\n onApplyFilters={onApplyFilters}\n />\n )}\n </section>\n </div>\n </div>\n );\n};\n","import type { LessonFiltersData } from '../types/lessonFilters';\nimport { arraysEqual } from './arraysEqual';\n\n/**\n * Compares two LessonFiltersData objects for equality (order-independent)\n * @param filters1 - First filters object\n * @param filters2 - Second filters object\n * @returns True if filters are equal\n */\nexport function areLessonFiltersEqual(\n filters1: LessonFiltersData | null,\n filters2: LessonFiltersData | null\n): boolean {\n if (filters1 === filters2) return true;\n if (!filters1 || !filters2) return false;\n\n return (\n arraysEqual(filters1.subjectIds, filters2.subjectIds) &&\n arraysEqual(filters1.topicIds, filters2.topicIds) &&\n arraysEqual(filters1.subtopicIds, filters2.subtopicIds) &&\n arraysEqual(filters1.contentIds, filters2.contentIds)\n );\n}\n","import {\n Radio,\n CheckBox,\n IconRender,\n Text,\n TruncatedText,\n useTheme,\n getSubjectColorWithOpacity,\n} from '../../..';\nimport type { KnowledgeArea } from '../../../types/activityFilters';\nimport { ProhibitIcon } from '@phosphor-icons/react/dist/csr/Prohibit';\nimport { GridFourIcon } from '@phosphor-icons/react/dist/csr/GridFour';\n\nexport interface SubjectsFilterProps {\n knowledgeAreas: KnowledgeArea[];\n /** Single-select value (used when `multiple` is false) */\n selectedSubject?: string | null;\n /** Single-select handler (used when `multiple` is false) */\n onSubjectChange?: (subjectId: string) => void;\n loading?: boolean;\n error?: string | null;\n /** Show \"Sem matéria\" option to filter questions without subject */\n showNoSubjectOption?: boolean;\n /** Value to use for \"Sem matéria\" option */\n noSubjectValue?: string;\n /** Enable multi-select: renders CheckBoxes instead of Radios */\n multiple?: boolean;\n /** Selected subject ids (used when `multiple` is true) */\n selectedSubjectIds?: string[];\n /** Toggle handler for a single subject (used when `multiple` is true) */\n onToggleSubject?: (subjectId: string) => void;\n /** Show the \"Todas as matérias\" select-all card (multi-select only) */\n showAllSubjectsOption?: boolean;\n /** Whether every subject is currently selected */\n allSubjectsSelected?: boolean;\n /** Toggle handler for the \"Todas as matérias\" select-all card */\n onToggleAllSubjects?: () => void;\n}\n\n/**\n * SubjectsFilter component for selecting subjects/knowledge areas.\n *\n * Defaults to single-select (Radio). Pass `multiple` to render a multi-select\n * grid (CheckBox per subject) with an optional \"Todas as matérias\" select-all\n * card, keeping the same visual card style as the subjects.\n * @param props - Component props\n * @returns JSX element\n */\nexport const SubjectsFilter = ({\n knowledgeAreas,\n selectedSubject = null,\n onSubjectChange,\n loading = false,\n error = null,\n showNoSubjectOption = false,\n noSubjectValue = '__NO_SUBJECT__',\n multiple = false,\n selectedSubjectIds = [],\n onToggleSubject,\n showAllSubjectsOption = false,\n allSubjectsSelected = false,\n onToggleAllSubjects,\n}: SubjectsFilterProps) => {\n const { isDark } = useTheme();\n\n if (loading) {\n return (\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando matérias...\n </Text>\n );\n }\n\n if (error) {\n return (\n <Text size=\"sm\" className=\"text-text-600\">\n {error}\n </Text>\n );\n }\n\n // Colored icon chip + name — shared by the Radio and CheckBox renderings so\n // both selection modes look identical.\n const renderSubjectLabel = (area: KnowledgeArea) => (\n <div className=\"flex items-center gap-2 w-full min-w-0\">\n <span\n className=\"size-4 rounded-sm flex items-center justify-center shrink-0 text-text-950\"\n style={{\n backgroundColor: getSubjectColorWithOpacity(area.color, isDark),\n }}\n >\n <IconRender\n iconName={area.icon || 'BookOpen'}\n size={14}\n color=\"currentColor\"\n />\n </span>\n <TruncatedText size=\"sm\" weight=\"normal\" wrapperClassName=\"flex-1\">\n {area.name}\n </TruncatedText>\n </div>\n );\n\n if (multiple) {\n const someSelected = selectedSubjectIds.length > 0;\n return (\n <div className=\"grid grid-cols-3 gap-3\">\n {showAllSubjectsOption && (\n <div className=\"flex items-center gap-2 min-w-0\">\n <CheckBox\n id=\"subject-all\"\n checked={allSubjectsSelected}\n indeterminate={!allSubjectsSelected && someSelected}\n onChange={() => onToggleAllSubjects?.()}\n />\n <label\n htmlFor=\"subject-all\"\n className=\"flex items-center gap-2 min-w-0 flex-1 cursor-pointer select-none\"\n >\n <span className=\"size-4 rounded-sm flex items-center justify-center shrink-0 text-text-600 bg-background-100\">\n <GridFourIcon size={14} weight=\"bold\" />\n </span>\n <TruncatedText\n size=\"sm\"\n weight=\"normal\"\n color=\"text-text-600\"\n wrapperClassName=\"flex-1\"\n >\n Todas as matérias\n </TruncatedText>\n </label>\n </div>\n )}\n {knowledgeAreas.map((area: KnowledgeArea) => (\n <div key={area.id} className=\"flex items-center gap-2 min-w-0\">\n <CheckBox\n id={`subject-${area.id}`}\n checked={selectedSubjectIds.includes(area.id)}\n onChange={() => onToggleSubject?.(area.id)}\n />\n <label\n htmlFor={`subject-${area.id}`}\n className=\"flex items-center gap-2 min-w-0 flex-1 cursor-pointer select-none\"\n >\n {renderSubjectLabel(area)}\n </label>\n </div>\n ))}\n </div>\n );\n }\n\n return (\n <div className=\"grid grid-cols-3 gap-3\">\n {showNoSubjectOption && (\n <Radio\n key={noSubjectValue}\n value={noSubjectValue}\n checked={selectedSubject === noSubjectValue}\n onChange={() => onSubjectChange?.(noSubjectValue)}\n label={\n <div className=\"flex items-center gap-2 w-full min-w-0\">\n <span className=\"size-4 rounded-sm flex items-center justify-center shrink-0 text-text-600 bg-background-100\">\n <ProhibitIcon size={14} weight=\"bold\" />\n </span>\n <TruncatedText\n size=\"sm\"\n weight=\"normal\"\n color=\"text-text-600\"\n wrapperClassName=\"flex-1\"\n >\n Sem matéria\n </TruncatedText>\n </div>\n }\n />\n )}\n {knowledgeAreas.map((area: KnowledgeArea) => (\n <Radio\n key={area.id}\n value={area.id}\n checked={selectedSubject === area.id}\n onChange={() => onSubjectChange?.(area.id)}\n label={renderSubjectLabel(area)}\n />\n ))}\n </div>\n );\n};\n","import { Text, CheckboxGroup, type CategoryConfig } from '../../..';\nimport type { KnowledgeStructureState } from '../../../types/activityFilters';\n\nexport interface KnowledgeStructureFilterProps {\n knowledgeStructure: KnowledgeStructureState;\n knowledgeCategories: CategoryConfig[];\n handleCategoriesChange?: (updatedCategories: CategoryConfig[]) => void;\n}\n\n/**\n * KnowledgeStructureFilter component for selecting topics, subtopics, and contents\n * @param props - Component props\n * @returns JSX element\n */\nexport const KnowledgeStructureFilter = ({\n knowledgeStructure,\n knowledgeCategories,\n handleCategoriesChange,\n}: KnowledgeStructureFilterProps) => {\n return (\n <div className=\"mt-4\">\n <Text size=\"sm\" weight=\"bold\" className=\"mb-3 block\">\n Tema, Subtema e Assunto\n </Text>\n {knowledgeStructure.loading && (\n <Text size=\"sm\" className=\"text-text-600 mb-3\">\n Carregando estrutura de conhecimento...\n </Text>\n )}\n {knowledgeStructure.error && (\n <Text size=\"sm\" className=\"mb-3 text-error-500\">\n {knowledgeStructure.error}\n </Text>\n )}\n {knowledgeCategories.length > 0 && handleCategoriesChange && (\n <CheckboxGroup\n categories={knowledgeCategories}\n onCategoriesChange={handleCategoriesChange}\n compactSingleItem={false}\n showSingleItem={true}\n />\n )}\n {!knowledgeStructure.loading &&\n knowledgeCategories.length === 0 &&\n knowledgeStructure.topics.length === 0 && (\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhum tema disponível para as matérias selecionadas\n </Text>\n )}\n </div>\n );\n};\n","import { Button } from '../../..';\n\nexport interface FilterActionsProps {\n onClearFilters?: () => void;\n onApplyFilters?: () => void;\n}\n\n/**\n * FilterActions component for clear and apply filter buttons\n * @param props - Component props\n * @returns JSX element or null if no actions provided\n */\nexport const FilterActions = ({\n onClearFilters,\n onApplyFilters,\n}: FilterActionsProps) => {\n if (!onClearFilters && !onApplyFilters) {\n return null;\n }\n\n return (\n <div className=\"grid grid-cols-2 gap-2 justify-end mt-4 px-4 pt-4 border-t border-border-200\">\n {onClearFilters && (\n <Button variant=\"link\" onClick={onClearFilters} size=\"small\">\n Limpar filtros\n </Button>\n )}\n {onApplyFilters && (\n <Button variant=\"outline\" onClick={onApplyFilters} size=\"small\">\n Filtrar\n </Button>\n )}\n </div>\n );\n};\n","import { useEffect, useRef } from 'react';\n\nexport interface InitialFiltersLoaderParams {\n initialFilters: {\n subjectIds?: string[];\n topicIds?: string[];\n subtopicIds?: string[];\n } | null;\n loadTopics?: (subjectIds: string[]) => void | Promise<void>;\n loadSubtopics?: (topicIds: string[]) => void | Promise<void>;\n loadContents?: (subtopicIds: string[]) => void | Promise<void>;\n}\n\n/**\n * Hook to handle loading of topics, subtopics, and contents based on initialFilters\n * @param params - Parameters for the loader\n */\nexport const useInitialFiltersLoader = ({\n initialFilters,\n loadTopics,\n loadSubtopics,\n loadContents,\n}: InitialFiltersLoaderParams) => {\n const hasRequestedTopicsRef = useRef(false);\n const hasRequestedSubtopicsRef = useRef(false);\n const hasRequestedContentsRef = useRef(false);\n\n useEffect(() => {\n // Reset refs synchronously before any loader checks to avoid race conditions\n hasRequestedTopicsRef.current = false;\n hasRequestedSubtopicsRef.current = false;\n hasRequestedContentsRef.current = false;\n\n if (!initialFilters) {\n return;\n }\n\n const subjectIds = initialFilters.subjectIds || [];\n const topicIds = initialFilters.topicIds || [];\n const subtopicIds = initialFilters.subtopicIds || [];\n\n if (subjectIds.length > 0 && !hasRequestedTopicsRef.current) {\n if (loadTopics) {\n loadTopics(subjectIds);\n }\n hasRequestedTopicsRef.current = true;\n }\n\n if (topicIds.length > 0 && !hasRequestedSubtopicsRef.current) {\n if (loadSubtopics) {\n loadSubtopics(topicIds);\n }\n hasRequestedSubtopicsRef.current = true;\n }\n\n if (subtopicIds.length > 0 && !hasRequestedContentsRef.current) {\n if (loadContents) {\n loadContents(subtopicIds);\n }\n hasRequestedContentsRef.current = true;\n }\n }, [initialFilters, loadTopics, loadSubtopics, loadContents]);\n\n return {\n hasRequestedTopicsRef,\n hasRequestedSubtopicsRef,\n hasRequestedContentsRef,\n };\n};\n","import { useEffect, useRef } from 'react';\nimport type { CategoryConfig } from '../../CheckBoxGroup/CheckBoxGroup';\n\nexport interface UseKnowledgeStructureInitialFiltersParams {\n initialFilters: {\n topicIds?: string[];\n subtopicIds?: string[];\n contentIds?: string[];\n } | null;\n knowledgeCategories: CategoryConfig[];\n handleCategoriesChange?: (updatedCategories: CategoryConfig[]) => void;\n}\n\n/**\n * Hook to handle applying initial knowledge structure filters (topics, subtopics, contents)\n * @param params - Parameters for the hook\n * @returns Refs to track application state\n */\nexport const useKnowledgeStructureInitialFilters = ({\n initialFilters,\n knowledgeCategories,\n handleCategoriesChange,\n}: UseKnowledgeStructureInitialFiltersParams) => {\n const hasAppliedKnowledgeInitialFiltersRef = useRef(false);\n const hasAppliedTopicsRef = useRef(false);\n const hasAppliedSubtopicsRef = useRef(false);\n const hasAppliedContentsRef = useRef(false);\n const knowledgeCategoriesRef = useRef<CategoryConfig[]>([]);\n\n useEffect(() => {\n knowledgeCategoriesRef.current = knowledgeCategories;\n }, [knowledgeCategories]);\n\n useEffect(() => {\n hasAppliedKnowledgeInitialFiltersRef.current = false;\n hasAppliedTopicsRef.current = false;\n hasAppliedSubtopicsRef.current = false;\n hasAppliedContentsRef.current = false;\n }, [initialFilters]);\n\n useEffect(() => {\n if (\n !initialFilters ||\n hasAppliedKnowledgeInitialFiltersRef.current ||\n knowledgeCategoriesRef.current.length === 0 ||\n !handleCategoriesChange\n ) {\n return;\n }\n\n const topicIds = initialFilters.topicIds || [];\n const subtopicIds = initialFilters.subtopicIds || [];\n const contentIds = initialFilters.contentIds || [];\n\n const hasKnowledgeSelections =\n topicIds.length > 0 || subtopicIds.length > 0 || contentIds.length > 0;\n\n if (!hasKnowledgeSelections) {\n hasAppliedKnowledgeInitialFiltersRef.current = true;\n return;\n }\n\n const currentKnowledgeCategories = knowledgeCategoriesRef.current;\n let changed = false;\n\n const updatedCategories = currentKnowledgeCategories.map((category) => {\n if (\n category.key === 'tema' &&\n topicIds.length > 0 &&\n !hasAppliedTopicsRef.current\n ) {\n const selectedIds = (category.itens || [])\n .filter((item) => topicIds.includes(item.id))\n .map((item) => item.id);\n if (selectedIds.length > 0) {\n hasAppliedTopicsRef.current = true;\n changed = true;\n return { ...category, selectedIds };\n }\n return category;\n }\n if (\n category.key === 'subtema' &&\n subtopicIds.length > 0 &&\n !hasAppliedSubtopicsRef.current\n ) {\n const selectedIds = (category.itens || [])\n .filter((item) => subtopicIds.includes(item.id))\n .map((item) => item.id);\n if (selectedIds.length > 0) {\n hasAppliedSubtopicsRef.current = true;\n changed = true;\n return { ...category, selectedIds };\n }\n return category;\n }\n if (\n category.key === 'assunto' &&\n contentIds.length > 0 &&\n !hasAppliedContentsRef.current\n ) {\n const selectedIds = (category.itens || [])\n .filter((item) => contentIds.includes(item.id))\n .map((item) => item.id);\n if (selectedIds.length > 0) {\n hasAppliedContentsRef.current = true;\n changed = true;\n return { ...category, selectedIds };\n }\n return category;\n }\n return category;\n });\n\n if (changed) {\n handleCategoriesChange(updatedCategories);\n }\n\n if (\n (topicIds.length === 0 || hasAppliedTopicsRef.current) &&\n (subtopicIds.length === 0 || hasAppliedSubtopicsRef.current) &&\n (contentIds.length === 0 || hasAppliedContentsRef.current)\n ) {\n hasAppliedKnowledgeInitialFiltersRef.current = true;\n }\n }, [initialFilters, knowledgeCategories, handleCategoriesChange]);\n\n return {\n hasAppliedKnowledgeInitialFiltersRef,\n hasAppliedTopicsRef,\n hasAppliedSubtopicsRef,\n hasAppliedContentsRef,\n knowledgeCategoriesRef,\n };\n};\n","import { BookIcon } from '@phosphor-icons/react/dist/csr/Book';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport { Button, Text, SkeletonText, BaseApiClient, EmptyState } from '../..';\nimport type { Lesson } from '../../types/lessons';\nimport { useLessonBank, type LessonFilters } from './hooks/useLessonBank';\nimport Video from '../../assets/icons/subjects/Video';\nimport { LessonWatchModal } from '../shared/LessonWatchModal';\nimport { ToastNotification } from '../shared/ToastNotification/ToastNotification';\nimport { useToastNotification } from '../shared/ToastNotification/useToastNotification';\nimport Activities from '../../assets/icons/Activities';\n\ninterface LessonBankProps {\n apiClient: BaseApiClient;\n onAddLesson?: (lesson: Lesson) => void;\n addedLessonIds?: string[];\n className?: string;\n isFromTrailRoute?: boolean;\n lessonId?: string;\n userId?: string;\n getInitialTimestamp?: (lessonId: string) => number;\n onVideoTimeUpdate?: (lessonId: string, time: number) => void;\n onVideoComplete?: (lessonId: string) => void;\n onPodcastEnded?: (lessonId: string) => void | Promise<void>;\n filters?: LessonFilters;\n}\n\n/**\n * Component that displays the list of lessons from the API\n * Fetches and displays lessons with infinite scroll\n */\nexport const LessonBank = ({\n apiClient,\n onAddLesson,\n addedLessonIds = [],\n className,\n isFromTrailRoute = false,\n lessonId,\n userId,\n getInitialTimestamp,\n onVideoTimeUpdate,\n onVideoComplete,\n onPodcastEnded,\n filters,\n}: LessonBankProps) => {\n // Toast notifications\n const { toastState, showSuccess, hideToast } = useToastNotification();\n\n const {\n loading,\n loadingMore,\n error,\n selectedLesson,\n isWatchModalOpen,\n filteredLessons,\n totalLessons,\n pagination,\n observerTarget,\n handleWatch,\n handleAddToLesson,\n handleAddToLessonFromModal,\n handleCloseModal,\n getVideoData,\n getPodcastData,\n getBoardImages,\n getBoardImageRef,\n getInitialTimestampValue,\n handleVideoTimeUpdate,\n handleVideoCompleteCallback,\n handlePodcastEnded,\n uniqueLesson,\n } = useLessonBank({\n apiClient,\n filters,\n addedLessonIds,\n onAddLesson,\n isFromTrailRoute,\n lessonId,\n getInitialTimestamp,\n onVideoTimeUpdate,\n onVideoComplete,\n onPodcastEnded,\n });\n\n const handleAddWithToast = (lesson: Lesson) => {\n handleAddToLesson(lesson);\n showSuccess('Aula adicionada à aula recomendada');\n };\n\n const handleAddFromModalWithToast = () => {\n handleAddToLessonFromModal();\n showSuccess('Aula adicionada à aula recomendada');\n };\n\n /**\n * Renders the appropriate content based on loading, error, and lessons state\n */\n const renderLessonsContent = () => {\n if (loading && filteredLessons.length === 0) {\n return (\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <Text size=\"md\" className=\"text-text-600\">\n Erro ao carregar aulas: {error}\n </Text>\n </div>\n );\n }\n\n if (filteredLessons.length === 0) {\n return (\n <EmptyState\n image={<Activities />}\n title=\"Nenhum resultado encontrado\"\n description=\"Utilize o filtro ao lado para encontrar aulas.\"\n size=\"compact\"\n />\n );\n }\n\n return (\n <>\n {filteredLessons.map((lesson) => (\n <div\n key={lesson.id}\n className=\"flex flex-col gap-3 p-4 border border-border-200 rounded-lg bg-background\"\n >\n <Text size=\"md\" weight=\"medium\" className=\"text-text-950\">\n {lesson.content?.name ||\n lesson.videoTitle ||\n lesson.title ||\n 'Aula sem título'}\n </Text>\n\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"small\"\n onClick={() => handleWatch(lesson)}\n className=\"flex-1\"\n iconLeft={<Video size={16} color=\"currentColor\" />}\n >\n Assistir\n </Button>\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"small\"\n onClick={() => handleAddWithToast(lesson)}\n className=\"flex-1\"\n iconLeft={<PlusIcon size={16} />}\n >\n Adicionar à aula\n </Button>\n </div>\n </div>\n ))}\n {pagination?.hasNext && (\n <div ref={observerTarget} className=\"h-4 w-full\">\n {loadingMore && (\n <div className=\"flex flex-col gap-2 py-4\">\n {[1, 2].map((i) => (\n <div key={i} className=\"p-4 border rounded\">\n <SkeletonText lines={2} />\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n </>\n );\n };\n\n return (\n <div\n className={`w-full flex flex-col p-4 gap-2 overflow-hidden h-full min-h-0 ${className || ''}`}\n >\n <div className=\"flex flex-col gap-2 flex-shrink-0\">\n <section className=\"flex flex-row items-center gap-2 text-text-950\">\n <BookIcon size={24} />\n <Text size=\"lg\" weight=\"bold\">\n Banco de Aulas\n </Text>\n </section>\n\n <section className=\"flex flex-row justify-between items-center\">\n <Text size=\"sm\" className=\"text-text-800\">\n {loading\n ? 'Carregando...'\n : `${totalLessons} ${uniqueLesson()} total`}\n </Text>\n </section>\n </div>\n\n <div className=\"flex flex-col gap-3 overflow-auto flex-1 min-h-0\">\n {renderLessonsContent()}\n </div>\n\n <LessonWatchModal\n isOpen={isWatchModalOpen}\n onClose={handleCloseModal}\n selectedLesson={selectedLesson}\n title={\n selectedLesson?.content?.name ||\n selectedLesson?.videoTitle ||\n selectedLesson?.content?.bnccCode ||\n selectedLesson?.title ||\n 'Assistir Aula'\n }\n userId={userId}\n getVideoData={getVideoData}\n getInitialTimestampValue={getInitialTimestampValue}\n handleVideoTimeUpdate={handleVideoTimeUpdate}\n handleVideoCompleteCallback={handleVideoCompleteCallback}\n getPodcastData={getPodcastData}\n onPodcastEnded={handlePodcastEnded}\n getBoardImages={getBoardImages}\n getBoardImageRef={getBoardImageRef}\n footer={\n <div className=\"flex gap-3\">\n <Button variant=\"outline\" onClick={handleCloseModal}>\n Cancelar\n </Button>\n <Button variant=\"solid\" onClick={handleAddFromModalWithToast}>\n Adicionar à aula\n </Button>\n </div>\n }\n />\n\n {/* Toast Notification */}\n <ToastNotification\n isOpen={toastState.isOpen}\n onClose={hideToast}\n title={toastState.title}\n description={toastState.description}\n action={toastState.action}\n />\n </div>\n );\n};\n\nexport type { LessonBankProps };\nexport type { LessonFilters } from './hooks/useLessonBank';\n","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n useCallback,\n RefObject,\n} from 'react';\nimport type { BaseApiClient } from '../../../types/api';\nimport type {\n Lesson,\n LessonsListResponse,\n LessonsPagination,\n} from '../../../types/lessons';\nimport type { WhiteboardImage } from '../../Whiteboard/Whiteboard';\n\nexport interface LessonFilters {\n subjectId?: string[];\n topicIds?: string[];\n subtopicIds?: string[];\n contentIds?: string[];\n selectedIds?: string[];\n}\n\ninterface UseLessonBankConfig {\n apiClient: BaseApiClient;\n filters?: LessonFilters;\n addedLessonIds?: string[];\n onAddLesson?: (lesson: Lesson) => void;\n isFromTrailRoute?: boolean;\n lessonId?: string;\n getInitialTimestamp?: (lessonId: string) => number;\n onVideoTimeUpdate?: (lessonId: string, time: number) => void;\n onVideoComplete?: (lessonId: string) => void;\n onPodcastEnded?: (lessonId: string) => void | Promise<void>;\n}\n\ninterface UseLessonBankReturn {\n // State\n lessons: Lesson[];\n pagination: LessonsPagination | null;\n loading: boolean;\n loadingMore: boolean;\n error: string | null;\n selectedLesson: Lesson | null;\n isWatchModalOpen: boolean;\n filteredLessons: Lesson[];\n totalLessons: number;\n\n // Refs\n observerTarget: RefObject<HTMLDivElement | null>;\n firstBoardImageRef: RefObject<HTMLDivElement | null>;\n lastBoardImageRef: RefObject<HTMLDivElement | null>;\n\n // Actions\n handleWatch: (lesson: Lesson) => void;\n handleAddToLesson: (lesson: Lesson) => void;\n handleAddToLessonFromModal: () => void;\n handleCloseModal: () => void;\n\n // Data getters\n getVideoData: (lesson: Lesson | null) => {\n src: string;\n poster?: string;\n subtitles?: string;\n };\n getPodcastData: (lesson: Lesson | null) => {\n src: string;\n title: string;\n };\n getBoardImages: (lesson: Lesson | null) => WhiteboardImage[];\n getBoardImageRef: (\n index: number,\n total: number\n ) => RefObject<HTMLDivElement | null> | null;\n getInitialTimestampValue: (id: string) => number;\n\n // Callbacks\n handleVideoTimeUpdate: (seconds: number) => void;\n handleVideoCompleteCallback: () => void;\n handlePodcastEnded: () => Promise<void>;\n\n // Helpers\n uniqueLesson: () => string;\n}\n\n/**\n * Hook for managing lesson bank state and operations\n */\nexport const useLessonBank = (\n config: UseLessonBankConfig\n): UseLessonBankReturn => {\n const {\n apiClient,\n filters,\n addedLessonIds = [],\n onAddLesson,\n isFromTrailRoute = false,\n lessonId,\n getInitialTimestamp,\n onVideoTimeUpdate,\n onVideoComplete,\n onPodcastEnded,\n } = config;\n\n // State\n const [lessons, setLessons] = useState<Lesson[]>([]);\n const [pagination, setPagination] = useState<LessonsPagination | null>(null);\n const [loading, setLoading] = useState(false);\n const [loadingMore, setLoadingMore] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [selectedLesson, setSelectedLesson] = useState<Lesson | null>(null);\n const [isWatchModalOpen, setIsWatchModalOpen] = useState(false);\n\n // Refs\n const observerTarget = useRef<HTMLDivElement>(null);\n const hasMarkedPodcast = useRef(false);\n const firstBoardImageRef = useRef<HTMLDivElement>(null);\n const lastBoardImageRef = useRef<HTMLDivElement>(null);\n const filtersRef = useRef(filters);\n const apiClientRef = useRef(apiClient);\n\n // Update refs when props change\n useEffect(() => {\n filtersRef.current = filters;\n apiClientRef.current = apiClient;\n }, [filters, apiClient]);\n\n /**\n * Build filters body from filters object\n * Maps to the expected API format for POST /lesson/list\n * Note: selectedIds is NOT sent to the API - it's only used for client-side filtering\n */\n const buildFiltersBody = useCallback(\n (currentFilters: LessonFilters | undefined): Record<string, unknown> => {\n const filtersBody: Record<string, unknown> = {};\n if (currentFilters?.subjectId && currentFilters.subjectId.length > 0) {\n filtersBody.subjectId = currentFilters.subjectId;\n }\n if (currentFilters?.topicIds && currentFilters.topicIds.length > 0) {\n filtersBody.topicId = currentFilters.topicIds; // API expects topicId (singular)\n }\n if (\n currentFilters?.subtopicIds &&\n currentFilters.subtopicIds.length > 0\n ) {\n filtersBody.subtopicId = currentFilters.subtopicIds; // API expects subtopicId (singular)\n }\n if (currentFilters?.contentIds && currentFilters.contentIds.length > 0) {\n filtersBody.contentId = currentFilters.contentIds; // API expects contentId (singular)\n }\n return filtersBody;\n },\n []\n );\n\n /**\n * Build the complete request body for lesson list API\n * @param page - Page number\n * @param limit - Items per page\n * @param currentFilters - Optional filters to apply\n */\n const buildLessonRequestBody = useCallback(\n (\n page: number,\n limit: number,\n currentFilters: LessonFilters | undefined\n ): Record<string, unknown> => {\n const requestBody: Record<string, unknown> = { page, limit };\n\n if (currentFilters) {\n const filtersBody = buildFiltersBody(currentFilters);\n Object.assign(requestBody, filtersBody);\n }\n\n return requestBody;\n },\n [buildFiltersBody]\n );\n\n /**\n * Fetch lessons from API\n */\n const fetchLessons = useCallback(\n async (append = false) => {\n if (append) {\n setLoadingMore(true);\n } else {\n setLoading(true);\n setError(null);\n }\n\n try {\n const currentPagination = pagination;\n const page =\n append && currentPagination ? currentPagination.page + 1 : 1;\n const requestBody = buildLessonRequestBody(\n page,\n 20,\n filtersRef.current\n );\n\n const response = await apiClientRef.current.post<LessonsListResponse>(\n '/lesson/list',\n requestBody\n );\n\n if (append) {\n setLessons((prev) => [...prev, ...response.data.data.lessons]);\n } else {\n setLessons(response.data.data.lessons);\n }\n\n setPagination(response.data.data.pagination);\n setLoading(false);\n setLoadingMore(false);\n setError(null);\n } catch (err) {\n console.error('Erro ao carregar aulas:', err);\n setError('Erro ao carregar aulas');\n setLoading(false);\n setLoadingMore(false);\n }\n },\n [pagination, buildLessonRequestBody]\n );\n\n /**\n * Load more lessons (for infinite scroll)\n */\n const loadMore = useCallback(async () => {\n if (pagination?.hasNext && !loading && !loadingMore) {\n await fetchLessons(true);\n }\n }, [pagination, loading, loadingMore, fetchLessons]);\n\n /**\n * Create a stable key from filters to detect changes\n * Note: selectedIds is excluded from the key because it's only used for client-side filtering,\n * not for API requests. Changes to selectedIds should not trigger a refetch.\n */\n const filtersKey = useMemo(() => {\n if (!filters) return '';\n return JSON.stringify({\n subjectId: filters.subjectId || [],\n topicIds: filters.topicIds || [],\n subtopicIds: filters.subtopicIds || [],\n contentIds: filters.contentIds || [],\n });\n }, [filters]);\n\n /**\n * Reset pagination and fetch lessons when filters change\n * Only runs when filters actually change, not when other props change\n * Only fetches if filters contain at least a subjectId\n */\n useEffect(() => {\n // Check if filters are valid (at least subjectId must be present)\n const hasValidFilters = filters?.subjectId && filters.subjectId.length > 0;\n\n // If no valid filters, reset state and skip fetch\n if (!hasValidFilters) {\n setPagination(null);\n setLessons([]);\n setError(null);\n setLoading(false);\n return;\n }\n\n setPagination(null);\n setLessons([]);\n const loadLessons = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const requestBody = buildLessonRequestBody(1, 20, filters);\n\n const response = await apiClientRef.current.post<LessonsListResponse>(\n '/lesson/list',\n requestBody\n );\n\n setLessons(response.data.data.lessons);\n setPagination(response.data.data.pagination);\n setLoading(false);\n setError(null);\n } catch (err) {\n console.error('Erro ao carregar aulas:', err);\n setError('Erro ao carregar aulas');\n setLoading(false);\n }\n };\n\n loadLessons();\n }, [filtersKey, buildLessonRequestBody]);\n\n /**\n * Intersection Observer for infinite scroll\n * Loads more lessons when user scrolls to the bottom\n */\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n if (\n entries[0].isIntersecting &&\n !loading &&\n !loadingMore &&\n pagination?.hasNext\n ) {\n loadMore();\n }\n },\n { threshold: 0.1 }\n );\n\n const currentTarget = observerTarget.current;\n if (currentTarget) {\n observer.observe(currentTarget);\n }\n\n return () => {\n if (currentTarget) {\n observer.unobserve(currentTarget);\n }\n };\n }, [loading, loadingMore, pagination, loadMore]);\n\n /**\n * Handle watch button click\n */\n const handleWatch = useCallback((lesson: Lesson) => {\n setSelectedLesson(lesson);\n setIsWatchModalOpen(true);\n }, []);\n\n /**\n * Handle add to lesson button click (from card)\n */\n const handleAddToLesson = useCallback(\n (lesson: Lesson) => {\n if (onAddLesson) {\n onAddLesson(lesson);\n }\n },\n [onAddLesson]\n );\n\n /**\n * Handle add to lesson from modal\n */\n const handleAddToLessonFromModal = useCallback(() => {\n if (selectedLesson && onAddLesson) {\n onAddLesson(selectedLesson);\n }\n setIsWatchModalOpen(false);\n setSelectedLesson(null);\n }, [selectedLesson, onAddLesson]);\n\n /**\n * Handle modal close\n */\n const handleCloseModal = useCallback(() => {\n setIsWatchModalOpen(false);\n setSelectedLesson(null);\n }, []);\n\n /**\n * Get video data from lesson\n */\n const getVideoData = useCallback((lesson: Lesson | null) => {\n if (!lesson) {\n return {\n src: '',\n poster: undefined,\n subtitles: undefined,\n };\n }\n\n return {\n src: lesson.urlVideo || '',\n poster: lesson.urlCover,\n subtitles: lesson.urlSubtitle,\n };\n }, []);\n\n /**\n * Get podcast data from lesson\n */\n const getPodcastData = useCallback((lesson: Lesson | null) => {\n if (!lesson) {\n return {\n src: '',\n title: '',\n };\n }\n\n return {\n src: lesson.urlPodCast || '',\n title: lesson.podCastTitle || '',\n };\n }, []);\n\n /**\n * Get board images from lesson\n */\n const getBoardImages = useCallback(\n (lesson: Lesson | null): WhiteboardImage[] => {\n if (!lesson) {\n return [];\n }\n\n const boardImages = (\n lesson as Lesson & {\n boardImages?: WhiteboardImage[];\n }\n ).boardImages;\n\n return boardImages || [];\n },\n []\n );\n\n /**\n * Get ref for board image (only first and last)\n */\n const getBoardImageRef = useCallback((index: number, total: number) => {\n if (index === 0) {\n return firstBoardImageRef;\n }\n if (index === total - 1) {\n return lastBoardImageRef;\n }\n return null;\n }, []);\n\n /**\n * Get initial timestamp for video\n */\n const getInitialTimestampValue = useCallback(\n (id: string): number => {\n if (getInitialTimestamp) {\n return getInitialTimestamp(id);\n }\n // Try to get from localStorage as fallback\n const saved = localStorage.getItem(`lesson-${id}`);\n if (saved) {\n const parsed = Number.parseFloat(saved);\n if (Number.isFinite(parsed) && parsed >= 0) {\n return parsed;\n }\n }\n return 0;\n },\n [getInitialTimestamp]\n );\n\n /**\n * Handle video time update\n */\n const handleVideoTimeUpdate = useCallback(\n (seconds: number) => {\n if (!selectedLesson) return;\n\n const currentLessonId = isFromTrailRoute\n ? lessonId || ''\n : selectedLesson.id;\n\n if (onVideoTimeUpdate) {\n onVideoTimeUpdate(currentLessonId, seconds);\n }\n },\n [selectedLesson, isFromTrailRoute, lessonId, onVideoTimeUpdate]\n );\n\n /**\n * Handle video complete callback\n */\n const handleVideoCompleteCallback = useCallback(() => {\n if (!selectedLesson) return;\n\n const currentLessonId = isFromTrailRoute\n ? lessonId || ''\n : selectedLesson.id;\n\n if (onVideoComplete) {\n onVideoComplete(currentLessonId);\n }\n }, [selectedLesson, isFromTrailRoute, lessonId, onVideoComplete]);\n\n /**\n * Handle podcast ended callback\n */\n const handlePodcastEnded = useCallback(async () => {\n if (!selectedLesson) return;\n\n const currentLessonId = isFromTrailRoute\n ? lessonId || ''\n : selectedLesson.id;\n\n if (onPodcastEnded && !hasMarkedPodcast.current) {\n hasMarkedPodcast.current = true;\n\n try {\n await onPodcastEnded(currentLessonId);\n } catch (error) {\n // Revert flag if callback failed\n hasMarkedPodcast.current = false;\n console.error('Error in podcast ended callback:', error);\n }\n }\n }, [selectedLesson, isFromTrailRoute, lessonId, onPodcastEnded]);\n\n /**\n * Reset podcast flag when lesson changes\n */\n useEffect(() => {\n hasMarkedPodcast.current = false;\n }, [selectedLesson?.id]);\n\n /**\n * Filter out lessons that are already added\n */\n const filteredLessons = useMemo(() => {\n return lessons.filter((lesson) => !addedLessonIds.includes(lesson.id));\n }, [lessons, addedLessonIds]);\n\n /**\n * Calculate total lessons based on filtered lessons (visible in list)\n * This reflects the actual number of lessons displayed, excluding already added ones\n */\n const totalLessons = useMemo(() => {\n return filteredLessons.length;\n }, [filteredLessons.length]);\n\n const uniqueLesson = useCallback(() => {\n return totalLessons === 1 ? 'aula' : 'aulas';\n }, [totalLessons]);\n\n return {\n // State\n lessons,\n pagination,\n loading,\n loadingMore,\n error,\n selectedLesson,\n isWatchModalOpen,\n filteredLessons,\n totalLessons,\n\n // Refs\n observerTarget,\n firstBoardImageRef,\n lastBoardImageRef,\n\n // Actions\n handleWatch,\n handleAddToLesson,\n handleAddToLessonFromModal,\n handleCloseModal,\n\n // Data getters\n getVideoData,\n getPodcastData,\n getBoardImages,\n getBoardImageRef,\n getInitialTimestampValue,\n\n // Callbacks\n handleVideoTimeUpdate,\n handleVideoCompleteCallback,\n handlePodcastEnded,\n\n // Helpers\n uniqueLesson,\n };\n};\n","export const Video = ({ size, color }: { size: number; color: string }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M10.2775 6.58375L7.2775 4.58375C7.20218 4.5335 7.11463 4.50464 7.0242 4.50026C6.93376 4.49588 6.84383 4.51614 6.76401 4.55887C6.68418 4.60161 6.61746 4.66521 6.57097 4.74291C6.52447 4.8206 6.49994 4.90946 6.5 5V9C6.49994 9.09054 6.52447 9.1794 6.57097 9.25709C6.61746 9.33479 6.68418 9.39839 6.76401 9.44113C6.84383 9.48386 6.93376 9.50412 7.0242 9.49974C7.11463 9.49536 7.20218 9.4665 7.2775 9.41625L10.2775 7.41625C10.3461 7.37061 10.4023 7.30873 10.4412 7.23611C10.4801 7.16349 10.5005 7.08238 10.5005 7C10.5005 6.91762 10.4801 6.83651 10.4412 6.76389C10.4023 6.69127 10.3461 6.62939 10.2775 6.58375ZM7.5 8.06563V5.9375L9.09875 7L7.5 8.06563ZM13.5 2.5H2.5C2.23478 2.5 1.98043 2.60536 1.79289 2.79289C1.60536 2.98043 1.5 3.23478 1.5 3.5V10.5C1.5 10.7652 1.60536 11.0196 1.79289 11.2071C1.98043 11.3946 2.23478 11.5 2.5 11.5H13.5C13.7652 11.5 14.0196 11.3946 14.2071 11.2071C14.3946 11.0196 14.5 10.7652 14.5 10.5V3.5C14.5 3.23478 14.3946 2.98043 14.2071 2.79289C14.0196 2.60536 13.7652 2.5 13.5 2.5ZM13.5 10.5H2.5V3.5H13.5V10.5ZM14.5 13C14.5 13.1326 14.4473 13.2598 14.3536 13.3536C14.2598 13.4473 14.1326 13.5 14 13.5H2C1.86739 13.5 1.74021 13.4473 1.64645 13.3536C1.55268 13.2598 1.5 13.1326 1.5 13C1.5 12.8674 1.55268 12.7402 1.64645 12.6464C1.74021 12.5527 1.86739 12.5 2 12.5H14C14.1326 12.5 14.2598 12.5527 14.3536 12.6464C14.4473 12.7402 14.5 12.8674 14.5 13Z\"\n fill={color}\n />\n </svg>\n);\n\nexport default Video;\n","import React, { type RefObject } from 'react';\nimport {\n Button,\n Modal,\n Text,\n VideoPlayer,\n Alert,\n CardAudio,\n Whiteboard,\n} from '../../../index';\nimport type { Lesson } from '../../../types/lessons';\nimport type { WhiteboardImage } from '../../Whiteboard/Whiteboard';\n\nexport interface LessonWatchModalProps {\n isOpen: boolean;\n onClose: () => void;\n selectedLesson: Lesson | null;\n userId?: string;\n getVideoData: (lesson: Lesson | null) => {\n src: string;\n poster?: string;\n subtitles?: string;\n };\n getInitialTimestampValue: (lessonId: string) => number;\n handleVideoTimeUpdate: (time: number) => void;\n handleVideoCompleteCallback: () => void;\n getPodcastData: (lesson: Lesson | null) => { src: string; title: string };\n onPodcastEnded: () => void;\n getBoardImages: (lesson: Lesson | null) => WhiteboardImage[];\n getBoardImageRef: (\n index: number,\n total: number\n ) => RefObject<HTMLDivElement | null> | null;\n /**\n * Custom footer content. If not provided, defaults to a Cancel button.\n */\n footer?: React.ReactNode;\n /**\n * Modal title. Defaults to selectedLesson?.title || 'Assistir Aula'\n */\n title?: string;\n}\n\ntype PodcastSectionProps = Pick<\n LessonWatchModalProps,\n 'getPodcastData' | 'onPodcastEnded'\n> & { lesson: Lesson };\n\ntype BoardImagesSectionProps = Pick<\n LessonWatchModalProps,\n 'getBoardImages' | 'getBoardImageRef'\n> & { lesson: Lesson };\n\ntype VideoSectionProps = Omit<\n LessonWatchModalProps,\n 'isOpen' | 'onClose' | 'selectedLesson' | 'footer' | 'title'\n> & { lesson: Lesson };\n\n/**\n * Renders podcast section if available\n */\nconst PodcastSection = ({\n lesson,\n getPodcastData,\n onPodcastEnded,\n}: PodcastSectionProps) => {\n const podcastData = getPodcastData(lesson);\n if (!podcastData.src) {\n return null;\n }\n return (\n <div className=\"w-full\">\n <Text size=\"md\" weight=\"bold\" className=\"pb-2\">\n {podcastData.title}\n </Text>\n <CardAudio\n src={podcastData.src}\n title={podcastData.title}\n onEnded={onPodcastEnded}\n />\n </div>\n );\n};\n\n/**\n * Renders board images section if available\n */\nconst BoardImagesSection = ({\n lesson,\n getBoardImages,\n getBoardImageRef,\n}: BoardImagesSectionProps) => {\n const boardImages: WhiteboardImage[] = getBoardImages(lesson);\n if (boardImages.length === 0) {\n return null;\n }\n return (\n <div className=\"w-full\">\n <Text size=\"md\" weight=\"bold\" className=\"pb-2\">\n Quadros da aula\n </Text>\n <div className=\"flex flex-wrap items-center justify-center gap-4\">\n {boardImages.map((image: WhiteboardImage, index: number) => (\n <div\n key={image.id || `board-image-${index}`}\n ref={getBoardImageRef(index, boardImages.length)}\n className=\"flex flex-row rounded-xl bg-background-50\"\n >\n <Whiteboard\n images={[image]}\n showDownload={true}\n imagesPerRow={2}\n className=\"gap-4 w-full items-center border-border-50\"\n />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\n/**\n * Renders video section with player, podcast, and board images\n */\nconst VideoSection = ({\n lesson,\n userId,\n getVideoData,\n getInitialTimestampValue,\n handleVideoTimeUpdate,\n handleVideoCompleteCallback,\n getPodcastData,\n onPodcastEnded,\n getBoardImages,\n getBoardImageRef,\n}: VideoSectionProps) => {\n const videoData = getVideoData(lesson);\n if (!videoData.src) {\n return (\n <div className=\"px-6 py-6 flex flex-col gap-4\">\n <Text size=\"md\" className=\"text-text-600\">\n Vídeo não disponível para esta aula.\n </Text>\n </div>\n );\n }\n\n return (\n <>\n <VideoPlayer\n src={videoData.src}\n poster={videoData.poster}\n subtitles={videoData.subtitles}\n onTimeUpdate={handleVideoTimeUpdate}\n onVideoComplete={handleVideoCompleteCallback}\n initialTime={getInitialTimestampValue(lesson.id)}\n className=\"w-full h-full object-cover rounded-b-xl\"\n autoSave={true}\n storageKey={`lesson-${lesson.id}`}\n userId={userId}\n />\n <div className=\"flex flex-col gap-4\">\n <Alert\n action=\"info\"\n variant=\"solid\"\n description=\"Cada aula inclui questionários automáticos para o aluno praticar o conteúdo.\"\n className=\"w-full\"\n />\n <PodcastSection\n lesson={lesson}\n getPodcastData={getPodcastData}\n onPodcastEnded={onPodcastEnded}\n />\n <BoardImagesSection\n lesson={lesson}\n getBoardImages={getBoardImages}\n getBoardImageRef={getBoardImageRef}\n />\n </div>\n </>\n );\n};\n\n/**\n * Modal component for watching lessons with video, podcast, and board images\n */\nexport const LessonWatchModal = ({\n isOpen,\n onClose,\n selectedLesson,\n userId,\n getVideoData,\n getInitialTimestampValue,\n handleVideoTimeUpdate,\n handleVideoCompleteCallback,\n getPodcastData,\n onPodcastEnded,\n getBoardImages,\n getBoardImageRef,\n footer,\n title,\n}: LessonWatchModalProps) => {\n const defaultFooter = (\n <div className=\"flex gap-3\">\n <Button variant=\"outline\" onClick={onClose}>\n Cancelar\n </Button>\n </div>\n );\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title || selectedLesson?.title || 'Assistir Aula'}\n size=\"lg\"\n hideCloseButton={true}\n footer={footer ?? defaultFooter}\n >\n <div className=\"flex flex-col gap-4 max-h-[70vh] overflow-auto\">\n {selectedLesson ? (\n <VideoSection\n lesson={selectedLesson}\n userId={userId}\n getVideoData={getVideoData}\n getInitialTimestampValue={getInitialTimestampValue}\n handleVideoTimeUpdate={handleVideoTimeUpdate}\n handleVideoCompleteCallback={handleVideoCompleteCallback}\n getPodcastData={getPodcastData}\n onPodcastEnded={onPodcastEnded}\n getBoardImages={getBoardImages}\n getBoardImageRef={getBoardImageRef}\n />\n ) : (\n <div className=\"px-6 py-6 flex flex-col gap-4\">\n <Text size=\"md\" className=\"text-text-600\">\n Carregando...\n </Text>\n </div>\n )}\n </div>\n </Modal>\n );\n};\n","import Toast from '../../Toast/Toast';\n\ninterface ToastNotificationProps {\n /**\n * Controls whether the toast is visible\n */\n isOpen: boolean;\n /**\n * Callback when the toast is closed\n */\n onClose: () => void;\n /**\n * The title/message to display\n */\n title: string;\n /**\n * Optional description for more details\n */\n description?: string;\n /**\n * The type of toast\n * @default 'success'\n */\n action?: 'success' | 'warning' | 'info';\n /**\n * The variant of the toast\n * @default 'solid'\n */\n variant?: 'solid' | 'outlined';\n}\n\n/**\n * Shared toast notification component that displays centered at the top of the screen\n * Provides consistent positioning and styling across the application\n *\n * @example\n * ```tsx\n * const [showToast, setShowToast] = useState(false);\n *\n * <ToastNotification\n * isOpen={showToast}\n * onClose={() => setShowToast(false)}\n * title=\"Atividade adicionada à aula recomendada\"\n * />\n * ```\n */\nexport const ToastNotification = ({\n isOpen,\n onClose,\n title,\n description,\n action = 'success',\n variant = 'solid',\n}: ToastNotificationProps) => {\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed top-4 left-1/2 transform -translate-x-1/2 z-50\">\n <Toast\n title={title}\n description={description}\n variant={variant}\n action={action}\n onClose={onClose}\n />\n </div>\n );\n};\n\nexport type { ToastNotificationProps };\n","import { useState, useCallback, useRef, useEffect } from 'react';\n\ninterface ToastState {\n isOpen: boolean;\n title: string;\n description?: string;\n action: 'success' | 'warning' | 'info';\n}\n\ninterface UseToastNotificationReturn {\n /**\n * Current toast state\n */\n toastState: ToastState;\n /**\n * Show a success toast\n * @param title - The title/message to display\n * @param description - Optional description\n * @param duration - Auto-dismiss duration in milliseconds (default: 3000)\n */\n showSuccess: (title: string, description?: string, duration?: number) => void;\n /**\n * Show a warning/error toast\n * @param title - The title/message to display\n * @param description - Optional description\n * @param duration - Auto-dismiss duration in milliseconds (default: 3000)\n */\n showError: (title: string, description?: string, duration?: number) => void;\n /**\n * Show an info toast\n * @param title - The title/message to display\n * @param description - Optional description\n * @param duration - Auto-dismiss duration in milliseconds (default: 3000)\n */\n showInfo: (title: string, description?: string, duration?: number) => void;\n /**\n * Manually close the toast\n */\n hideToast: () => void;\n}\n\n/**\n * Custom hook for managing toast notifications\n * Provides methods to show success, error, and info toasts with auto-dismiss\n *\n * @example\n * ```tsx\n * const { toastState, showSuccess, showError, hideToast } = useToastNotification();\n *\n * const handleSubmit = async () => {\n * try {\n * await submitData();\n * showSuccess('Dados salvos com sucesso!');\n * } catch (error) {\n * showError('Erro ao salvar dados');\n * }\n * };\n *\n * return (\n * <>\n * <button onClick={handleSubmit}>Salvar</button>\n * <ToastNotification\n * isOpen={toastState.isOpen}\n * onClose={hideToast}\n * title={toastState.title}\n * description={toastState.description}\n * action={toastState.action}\n * />\n * </>\n * );\n * ```\n */\nexport const useToastNotification = (): UseToastNotificationReturn => {\n const [toastState, setToastState] = useState<ToastState>({\n isOpen: false,\n title: '',\n description: undefined,\n action: 'success',\n });\n\n // Ref to store the timeout ID for cleanup\n const toastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const hideToast = useCallback(() => {\n // Clear any existing timeout when manually hiding\n if (toastTimeoutRef.current) {\n clearTimeout(toastTimeoutRef.current);\n toastTimeoutRef.current = null;\n }\n setToastState((prev) => ({ ...prev, isOpen: false }));\n }, []);\n\n const showToast = useCallback(\n (\n title: string,\n action: 'success' | 'warning' | 'info',\n description?: string,\n duration: number = 3000\n ) => {\n // Clear any existing timeout before creating a new one\n if (toastTimeoutRef.current) {\n clearTimeout(toastTimeoutRef.current);\n toastTimeoutRef.current = null;\n }\n\n setToastState({\n isOpen: true,\n title,\n description,\n action,\n });\n\n // Schedule auto-dismiss if duration is positive\n if (duration > 0) {\n toastTimeoutRef.current = setTimeout(() => {\n setToastState((prev) => ({ ...prev, isOpen: false }));\n toastTimeoutRef.current = null;\n }, duration);\n }\n },\n []\n );\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (toastTimeoutRef.current) {\n clearTimeout(toastTimeoutRef.current);\n }\n };\n }, []);\n\n const showSuccess = useCallback(\n (title: string, description?: string, duration?: number) => {\n showToast(title, 'success', description, duration);\n },\n [showToast]\n );\n\n const showError = useCallback(\n (title: string, description?: string, duration?: number) => {\n showToast(title, 'warning', description, duration);\n },\n [showToast]\n );\n\n const showInfo = useCallback(\n (title: string, description?: string, duration?: number) => {\n showToast(title, 'info', description, duration);\n },\n [showToast]\n );\n\n return {\n toastState,\n showSuccess,\n showError,\n showInfo,\n hideToast,\n };\n};\n","import { RefObject, useEffect, useMemo, useRef, useState } from 'react';\nimport { BookIcon } from '@phosphor-icons/react/dist/csr/Book';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { PencilSimpleIcon } from '@phosphor-icons/react/dist/csr/PencilSimple';\nimport { Button, Text, Divider, EmptyState } from '../../index';\nimport type { Lesson } from '../../types/lessons';\nimport type { WhiteboardImage } from '../Whiteboard/Whiteboard';\nimport { cn } from '../../utils/utils';\nimport Video from '../../assets/icons/subjects/Video';\nimport { LessonWatchModal } from '../shared/LessonWatchModal';\nimport { AddActivityOptionModal, type ActivityOption } from './components';\nimport { ChooseActivityModelModal } from '../ChooseActivityModelModal';\nimport type { BaseApiClient } from '../../types/api';\nimport type { ActivityModelTableItem } from '../../types/activitiesHistory';\nimport { ToastNotification } from '../shared/ToastNotification/ToastNotification';\nimport { useToastNotification } from '../shared/ToastNotification/useToastNotification';\nimport Activities from '../../assets/icons/Activities';\n\n/**\n * Extended lesson type with optional media properties\n */\ninterface LessonWithMedia extends Lesson {\n videoSrc?: string;\n videoPoster?: string;\n videoSubtitles?: string;\n podcastSrc?: string;\n podcastTitle?: string;\n boardImages?: WhiteboardImage[];\n}\n\ntype PreviewLesson = LessonWithMedia & {\n position?: number;\n};\n\ninterface LessonPreviewProps {\n title?: string;\n lessons?: PreviewLesson[];\n onRemoveAll?: () => void;\n onRemoveLesson?: (lessonId: string) => void;\n className?: string;\n onReorder?: (orderedLessons: PreviewLesson[]) => void;\n /**\n * Emits the current ordered list (with positions) whenever it changes.\n */\n onPositionsChange?: (orderedLessons: PreviewLesson[]) => void;\n /**\n * Callback when video time updates\n */\n onVideoTimeUpdate?: (lessonId: string, time: number) => void;\n /**\n * Callback when video completes\n */\n onVideoComplete?: (lessonId: string) => void;\n /**\n * Callback when podcast ends\n */\n onPodcastEnded?: (lessonId: string) => void | Promise<void>;\n /**\n * Get initial timestamp for a lesson\n */\n getInitialTimestamp?: (lessonId: string) => number;\n /** User ID used to scope video-progress localStorage keys per user */\n userId?: string;\n /**\n * Callback when create new activity is clicked\n */\n onCreateNewActivity?: () => void;\n /**\n * API client for fetching activity models\n */\n apiClient: BaseApiClient;\n /**\n * Callback when an activity model is selected\n */\n onActivitySelected?: (model: ActivityModelTableItem) => void;\n /**\n * Callback when edit activity button is clicked\n */\n onEditActivity?: (activity: ActivityModelTableItem) => void;\n /**\n * Callback when remove activity button is clicked\n */\n onRemoveActivity?: () => void;\n /**\n * Initial selected activity (if any)\n */\n selectedActivity?: ActivityModelTableItem | null;\n}\n\nexport const LessonPreview = ({\n title = 'Prévia das aulas',\n lessons = [],\n onRemoveAll,\n onRemoveLesson,\n className,\n onReorder,\n onPositionsChange,\n onVideoTimeUpdate,\n onVideoComplete,\n onPodcastEnded,\n getInitialTimestamp,\n userId,\n onCreateNewActivity,\n apiClient,\n onActivitySelected,\n onEditActivity,\n onRemoveActivity,\n selectedActivity: initialSelectedActivity = null,\n}: LessonPreviewProps) => {\n const onPositionsChangeRef = useRef(onPositionsChange);\n onPositionsChangeRef.current = onPositionsChange;\n\n const normalizeWithPositions = useMemo(\n () => (items: PreviewLesson[]) =>\n items.map((item, index) => ({\n ...item,\n position: index + 1,\n })),\n []\n );\n\n const [orderedLessons, setOrderedLessons] = useState<PreviewLesson[]>(() =>\n normalizeWithPositions(lessons)\n );\n\n const [selectedLesson, setSelectedLesson] = useState<Lesson | null>(null);\n const [isWatchModalOpen, setIsWatchModalOpen] = useState(false);\n const [isActivityOptionModalOpen, setIsActivityOptionModalOpen] =\n useState(false);\n const [isChooseModelModalOpen, setIsChooseModelModalOpen] = useState(false);\n const [selectedActivity, setSelectedActivity] =\n useState<ActivityModelTableItem | null>(initialSelectedActivity);\n\n // Toast notifications\n const { toastState, showSuccess, hideToast } = useToastNotification();\n\n // Refs for board images\n const firstBoardImageRef = useRef<HTMLDivElement | null>(null);\n const lastBoardImageRef = useRef<HTMLDivElement | null>(null);\n const hasMarkedPodcast = useRef(false);\n\n // Sync when external lessons change (e.g., reset from parent)\n useEffect(() => {\n const normalized = normalizeWithPositions(lessons);\n setOrderedLessons(normalized);\n onPositionsChangeRef.current?.(normalized);\n }, [lessons, normalizeWithPositions]);\n\n // Sync selected activity when prop changes\n useEffect(() => {\n setSelectedActivity(initialSelectedActivity);\n }, [initialSelectedActivity]);\n\n const total = orderedLessons.length;\n const totalLabel =\n total === 1 ? '1 aula adicionada' : `${total} aulas adicionadas`;\n\n /**\n * Get video data from lesson\n */\n const getVideoData = (lesson: LessonWithMedia | null) => {\n if (!lesson) return { src: '' };\n return {\n src: lesson.videoSrc || '',\n poster: lesson.videoPoster,\n subtitles: lesson.videoSubtitles,\n };\n };\n\n /**\n * Get podcast data from lesson\n */\n const getPodcastData = (lesson: LessonWithMedia | null) => {\n if (!lesson) return { src: '', title: '' };\n return {\n src: lesson.podcastSrc || '',\n title: lesson.podcastTitle || 'Podcast da aula',\n };\n };\n\n /**\n * Get board images from lesson\n */\n const getBoardImages = (\n lesson: LessonWithMedia | null\n ): WhiteboardImage[] => {\n if (!lesson) return [];\n return lesson.boardImages || [];\n };\n\n /**\n * Get ref for board image based on index\n */\n const getBoardImageRef = (\n index: number,\n total: number\n ): RefObject<HTMLDivElement | null> | null => {\n if (index === 0) return firstBoardImageRef;\n if (index === total - 1) return lastBoardImageRef;\n return null;\n };\n\n /**\n * Get initial timestamp value for a lesson\n */\n const getInitialTimestampValue = (id: string): number => {\n if (getInitialTimestamp) {\n return getInitialTimestamp(id);\n }\n // Try to get from localStorage\n try {\n const stored = localStorage.getItem(`lesson-${id}-time`);\n if (stored) {\n return Number.parseFloat(stored) || 0;\n }\n } catch {\n // Ignore localStorage errors\n }\n return 0;\n };\n\n /**\n * Handle video time update\n */\n const handleVideoTimeUpdate = (time: number) => {\n if (selectedLesson && onVideoTimeUpdate) {\n onVideoTimeUpdate(selectedLesson.id, time);\n }\n };\n\n /**\n * Handle video complete callback\n */\n const handleVideoCompleteCallback = () => {\n if (selectedLesson && onVideoComplete) {\n onVideoComplete(selectedLesson.id);\n }\n };\n\n /**\n * Handle podcast ended\n */\n const handlePodcastEnded = async () => {\n if (selectedLesson && onPodcastEnded && !hasMarkedPodcast.current) {\n hasMarkedPodcast.current = true;\n try {\n await onPodcastEnded(selectedLesson.id);\n } catch {\n // Revert flag if callback failed\n hasMarkedPodcast.current = false;\n }\n }\n };\n\n /**\n * Handle watch lesson\n */\n const handleWatch = (lesson: Lesson) => {\n setSelectedLesson(lesson);\n setIsWatchModalOpen(true);\n hasMarkedPodcast.current = false;\n };\n\n /**\n * Handle close modal\n */\n const handleCloseModal = () => {\n setIsWatchModalOpen(false);\n setSelectedLesson(null);\n hasMarkedPodcast.current = false;\n };\n\n const handleReorder = (fromId: string, toId: string) => {\n if (fromId === toId) return;\n const current = [...orderedLessons];\n const fromIndex = current.findIndex((l) => l.id === fromId);\n const toIndex = current.findIndex((l) => l.id === toId);\n if (fromIndex === -1 || toIndex === -1) return;\n\n const [moved] = current.splice(fromIndex, 1);\n current.splice(toIndex, 0, moved);\n const normalized = normalizeWithPositions(current);\n setOrderedLessons(normalized);\n onReorder?.(normalized);\n onPositionsChange?.(normalized);\n };\n\n const handleSelectActivityOption = (option: ActivityOption) => {\n setIsActivityOptionModalOpen(false);\n if (option === 'create-new' && onCreateNewActivity) {\n onCreateNewActivity();\n } else if (option === 'choose-model') {\n // Aguarda o primeiro modal fechar antes de abrir o próximo\n setTimeout(() => {\n setIsChooseModelModalOpen(true);\n }, 100);\n }\n };\n\n const handleSelectActivityModel = (model: ActivityModelTableItem) => {\n setIsChooseModelModalOpen(false);\n setSelectedActivity(model);\n if (onActivitySelected) {\n onActivitySelected(model);\n }\n // Show success toast\n showSuccess('Atividade adicionada à aula recomendada');\n };\n\n const handleRemoveActivity = () => {\n setSelectedActivity(null);\n if (onRemoveActivity) {\n onRemoveActivity();\n }\n };\n\n const handleEditActivity = () => {\n if (selectedActivity && onEditActivity) {\n onEditActivity(selectedActivity);\n }\n };\n\n return (\n <>\n <div\n className={cn(\n 'w-full flex-shrink-0 p-4 rounded-lg bg-background flex flex-col gap-4',\n className\n )}\n >\n <section className=\"flex flex-row items-center gap-2 text-text-950\">\n <BookIcon size={24} />\n <Text size=\"lg\" weight=\"bold\">\n {title}\n </Text>\n </section>\n\n <section className=\"flex flex-row justify-between items-center\">\n <Text size=\"sm\" className=\"text-text-800\">\n {totalLabel}\n </Text>\n {onRemoveAll && orderedLessons.length > 0 && (\n <Button\n size=\"small\"\n variant=\"link\"\n action=\"negative\"\n iconLeft={<TrashIcon size={16} />}\n onClick={onRemoveAll}\n >\n Remover tudo\n </Button>\n )}\n </section>\n\n {orderedLessons.length === 0 ? (\n <EmptyState\n image={<Activities />}\n title=\"Nenhuma aula adicionada ainda\"\n description=\"Utilize a coluna ao lado para adicionar aulas à aula recomendada.\"\n size=\"compact\"\n />\n ) : (\n <section className=\"flex flex-col gap-3\">\n {orderedLessons.map(\n (\n { id, title: lessonTitle = 'Aula sem título', position },\n index\n ) => (\n <div\n key={id}\n draggable\n data-draggable=\"true\"\n role=\"button\"\n tabIndex={0}\n aria-label={`Mover aula ${lessonTitle}`}\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', id);\n if (e.currentTarget instanceof HTMLElement) {\n const preview = e.currentTarget.querySelector(\n '[data-drag-preview=\"true\"]'\n );\n if (preview) {\n e.dataTransfer.setDragImage(preview, 8, 8);\n } else {\n e.dataTransfer.setDragImage(e.currentTarget, 8, 8);\n }\n }\n }}\n onDragOver={(e) => {\n e.preventDefault();\n }}\n onDrop={(e) => {\n e.preventDefault();\n const fromId = e.dataTransfer.getData('text/plain');\n handleReorder(fromId, id);\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowUp' && index > 0) {\n e.preventDefault();\n const targetId = orderedLessons[index - 1].id;\n handleReorder(id, targetId);\n } else if (\n e.key === 'ArrowDown' &&\n index < orderedLessons.length - 1\n ) {\n e.preventDefault();\n const targetId = orderedLessons[index + 1].id;\n handleReorder(id, targetId);\n } else if (e.key === 'Enter' || e.key === ' ') {\n // Keyboard grab/drop noop; prevent scroll on space\n e.preventDefault();\n }\n }}\n className=\"rounded-lg border border-border-200 bg-background relative group\"\n >\n {/* Hidden drag preview with visual representation of the card */}\n <div\n data-drag-preview=\"true\"\n className=\"fixed -left-[9999px] -top-[9999px] pointer-events-none z-[9999] w-[440px]\"\n >\n <div className=\"w-full rounded-lg border border-border-200 bg-background\">\n <div className=\"p-4 flex flex-row items-center justify-between gap-4\">\n <div className=\"flex flex-row items-center gap-3 flex-1\">\n <Text\n size=\"md\"\n weight=\"bold\"\n className=\"text-text-950 truncate\"\n >\n {lessonTitle}\n </Text>\n </div>\n <div className=\"flex flex-row items-center text-text-950\">\n <Video size={24} color=\"currentColor\" />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"p-4 flex flex-row items-center justify-between gap-4\">\n <div className=\"flex flex-row items-center gap-3 flex-1\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n {lessonTitle}\n </Text>\n </div>\n <div className=\"flex flex-row items-center text-text-950 gap-1\">\n <Button\n variant=\"link\"\n action=\"secondary\"\n onClick={() => {\n const lesson = orderedLessons.find(\n (l) => l.id === id\n );\n if (lesson) {\n handleWatch(lesson);\n }\n }}\n aria-label=\"Assistir aula\"\n className=\"px-0 cursor-pointer\"\n >\n <Video size={24} color=\"currentColor\" />\n </Button>\n {onRemoveLesson && (\n <Button\n variant=\"link\"\n action=\"secondary\"\n onClick={(e) => {\n e.stopPropagation();\n onRemoveLesson(id);\n }}\n aria-label={`Remover aula ${position ?? index + 1}`}\n className=\"px-0 cursor-pointer\"\n >\n <TrashIcon size={24} color=\"currentColor\" />\n </Button>\n )}\n </div>\n </div>\n </div>\n )\n )}\n </section>\n )}\n\n {/* Activity Section */}\n <Divider />\n <section className=\"flex flex-row items-center gap-2 text-text-950\">\n <BookIcon size={24} />\n <Text size=\"lg\" weight=\"bold\">\n Atividade da aula\n </Text>\n </section>\n\n {selectedActivity ? (\n <div className=\"flex flex-col gap-3\">\n <div className=\"bg-background rounded-lg\">\n <div className=\"p-4 flex flex-row items-center justify-between gap-4\">\n <div className=\"flex flex-row items-center gap-3 flex-1\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n {selectedActivity.title}\n </Text>\n </div>\n <div className=\"flex flex-row items-center text-text-950 gap-1\">\n <Button\n variant=\"link\"\n action=\"secondary\"\n onClick={handleEditActivity}\n aria-label=\"Editar atividade\"\n className=\"px-0 cursor-pointer\"\n >\n <PencilSimpleIcon size={24} color=\"currentColor\" />\n </Button>\n <Button\n variant=\"link\"\n action=\"secondary\"\n onClick={handleRemoveActivity}\n aria-label=\"Remover atividade\"\n className=\"px-0 cursor-pointer\"\n >\n <TrashIcon size={24} color=\"currentColor\" />\n </Button>\n </div>\n </div>\n </div>\n </div>\n ) : (\n <Button\n variant=\"outline\"\n action=\"primary\"\n onClick={() => setIsActivityOptionModalOpen(true)}\n className=\"w-full\"\n >\n Adicionar atividade\n </Button>\n )}\n </div>\n\n <LessonWatchModal\n isOpen={isWatchModalOpen}\n onClose={handleCloseModal}\n selectedLesson={selectedLesson}\n title={\n selectedLesson?.content?.name ||\n selectedLesson?.videoTitle ||\n selectedLesson?.content?.bnccCode ||\n selectedLesson?.title ||\n 'Assistir Aula'\n }\n userId={userId}\n getVideoData={getVideoData}\n getInitialTimestampValue={getInitialTimestampValue}\n handleVideoTimeUpdate={handleVideoTimeUpdate}\n handleVideoCompleteCallback={handleVideoCompleteCallback}\n getPodcastData={getPodcastData}\n onPodcastEnded={handlePodcastEnded}\n getBoardImages={getBoardImages}\n getBoardImageRef={getBoardImageRef}\n />\n\n {/* Activity Option Modal */}\n <AddActivityOptionModal\n isOpen={isActivityOptionModalOpen}\n onClose={() => setIsActivityOptionModalOpen(false)}\n onSelectOption={handleSelectActivityOption}\n />\n\n {/* Choose Activity Model Modal */}\n <ChooseActivityModelModal\n isOpen={isChooseModelModalOpen}\n onClose={() => setIsChooseModelModalOpen(false)}\n onSelectModel={handleSelectActivityModel}\n apiClient={apiClient}\n />\n\n {/* Toast Notification */}\n <ToastNotification\n isOpen={toastState.isOpen}\n onClose={hideToast}\n title={toastState.title}\n description={toastState.description}\n action={toastState.action}\n />\n </>\n );\n};\n\nexport type { LessonPreviewProps, PreviewLesson };\n","import { Modal, Text, SelectionButton } from '../../../index';\nimport { FileTextIcon } from '@phosphor-icons/react/dist/csr/FileText';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\n\nexport type ActivityOption = 'choose-model' | 'create-new';\n\ninterface AddActivityOptionModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSelectOption: (option: ActivityOption) => void;\n}\n\n/**\n * Modal for selecting activity option (choose model or create new)\n */\nexport const AddActivityOptionModal = ({\n isOpen,\n onClose,\n onSelectOption,\n}: AddActivityOptionModalProps) => {\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Selecione uma opção\"\n size=\"sm\"\n >\n <div className=\"flex flex-col gap-4\">\n <Text size=\"md\" className=\"text-text-800\">\n Como deseja adicionar a atividade?\n </Text>\n <div className=\"flex flex-col gap-2\">\n <SelectionButton\n icon={<FileTextIcon size={24} />}\n label=\"Escolher modelo de atividade\"\n selected={false}\n onClick={() => onSelectOption('choose-model')}\n className=\"w-full\"\n />\n <SelectionButton\n icon={<PlusIcon size={24} />}\n label=\"Criar nova atividade\"\n selected={false}\n onClick={() => onSelectOption('create-new')}\n className=\"w-full\"\n />\n </div>\n </div>\n </Modal>\n );\n};\n","import { useState, useEffect, useCallback } from 'react';\nimport dayjs from 'dayjs';\nimport { Modal, Button } from '../../index';\nimport { ActivityModelsList } from './components/ActivityModelsList';\nimport { ActivityModelDetails } from './components/ActivityModelDetails';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n ActivityModelTableItem,\n ActivityModelFilters,\n ActivityModelsApiResponse,\n ActivityModelResponse,\n} from '../../types/activitiesHistory';\nimport { ActivityDraftType } from '../../types/activitiesHistory';\nimport type { ActivityData } from '../ActivityCreate/ActivityCreate.types';\nimport { ActivityType } from '../ActivityCreate/ActivityCreate.types';\nimport type { TableParams } from '../TableProvider/TableProvider';\n\n/**\n * Map ActivityDraftType to ActivityType\n * @param draftType - Draft type from API response\n * @returns Corresponding ActivityType\n */\nconst mapDraftTypeToActivityType = (\n draftType: ActivityDraftType\n): ActivityType => {\n switch (draftType) {\n case ActivityDraftType.MODELO:\n return ActivityType.MODELO;\n case ActivityDraftType.RASCUNHO:\n return ActivityType.RASCUNHO;\n default:\n return ActivityType.RASCUNHO;\n }\n};\n\n/**\n * Transform API response to table item format for ChooseActivityModelModal\n * @param model - Activity model from API response\n * @returns Formatted model for table display\n */\nconst transformActivityModelToTableItem = (\n model: ActivityModelResponse\n): ActivityModelTableItem => {\n return {\n id: model.id,\n type: mapDraftTypeToActivityType(model.type),\n title: model.title || 'Sem título',\n savedAt: dayjs(model.createdAt).format('DD/MM/YYYY'),\n subject: model.subject || null,\n subjectId: model.subject?.id ?? model.subjectId,\n };\n};\n\nexport interface ChooseActivityModelModalProps {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Function to close the modal */\n onClose: () => void;\n /** Callback when a model is selected */\n onSelectModel: (model: ActivityModelTableItem) => void;\n /** API client for making requests */\n apiClient: BaseApiClient;\n}\n\n/**\n * Modal for choosing an activity model from the list\n * Displays a table with activity models that can be selected\n */\ntype ModalView = 'list' | 'details';\n\nexport const ChooseActivityModelModal = ({\n isOpen,\n onClose,\n onSelectModel,\n apiClient,\n}: ChooseActivityModelModalProps) => {\n const [currentView, setCurrentView] = useState<ModalView>('list');\n const [selectedModel, setSelectedModel] =\n useState<ActivityModelTableItem | null>(null);\n const [models, setModels] = useState<ActivityModelTableItem[]>([]);\n const [loading, setLoading] = useState(false);\n const [activityDetails, setActivityDetails] = useState<ActivityData | null>(\n null\n );\n const [loadingDetails, setLoadingDetails] = useState(false);\n\n const handleParamsChange = useCallback(\n async (params: TableParams) => {\n setLoading(true);\n try {\n const filters: ActivityModelFilters = {\n page: params.page,\n limit: params.limit,\n search: params.search,\n type: ActivityDraftType.MODELO,\n };\n\n const response = await apiClient.get<ActivityModelsApiResponse>(\n '/activity-drafts',\n {\n params: filters as Record<string, unknown>,\n }\n );\n\n // Transform API response to table items\n const tableItems = response.data.data.activityDrafts.map(\n transformActivityModelToTableItem\n );\n\n setModels(tableItems);\n } catch (error) {\n console.error('Error fetching models:', error);\n setModels([]);\n } finally {\n setLoading(false);\n }\n },\n [apiClient]\n );\n\n useEffect(() => {\n if (isOpen) {\n setSelectedModel(null);\n setCurrentView('list');\n setActivityDetails(null);\n handleParamsChange({ page: 1, limit: 10 });\n }\n }, [isOpen, handleParamsChange]);\n\n const fetchActivityDetails = useCallback(\n async (activityId: string) => {\n setLoadingDetails(true);\n try {\n const response = await apiClient.get<\n { data: ActivityData } | ActivityData\n >(`/activity-drafts/${activityId}`);\n\n // Handle both response formats\n const data =\n 'data' in response.data ? response.data.data : response.data;\n setActivityDetails(data);\n setCurrentView('details');\n } catch (error) {\n console.error('Error fetching activity details:', error);\n setActivityDetails(null);\n } finally {\n setLoadingDetails(false);\n }\n },\n [apiClient]\n );\n\n const handleRowClick = useCallback(\n (row: ActivityModelTableItem) => {\n setSelectedModel(row);\n fetchActivityDetails(row.id);\n },\n [fetchActivityDetails]\n );\n\n const handleCancelDetails = useCallback(() => {\n // Fecha ambos os modais ao invés de voltar para a lista\n onClose();\n // Limpa o estado\n setCurrentView('list');\n setSelectedModel(null);\n setActivityDetails(null);\n }, [onClose]);\n\n const handleConfirmSelection = useCallback(() => {\n if (selectedModel) {\n onSelectModel(selectedModel);\n setSelectedModel(null);\n setActivityDetails(null);\n setCurrentView('list');\n }\n }, [selectedModel, onSelectModel]);\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={\n currentView === 'list'\n ? 'Adicionar atividade'\n : activityDetails?.title || 'Detalhes da atividade'\n }\n size=\"xl\"\n footer={\n currentView === 'list' ? (\n <div className=\"flex gap-3 justify-end\">\n <Button variant=\"outline\" onClick={onClose}>\n Cancelar\n </Button>\n </div>\n ) : (\n <div className=\"flex gap-3 justify-end\">\n <Button variant=\"outline\" onClick={handleCancelDetails}>\n Cancelar\n </Button>\n <Button variant=\"solid\" onClick={handleConfirmSelection}>\n Adicionar atividade\n </Button>\n </div>\n )\n }\n >\n {currentView === 'list' ? (\n <ActivityModelsList\n models={models}\n loading={loading}\n onParamsChange={handleParamsChange}\n onRowClick={handleRowClick}\n />\n ) : (\n <ActivityModelDetails\n activityDetails={activityDetails}\n loading={loadingDetails}\n />\n )}\n </Modal>\n );\n};\n","import { useMemo } from 'react';\nimport { TableProvider, IconRender, Text, TruncatedText } from '../../../index';\nimport { createModelsTableColumnsBase } from '../../shared/ModelsTabBase/createModelsTableColumnsBase';\nimport type {\n ActivityModelTableItem,\n SubjectData,\n} from '../../../types/activitiesHistory';\nimport type {\n TableParams,\n ColumnConfig,\n} from '../../TableProvider/TableProvider';\n\ninterface ActivityModelsListProps {\n /** List of activity models to display */\n models: ActivityModelTableItem[];\n /** Loading state */\n loading: boolean;\n /** Callback when table params change (pagination, search, etc) */\n onParamsChange: (params: TableParams) => void;\n /** Callback when a row is clicked */\n onRowClick: (model: ActivityModelTableItem) => void;\n}\n\n/**\n * List of activity models displayed in a table\n * Used as content for the ChooseActivityModelModal\n */\nexport const ActivityModelsList = ({\n models,\n loading,\n onParamsChange,\n onRowClick,\n}: ActivityModelsListProps) => {\n // Create table columns without actions column\n const tableColumns = useMemo<ColumnConfig<ActivityModelTableItem>[]>(() => {\n const baseColumns = createModelsTableColumnsBase<ActivityModelTableItem>(\n undefined, // No subject mapping needed\n undefined, // No send button\n undefined, // No edit button\n () => {}, // No delete button\n {\n sendButtonLabel: '',\n sendButtonAriaLabel: '',\n deleteButtonAriaLabel: '',\n editButtonAriaLabel: '',\n }\n );\n\n // Replace subject column to handle SubjectData object\n const columnsWithoutSubject = baseColumns.filter(\n (col) => col.key !== 'actions' && col.key !== 'subject'\n );\n\n return columnsWithoutSubject.concat([\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[160px]',\n render: (value: unknown) => {\n const subject = value as SubjectData | null;\n if (!subject) {\n return (\n <Text size=\"sm\" color=\"text-text-400\">\n -\n </Text>\n );\n }\n\n return (\n <div className=\"flex items-center gap-2 min-w-0\">\n <span\n className=\"w-[21px] h-[21px] flex items-center justify-center rounded-sm text-text-950 shrink-0\"\n style={{ backgroundColor: subject.color }}\n >\n <IconRender\n iconName={subject.icon}\n size={17}\n color=\"currentColor\"\n />\n </span>\n <TruncatedText size=\"sm\">{subject.name}</TruncatedText>\n </div>\n );\n },\n },\n ]);\n }, []);\n\n return (\n <div className=\"flex flex-col gap-4\">\n <TableProvider\n data={models}\n headers={tableColumns}\n loading={loading}\n enableSearch\n enablePagination\n enableRowClick\n searchPlaceholder=\"Buscar modelo\"\n onParamsChange={onParamsChange}\n onRowClick={onRowClick}\n rowKey=\"id\"\n />\n </div>\n );\n};\n\nexport type { ActivityModelsListProps };\n","import type { MouseEvent } from 'react';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { PencilSimpleIcon } from '@phosphor-icons/react/dist/csr/PencilSimple';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport { TruncatedText } from '../../TruncatedText/TruncatedText';\nimport Button from '../../Button/Button';\nimport IconButton from '../../IconButton/IconButton';\nimport { renderSubjectCell } from '../../../utils/renderSubjectCell';\nimport type { ColumnConfig } from '../../TableProvider/TableProvider';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Base model item interface - all model types must extend this.\n * Includes index signature for TableProvider compatibility.\n */\nexport interface BaseModelItem {\n id: string;\n title: string;\n subject?:\n | string\n | {\n id: string;\n subjectName?: string;\n subjectIcon?: string;\n subjectColor?: string;\n name?: string;\n icon?: string;\n color?: string;\n }\n | null;\n savedAt?: string;\n [key: string]: unknown;\n}\n\n/**\n * Configuration for model columns labels and accessibility\n */\nexport interface ModelsColumnsConfig {\n /** Send button label (e.g., \"Enviar aula\", \"Enviar atividade\") */\n sendButtonLabel: string;\n /** Send button aria-label */\n sendButtonAriaLabel: string;\n /** Delete button aria-label */\n deleteButtonAriaLabel: string;\n /** Edit button aria-label */\n editButtonAriaLabel: string;\n}\n\n/**\n * Creates base table columns configuration for models.\n * Can be used by both RecommendedClassModels and ActivityModels with type-safe generics.\n * @param mapSubjectNameToEnum - Optional function to map subject names to enum values\n * @param onSend - Callback when send button is clicked\n * @param onEdit - Callback when edit button is clicked\n * @param onDelete - Callback when delete button is clicked\n * @param config - Configuration for labels and accessibility\n * @returns Array of column configurations for the models table\n */\nexport const createModelsTableColumnsBase = <T extends BaseModelItem>(\n mapSubjectNameToEnum: ((name: string) => SubjectEnum | null) | undefined,\n onSend: ((model: T) => void) | undefined,\n onEdit: ((model: T) => void) | undefined,\n onDelete: (model: T) => void,\n config: ModelsColumnsConfig\n): ColumnConfig<T>[] => [\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[400px]',\n render: (value: unknown) => {\n const title = typeof value === 'string' ? value : '';\n return <TruncatedText size=\"sm\">{title}</TruncatedText>;\n },\n },\n {\n key: 'savedAt',\n label: 'Salvo em',\n sortable: true,\n className: 'w-[120px]',\n },\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[160px]',\n render: (value: unknown) => {\n const subjectName = typeof value === 'string' ? value : '';\n return renderSubjectCell(subjectName, mapSubjectNameToEnum, true);\n },\n },\n {\n key: 'actions',\n label: '',\n sortable: false,\n className: 'w-[220px]',\n render: (_value: unknown, row: T) => {\n const handleSend = (e: MouseEvent) => {\n e.stopPropagation();\n onSend?.(row);\n };\n\n const handleEdit = (e: MouseEvent) => {\n e.stopPropagation();\n onEdit?.(row);\n };\n\n const handleDelete = (e: MouseEvent) => {\n e.stopPropagation();\n onDelete(row);\n };\n\n return (\n <div className=\"flex items-center gap-2 justify-end\">\n {onSend && (\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"small\"\n iconLeft={<PaperPlaneTiltIcon size={16} />}\n onClick={handleSend}\n aria-label={config.sendButtonAriaLabel}\n >\n {config.sendButtonLabel}\n </Button>\n )}\n <IconButton\n icon={<TrashIcon size={20} />}\n size=\"md\"\n onClick={handleDelete}\n aria-label={config.deleteButtonAriaLabel}\n className=\"text-text-600 hover:text-error-500 hover:bg-transparent\"\n />\n {onEdit && (\n <IconButton\n icon={<PencilSimpleIcon size={20} />}\n size=\"md\"\n onClick={handleEdit}\n aria-label={config.editButtonAriaLabel}\n className=\"text-text-600 hover:text-primary-700 hover:bg-transparent\"\n />\n )}\n </div>\n );\n },\n },\n];\n","import { useMemo, useState, type ReactNode } from 'react';\nimport { CardAccordation } from '../Accordation/Accordation';\nimport {\n IconRender,\n Text,\n getSubjectColorWithOpacity,\n Badge,\n} from '../../index';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport { questionTypeLabels } from '../../types/questionTypes';\nimport { cn } from '../../utils/utils';\nimport { AlternativesList, type Alternative } from '../Alternative/Alternative';\nimport { OptionStatus } from '../../enums/Options';\nimport { MultipleChoiceList } from '../MultipleChoice/MultipleChoice';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport {\n renderFromMap,\n type QuestionRendererMap,\n} from '../../utils/questionRenderer/index';\nimport { HtmlMathRenderer, stripHtml } from '../HtmlMathRenderer';\n\nexport interface MatchingPairPreview {\n id: string;\n option: string; // Left column value (e.g., \"Gato\")\n correctValue: string; // Right column value (e.g., \"Leite\")\n}\n\ninterface ActivityCardQuestionPreviewProps {\n subjectName?: string;\n subjectColor?: string;\n iconName?: string;\n bank?: string;\n year?: string;\n isDark?: boolean;\n questionType?: QUESTION_TYPE;\n /**\n * Optional label override when questionType is not provided.\n */\n questionTypeLabel?: string;\n statement?: string;\n question?: {\n options: {\n id: string;\n option: string;\n isCorrect?: boolean;\n }[];\n correctOptionIds?: string[];\n };\n /**\n * Matching pairs for RELACIONAR question type.\n * Each pair has an option (left column) and correctValue (right column).\n */\n matchingPairs?: MatchingPairPreview[];\n /**\n * Optional solution/explanation (gabarito comentado). When provided, renders a\n * \"Resolução\" block at the end of the expanded card.\n */\n solutionExplanation?: string | null;\n defaultExpanded?: boolean;\n value?: string;\n className?: string;\n children?: ReactNode;\n position?: number;\n}\n\nconst QuestionHeader = ({\n badgeColor,\n iconName,\n subjectName,\n resolvedQuestionTypeLabel,\n bank,\n year,\n position,\n}: {\n badgeColor: string;\n iconName?: string;\n subjectName?: string;\n resolvedQuestionTypeLabel?: string;\n bank?: string;\n year?: string;\n position?: number;\n}) => (\n <div className=\"flex flex-row gap-2 text-text-650\">\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <span\n className=\"size-4 rounded-sm flex items-center justify-center shrink-0 text-text-950\"\n style={{\n backgroundColor: badgeColor,\n }}\n >\n <IconRender\n iconName={iconName ?? 'Book'}\n size={14}\n color=\"currentColor\"\n />\n </span>\n <Text size=\"sm\">{subjectName ?? 'Assunto não informado'}</Text>\n </div>\n\n {typeof position === 'number' && (\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <Text size=\"sm\" className=\"text-text-700\">\n #{position}\n </Text>\n </div>\n )}\n\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <Text size=\"sm\" className=\"\">\n {resolvedQuestionTypeLabel ?? 'Tipo de questão'}\n </Text>\n </div>\n\n {(bank || year) && (\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <Text size=\"sm\">{[bank, year].filter(Boolean).join(' - ')}</Text>\n </div>\n )}\n </div>\n);\n\nexport const ActivityCardQuestionPreview = ({\n subjectName = 'Assunto não informado',\n subjectColor = '#000000',\n iconName = 'Book',\n bank,\n year,\n isDark = false,\n questionType,\n questionTypeLabel,\n statement = 'Enunciado não informado',\n question,\n matchingPairs,\n solutionExplanation,\n defaultExpanded = false,\n value,\n className,\n children,\n position,\n}: ActivityCardQuestionPreviewProps) => {\n const badgeColor =\n getSubjectColorWithOpacity(subjectColor, isDark) ?? subjectColor;\n const [isExpanded, setIsExpanded] = useState(defaultExpanded);\n const correctOptionIds = question?.correctOptionIds || [];\n\n const resolvedQuestionTypeLabel =\n questionType && questionTypeLabels[questionType]\n ? questionTypeLabels[questionType]\n : questionTypeLabel || 'Tipo de questão';\n const safeSubjectName: string = subjectName ?? 'Assunto não informado';\n const safeIconName: string = iconName ?? 'Book';\n const safeResolvedLabel: string =\n resolvedQuestionTypeLabel ?? 'Tipo de questão';\n\n const alternatives = useMemo<Alternative[]>(() => {\n if (!question?.options || questionType !== QUESTION_TYPE.ALTERNATIVA)\n return [];\n\n return question.options.map((option) => {\n const isCorrect = correctOptionIds.includes(option.id);\n return {\n value: option.id,\n label: option.option,\n status: isCorrect ? OptionStatus.CORRECT : OptionStatus.INCORRECT,\n disabled: !isCorrect,\n };\n });\n }, [question, questionType, correctOptionIds]);\n\n const multipleChoices = useMemo(() => {\n if (!question?.options || questionType !== QUESTION_TYPE.MULTIPLA_ESCOLHA)\n return [];\n\n return question.options.map((option) => {\n const isCorrect = correctOptionIds.includes(option.id);\n return {\n value: option.id,\n label: option.option,\n status: isCorrect ? OptionStatus.CORRECT : OptionStatus.INCORRECT,\n disabled: !isCorrect,\n };\n });\n }, [question, questionType, correctOptionIds]);\n\n const renderAlternative = () => {\n if (alternatives.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <AlternativesList\n alternatives={alternatives}\n mode=\"readonly\"\n layout=\"compact\"\n selectedValue={correctOptionIds[0]}\n name={`preview-alternatives-${value ?? subjectName}`}\n />\n </div>\n );\n };\n\n const renderMultipleChoice = () => {\n if (multipleChoices.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <MultipleChoiceList\n choices={multipleChoices}\n mode=\"readonly\"\n selectedValues={correctOptionIds}\n name={`preview-multiple-${value ?? subjectName}`}\n />\n </div>\n );\n };\n\n const renderTrueOrFalse = () => {\n if (!question?.options || question.options.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <div className=\"flex flex-col gap-3.5\">\n {question.options.map((option, index) => {\n // For VERDADEIRO_FALSO, use isCorrect from option\n const isCorrect =\n option.isCorrect ?? correctOptionIds.includes(option.id);\n const correctAnswer = isCorrect ? 'Verdadeiro' : 'Falso';\n\n return (\n <section key={option.id} className=\"flex flex-col gap-2\">\n <div\n className={cn(\n 'flex flex-row justify-between items-center gap-2 p-2 rounded-md border',\n isCorrect\n ? 'bg-success-background border-success-300'\n : 'bg-error-background border-error-300'\n )}\n >\n <Text size=\"sm\" className=\"text-text-900\">\n {String.fromCodePoint(97 + index)\n .concat(') ')\n .concat(option.option)}\n </Text>\n\n <div className=\"flex flex-row items-center gap-2 flex-shrink-0\">\n <Text size=\"sm\" className=\"text-text-700\">\n Resposta correta: {correctAnswer}\n </Text>\n <Badge\n variant=\"solid\"\n action={isCorrect ? 'success' : 'error'}\n iconLeft={\n isCorrect ? <CheckCircleIcon /> : <XCircleIcon />\n }\n >\n {isCorrect ? 'Resposta correta' : 'Resposta incorreta'}\n </Badge>\n </div>\n </div>\n </section>\n );\n })}\n </div>\n </div>\n );\n };\n\n const renderDissertative = () => {\n return (\n <div className=\"mt-4 px-2 py-4\">\n <Text size=\"sm\" className=\"text-text-600 italic\">\n Resposta do aluno\n </Text>\n </div>\n );\n };\n\n const renderConnectDots = () => {\n if (!matchingPairs || matchingPairs.length === 0) return null;\n\n return (\n <div className=\"mt-4\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-700 mb-3\">\n Alternativas\n </Text>\n <div className=\"flex flex-col gap-3.5\">\n {matchingPairs.map((pair, index) => {\n const letter = String.fromCodePoint(97 + index); // 'a', 'b', 'c'...\n\n return (\n <section key={pair.id} className=\"flex flex-col gap-2\">\n <div\n className={cn(\n 'flex flex-row justify-between items-center gap-2 p-2 rounded-md border',\n 'bg-success-background border-success-300'\n )}\n >\n <Text size=\"sm\" className=\"text-text-900\">\n {letter}) {pair.option}\n </Text>\n\n <div className=\"flex flex-row items-center gap-2 shrink-0\">\n <Text size=\"sm\" className=\"text-text-700\">\n Resposta correta: {pair.correctValue}\n </Text>\n <Badge\n variant=\"solid\"\n action=\"success\"\n iconLeft={<CheckCircleIcon />}\n >\n Resposta correta\n </Badge>\n </div>\n </div>\n </section>\n );\n })}\n </div>\n </div>\n );\n };\n\n const renderFill = () => null;\n const renderImage = () => null;\n\n const questionRenderers: QuestionRendererMap = {\n [QUESTION_TYPE.ALTERNATIVA]: renderAlternative,\n [QUESTION_TYPE.MULTIPLA_ESCOLHA]: renderMultipleChoice,\n [QUESTION_TYPE.DISSERTATIVA]: renderDissertative,\n [QUESTION_TYPE.VERDADEIRO_FALSO]: renderTrueOrFalse,\n [QUESTION_TYPE.RELACIONAR]: renderConnectDots,\n [QUESTION_TYPE.PREENCHER_LACUNAS]: renderFill,\n [QUESTION_TYPE.IMAGEM]: renderImage,\n };\n\n return (\n <div\n className=\"w-full\"\n data-position={position}\n role=\"button\"\n tabIndex={0}\n aria-expanded={isExpanded}\n onClick={() => {\n if (isExpanded) {\n setIsExpanded(false);\n }\n }}\n onMouseDown={(event) => {\n // Allow drag to start if inside a draggable container; otherwise avoid focus outline\n const draggableAncestor = (event.target as HTMLElement).closest(\n '[data-draggable=\"true\"]'\n );\n if (!draggableAncestor) {\n event.preventDefault();\n }\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n if (isExpanded) {\n setIsExpanded(false);\n }\n }\n }}\n >\n {/* Hidden drag preview with header + truncated statement (closed state) */}\n <div\n data-drag-preview=\"true\"\n className=\"fixed -left-[9999px] -top-[9999px] pointer-events-none z-[9999] w-[440px]\"\n >\n <div className=\"w-full rounded-lg border border-border-200 bg-background\">\n <div className=\"w-full min-w-0 flex flex-col gap-2 py-2\">\n <QuestionHeader\n badgeColor={badgeColor}\n iconName={safeIconName}\n subjectName={safeSubjectName}\n resolvedQuestionTypeLabel={safeResolvedLabel}\n bank={bank}\n year={year}\n position={position}\n />\n\n <Text\n size=\"md\"\n weight=\"medium\"\n className=\"text-text-950 truncate px-3\"\n >\n {stripHtml(statement)}\n </Text>\n </div>\n </div>\n </div>\n\n <CardAccordation\n className={cn(\n 'w-full rounded-lg border border-border-200 bg-background',\n className\n )}\n expanded={isExpanded}\n onToggleExpanded={setIsExpanded}\n defaultExpanded={defaultExpanded}\n value={value}\n trigger={\n <div className=\"w-full min-w-0 flex flex-col gap-2 py-2\">\n <QuestionHeader\n badgeColor={badgeColor}\n iconName={safeIconName}\n subjectName={safeSubjectName}\n resolvedQuestionTypeLabel={safeResolvedLabel}\n bank={bank}\n year={year}\n position={position}\n />\n\n {!isExpanded && (\n <Text\n size=\"md\"\n weight=\"medium\"\n className=\"text-text-950 truncate px-3\"\n >\n {stripHtml(statement)}\n </Text>\n )}\n </div>\n }\n >\n <HtmlMathRenderer\n content={statement}\n className=\"text-text-950 text-md break-words\"\n />\n {renderFromMap(questionRenderers, questionType)}\n {solutionExplanation?.replaceAll(/<[^<>]*>/g, '').trim() && (\n <div className=\"mt-4 rounded-lg border border-info-300 bg-info-background p-3\">\n <Text size=\"sm\" weight=\"bold\" className=\"text-info-700 mb-1\">\n Resolução\n </Text>\n <HtmlMathRenderer\n content={solutionExplanation}\n className=\"text-text-900 text-sm break-words\"\n />\n </div>\n )}\n {children}\n </CardAccordation>\n </div>\n );\n};\n\nexport type { ActivityCardQuestionPreviewProps };\n","import { Text } from '../../../index';\nimport { ActivityCardQuestionPreview } from '../../ActivityCardQuestionPreview/ActivityCardQuestionPreview';\nimport type { ActivityData } from '../../ActivityCreate/ActivityCreate.types';\n\ninterface ActivityModelDetailsProps {\n /** Activity data with questions */\n activityDetails: ActivityData | null;\n /** Loading state */\n loading: boolean;\n}\n\n/**\n * Detailed view of an activity model showing its questions\n * Used as content for the ChooseActivityModelModal\n */\nexport const ActivityModelDetails = ({\n activityDetails,\n loading,\n}: ActivityModelDetailsProps) => {\n const totalQuestions = activityDetails?.selectedQuestions?.length ?? 0;\n const totalQuestionsLabel =\n totalQuestions === 1\n ? '1 questão adicionada'\n : `${totalQuestions} questões adicionadas`;\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando detalhes...\n </Text>\n </div>\n );\n }\n\n if (!activityDetails) {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhum detalhe disponível\n </Text>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <Text size=\"sm\" className=\"text-text-800\">\n {totalQuestionsLabel}\n </Text>\n </div>\n\n <div className=\"flex flex-col gap-3 max-h-[500px] overflow-y-auto\">\n {activityDetails.selectedQuestions?.map((question, index) => (\n <div\n key={question.id}\n className=\"rounded-lg border border-border-200 bg-background\"\n >\n <ActivityCardQuestionPreview\n subjectName={\n question.knowledgeMatrix?.[0]?.subject?.name ??\n 'Assunto não informado'\n }\n subjectColor={\n question.knowledgeMatrix?.[0]?.subject?.color ?? '#000000'\n }\n iconName={\n question.knowledgeMatrix?.[0]?.subject?.icon ?? 'BookOpen'\n }\n questionType={question.questionType}\n statement={question.statement}\n defaultExpanded={false}\n question={{\n options:\n question.options?.map((opt) => ({\n id: opt.id,\n option: opt.option,\n isCorrect: opt.isCorrect,\n })) ?? [],\n correctOptionIds:\n question.options\n ?.filter((opt) => opt.isCorrect)\n .map((opt) => opt.id) ?? [],\n }}\n value={question.id}\n position={index + 1}\n />\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport type { ActivityModelDetailsProps };\n","import { create } from 'zustand';\nimport type { LessonFiltersData } from '../types/lessonFilters';\nimport type { Lesson, LessonsPagination } from '../types/lessons';\n\nexport interface LessonFiltersState {\n // Filter states\n draftFilters: LessonFiltersData | null;\n appliedFilters: LessonFiltersData | null;\n setDraftFilters: (filters: LessonFiltersData | null) => void;\n applyFilters: () => void;\n clearFilters: () => void;\n\n // Lessons cache state\n cachedLessons: Lesson[];\n cachedPagination: LessonsPagination | null;\n cachedFilters: LessonFiltersData | null;\n setCachedLessons: (\n lessons: Lesson[],\n pagination: LessonsPagination | null,\n filters: LessonFiltersData | null\n ) => void;\n clearCachedLessons: () => void;\n}\n\n/**\n * Zustand store to manage lesson filters with draft vs applied separation\n * and cache for lessons list to avoid unnecessary refetches\n */\nexport const useLessonFiltersStore = create<LessonFiltersState>((set) => ({\n // Filter states\n draftFilters: null,\n appliedFilters: null,\n setDraftFilters: (filters) => {\n set({ draftFilters: filters });\n },\n applyFilters: () => {\n set((state) => ({\n appliedFilters: state.draftFilters,\n }));\n },\n clearFilters: () => {\n set({\n draftFilters: null,\n appliedFilters: null,\n // Clear cached lessons when filters are cleared\n cachedLessons: [],\n cachedPagination: null,\n cachedFilters: null,\n });\n },\n\n // Lessons cache state\n cachedLessons: [],\n cachedPagination: null,\n cachedFilters: null,\n setCachedLessons: (lessons, pagination, filters) => {\n set({\n cachedLessons: lessons,\n cachedPagination: pagination,\n cachedFilters: filters,\n });\n },\n clearCachedLessons: () => {\n set({\n cachedLessons: [],\n cachedPagination: null,\n cachedFilters: null,\n });\n },\n}));\n","import type { LessonFiltersData } from '../../types/lessonFilters';\nimport type { Lesson } from '../../types/lessons';\nimport type { LessonBackendFiltersFormat } from './RecommendedLessonCreate.types';\nimport { RecommendedClassDraftType } from './RecommendedLessonCreate.types';\n\n/**\n * Knowledge area interface for subject lookup\n */\nexport interface KnowledgeArea {\n id: string;\n name: string;\n}\n\n/**\n * Preview lesson type for LessonPreview component\n * Includes media fields mapped from API response\n */\nexport interface PreviewLesson extends Lesson {\n position?: number;\n videoSrc?: string;\n videoPoster?: string;\n videoSubtitles?: string;\n podcastSrc?: string;\n podcastTitle?: string;\n}\n\n/**\n * Convert LessonFiltersData to backend format\n *\n * @param filters - LessonFiltersData to convert, can be null\n * @returns LessonBackendFiltersFormat object with converted filter data\n */\nexport function convertFiltersToBackendFormat(\n filters: LessonFiltersData | null\n): LessonBackendFiltersFormat {\n if (!filters) {\n return {\n subjects: [],\n topics: [],\n subtopics: [],\n contents: [],\n };\n }\n\n return {\n subjects: filters.subjectIds,\n topics: filters.topicIds,\n subtopics: filters.subtopicIds,\n contents: filters.contentIds,\n };\n}\n\n/**\n * Convert backend filters format to LessonFiltersData\n *\n * @param backendFilters - LessonBackendFiltersFormat to convert, can be null\n * @returns LessonFiltersData object with converted filter data, or null if input is null\n */\nexport function convertBackendFiltersToLessonFiltersData(\n backendFilters: LessonBackendFiltersFormat | null\n): LessonFiltersData | null {\n if (!backendFilters) {\n return null;\n }\n\n return {\n subjectIds: backendFilters.subjects || [],\n topicIds: backendFilters.topics || [],\n subtopicIds: backendFilters.subtopics || [],\n contentIds: backendFilters.contents || [],\n };\n}\n\n/**\n * Get subject name from subjectId by searching in knowledge areas\n *\n * @param subjectId - The subject ID to look up\n * @param knowledgeAreas - Array of knowledge areas to search in\n * @returns The subject name if found, null otherwise\n */\nexport function getSubjectName(\n subjectId: string | null,\n knowledgeAreas: KnowledgeArea[]\n): string | null {\n if (!subjectId || !knowledgeAreas.length) {\n return null;\n }\n const subject = knowledgeAreas.find((area) => area.id === subjectId);\n return subject?.name || null;\n}\n\n/**\n * Get goal draft type label using object literal mapping\n *\n * @param type - RecommendedClassDraftType enum value\n * @returns Label string for the goal draft type\n */\nexport function getGoalDraftTypeLabel(type: RecommendedClassDraftType): string {\n const typeLabels: Record<RecommendedClassDraftType, string> = {\n [RecommendedClassDraftType.RASCUNHO]: 'Rascunho',\n [RecommendedClassDraftType.MODELO]: 'Modelo',\n };\n return typeLabels[type];\n}\n\n/**\n * Generate recommended lesson title based on type, subject, first lesson, and date\n *\n * @param type - RecommendedClassDraftType enum value\n * @param subjectId - Subject ID to get name from\n * @param knowledgeAreas - Array of knowledge areas for subject lookup\n * @param lessons - Array of lessons (optional, to get first lesson name)\n * @returns Generated title string\n */\nexport function generateTitle(\n type: RecommendedClassDraftType,\n subjectId: string | null,\n knowledgeAreas: KnowledgeArea[],\n lessons?: Lesson[]\n): string {\n const typeLabel = getGoalDraftTypeLabel(type);\n const subjectName = getSubjectName(subjectId, knowledgeAreas);\n\n // Get first lesson name if available\n const firstLessonName =\n lessons && lessons.length > 0\n ? lessons[0].content?.name ||\n lessons[0].videoTitle ||\n lessons[0].content?.bnccCode ||\n lessons[0].title ||\n null\n : null;\n\n // Format current date as DD/MM/YYYY\n const currentDate = new Date();\n const formattedDate = currentDate.toLocaleDateString('pt-BR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n });\n\n // Build title parts\n const parts: string[] = [typeLabel];\n\n if (subjectName) {\n parts.push(subjectName);\n }\n\n if (firstLessonName) {\n // Truncate lesson name if too long (max 30 chars)\n const truncatedLessonName =\n firstLessonName.length > 30\n ? `${firstLessonName.substring(0, 27)}...`\n : firstLessonName;\n parts.push(truncatedLessonName);\n }\n\n // Always add date at the end\n parts.push(formattedDate);\n\n return parts.join(' - ');\n}\n\n/**\n * Convert RecommendedClassDraftType to the format used in URL\n *\n * @param type - RecommendedClassDraftType enum value\n * @returns String representation for URL (rascunho or modelo)\n */\nexport function getTypeFromUrl(type: RecommendedClassDraftType): string {\n switch (type) {\n case RecommendedClassDraftType.RASCUNHO:\n return 'rascunho';\n case RecommendedClassDraftType.MODELO:\n return 'modelo';\n default:\n return 'rascunho';\n }\n}\n\n/**\n * Convert string from URL to RecommendedClassDraftType\n *\n * @param type - String from URL (rascunho or modelo)\n * @returns RecommendedClassDraftType enum value\n */\nexport function getTypeFromUrlString(\n type: string | undefined\n): RecommendedClassDraftType {\n switch (type) {\n case 'rascunho':\n return RecommendedClassDraftType.RASCUNHO;\n case 'modelo':\n return RecommendedClassDraftType.MODELO;\n default:\n return RecommendedClassDraftType.RASCUNHO;\n }\n}\n\n/**\n * Convert Lesson to PreviewLesson format\n * Maps API fields (urlVideo, urlPodCast, etc.) to component-expected fields (videoSrc, podcastSrc, etc.)\n *\n * @param lesson - Lesson object to convert\n * @returns PreviewLesson object with converted data\n */\nexport function convertLessonToPreview(lesson: Lesson): PreviewLesson {\n return {\n ...lesson,\n title:\n lesson.content?.name ||\n lesson.videoTitle ||\n lesson.content?.bnccCode ||\n lesson.title,\n position: undefined,\n // Map API fields to component-expected fields for LessonPreview\n videoSrc: lesson.urlVideo,\n videoPoster: lesson.urlInitialFrame || lesson.urlCover,\n videoSubtitles: lesson.urlSubtitle,\n podcastSrc: lesson.urlPodCast,\n podcastTitle: lesson.podCastTitle,\n };\n}\n","import { SkeletonText, Skeleton, SkeletonCard } from '../../..';\n\n/**\n * Loading skeleton component for RecommendedLessonCreate page\n *\n * @returns Skeleton JSX element\n */\nexport const RecommendedLessonCreateSkeleton = () => {\n return (\n <div\n data-testid=\"create-recommended-class-page\"\n className=\"flex flex-col w-full h-screen overflow-hidden p-5 bg-background\"\n >\n {/* Header Section Skeleton */}\n <div className=\"w-full h-[80px] flex flex-row items-center justify-between px-6 gap-3 flex-shrink-0\">\n <section className=\"text-text-950\">\n <Skeleton variant=\"rectangular\" width={32} height={32} />\n </section>\n\n <section className=\"flex flex-col gap-0.5 w-full\">\n <div className=\"flex flex-row items-center justify-between w-full text-text-950\">\n <SkeletonText width={220} height={24} />\n <div className=\"flex flex-row gap-4 items-center\">\n <SkeletonText width={150} height={16} />\n <Skeleton variant=\"rounded\" width={120} height={32} />\n <Skeleton variant=\"rounded\" width={120} height={32} />\n </div>\n </div>\n <SkeletonText width={450} height={16} />\n </section>\n </div>\n\n {/* Main Content with 3 columns - Skeleton */}\n <div className=\"flex flex-row w-full flex-1 overflow-hidden gap-5 min-h-0\">\n {/* First Column - Filters Skeleton */}\n <div className=\"flex flex-col gap-3 overflow-hidden h-full min-h-0 max-h-full relative w-[400px] flex-shrink-0 p-4 bg-background\">\n <SkeletonText width={150} height={20} />\n <div className=\"flex flex-col gap-4 mt-4\">\n <div className=\"flex flex-col gap-3\">\n <SkeletonText width={80} height={16} />\n <div className=\"grid grid-cols-3 gap-3\">\n {[1, 2].map((i) => (\n <Skeleton\n variant=\"rounded\"\n key={i}\n width=\"100%\"\n height={60}\n />\n ))}\n </div>\n </div>\n <div className=\"flex flex-col gap-3\">\n <SkeletonText width={120} height={16} />\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3].map((i) => (\n <Skeleton\n variant=\"rounded\"\n key={i}\n width=\"100%\"\n height={40}\n />\n ))}\n </div>\n </div>\n </div>\n <div className=\"mt-auto\">\n <Skeleton variant=\"rounded\" width=\"100%\" height={40} />\n </div>\n </div>\n\n {/* Second Column - Lesson List Skeleton */}\n <div className=\"flex-1 min-w-0 overflow-hidden h-full p-4\">\n <div className=\"flex flex-col gap-4 h-full\">\n <div className=\"flex flex-row items-center justify-between\">\n <div className=\"flex flex-row items-center gap-2\">\n <Skeleton variant=\"rectangular\" width={24} height={24} />\n <SkeletonText width={150} height={20} />\n </div>\n </div>\n <SkeletonText width={100} height={16} />\n <div className=\"flex flex-col gap-3 flex-1 overflow-auto\">\n {[1, 2, 3, 4, 5].map((i) => (\n <SkeletonCard key={i} lines={2} />\n ))}\n </div>\n </div>\n </div>\n\n {/* Third Column - Lesson Preview Skeleton */}\n <div className=\"w-[400px] flex-shrink-0 overflow-hidden h-full min-h-0 p-4\">\n <div className=\"flex flex-col gap-4 h-full\">\n <div className=\"flex flex-col gap-2\">\n <SkeletonText width={180} height={20} />\n <SkeletonText width={130} height={16} />\n </div>\n <div className=\"flex flex-col gap-3 flex-1 overflow-auto\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"p-4 border border-border-200 rounded-lg\"\n >\n <SkeletonText lines={1} spacing=\"small\" />\n <div className=\"mt-3 flex gap-2\">\n <Skeleton variant=\"rounded\" width={80} height={24} />\n <Skeleton variant=\"rounded\" width={80} height={24} />\n </div>\n </div>\n ))}\n </div>\n <Skeleton variant=\"rounded\" width=\"100%\" height={36} />\n </div>\n </div>\n </div>\n </div>\n );\n};\n","import { Button, Text } from '../../..';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport type { RecommendedLessonData } from '../RecommendedLessonCreate.types';\nimport { RecommendedClassDraftType } from '../RecommendedLessonCreate.types';\nimport { getGoalDraftTypeLabel } from '../RecommendedLessonCreate.utils';\nimport { formatTime } from '../../../utils/categoryDataUtils';\nimport { useMobile } from '../../../hooks/useMobile';\n\n/**\n * Header component for RecommendedLessonCreate page\n * Displays title, save status, and action buttons\n *\n * @param props - Component props\n * @returns Header JSX element\n */\nexport const RecommendedLessonCreateHeader = ({\n recommendedLesson,\n draftType,\n lastSavedAt,\n isSaving,\n lessonsCount,\n onSaveModel,\n onSendLesson,\n onBack,\n}: {\n recommendedLesson?: RecommendedLessonData;\n draftType: RecommendedClassDraftType;\n lastSavedAt: Date | null;\n isSaving: boolean;\n lessonsCount: number;\n onSaveModel: () => void;\n onSendLesson: () => void;\n onBack?: () => void;\n}) => {\n const typeLabel = getGoalDraftTypeLabel(draftType);\n const { isMobile } = useMobile();\n\n const titleText = recommendedLesson\n ? 'Editar aula recomendada'\n : 'Criar aula recomendada';\n\n const getStatusText = () => {\n if (isSaving) {\n return 'Salvando...';\n }\n if (lastSavedAt) {\n return `${typeLabel} salvo às ${formatTime(lastSavedAt)}`;\n }\n return 'Nenhum rascunho salvo';\n };\n const statusText = getStatusText();\n\n const subtitleText =\n 'Crie uma aula recomendada customizada adicionando aulas do banco de aulas.';\n\n const backButton = (\n <Button\n onClick={onBack}\n aria-label=\"Voltar\"\n type=\"button\"\n variant=\"link\"\n data-testid=\"back-button\"\n >\n <CaretLeftIcon size={isMobile ? 24 : 32} />\n </Button>\n );\n\n const actionButtons = (\n <>\n <Button\n size=\"small\"\n variant=\"outline\"\n onClick={onSaveModel}\n disabled={!recommendedLesson || isSaving}\n >\n Salvar modelo\n </Button>\n <Button\n size=\"small\"\n iconLeft={<PaperPlaneTiltIcon />}\n onClick={onSendLesson}\n disabled={lessonsCount === 0}\n >\n Enviar aula\n </Button>\n </>\n );\n\n if (isMobile) {\n return (\n <div className=\"w-full flex flex-col gap-3 px-4 py-4 flex-shrink-0\">\n <div className=\"flex flex-row items-center gap-2\">\n {backButton}\n <Text size=\"lg\" weight=\"bold\">\n {titleText}\n </Text>\n </div>\n <Text size=\"sm\">{subtitleText}</Text>\n <div className=\"flex flex-row items-center justify-between gap-2 flex-wrap\">\n <Text size=\"sm\">{statusText}</Text>\n <div className=\"flex flex-row gap-2\">{actionButtons}</div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"w-full h-[80px] flex flex-row items-center justify-between px-6 gap-3 flex-shrink-0\">\n {backButton}\n\n <section className=\"flex flex-col gap-0.5 w-full\">\n <div className=\"flex flex-row items-center justify-between w-full text-text-950\">\n <Text size=\"lg\" weight=\"bold\">\n {titleText}\n </Text>\n\n <div className=\"flex flex-row gap-4 items-center\">\n <Text size=\"sm\">{statusText}</Text>\n {actionButtons}\n </div>\n </div>\n\n <Text size=\"sm\">{subtitleText}</Text>\n </section>\n </div>\n );\n};\n","import { useMemo, useRef, useState } from 'react';\nimport { CaretDownIcon } from '@phosphor-icons/react/dist/csr/CaretDown';\nimport { CheckIcon } from '@phosphor-icons/react/dist/csr/Check';\nimport DropdownMenu, {\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '../DropdownMenu/DropdownMenu';\nimport Search from '../Search/Search';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport type { ColumnFilterConfig } from './useColumnFilters';\n\nexport interface ColumnFilterMenuProps {\n /** Plain-text column name, used for the trigger's accessible label. */\n columnLabel: string;\n config: ColumnFilterConfig;\n /** Currently selected values (empty = \"all\"). */\n value: string[];\n onChange: (values: string[]) => void;\n}\n\n/**\n * The filter dropdown that lives inside a column header.\n *\n * Renders the `DropdownMenu` primitives directly (rather than wrapping them in\n * another component) because the store is injected by walking the child\n * element tree — an indirection would hide the items from it.\n */\nconst ColumnFilterMenu = ({\n columnLabel,\n config,\n value,\n onChange,\n}: ColumnFilterMenuProps) => {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const {\n options,\n multiple = false,\n allLabel = 'Todos',\n searchable = false,\n searchPlaceholder = 'Buscar...',\n onSearch,\n loading = false,\n } = config;\n\n const [query, setQuery] = useState('');\n\n const hasFilter = value.length > 0;\n\n // With `onSearch` the consumer owns the list (it re-fetches per query), so the\n // options arrive already filtered. Without it, filter what we were given.\n const visibleOptions = useMemo(() => {\n if (!searchable || onSearch || !query) return options;\n\n const term = query.toLowerCase();\n return options.filter((option) =>\n String(option.searchText ?? option.value)\n .toLowerCase()\n .includes(term)\n );\n }, [searchable, onSearch, query, options]);\n\n const toggle = (optionValue: string) => {\n if (!multiple) {\n // Re-picking the active value clears it.\n onChange(value[0] === optionValue ? [] : [optionValue]);\n return;\n }\n\n onChange(\n value.includes(optionValue)\n ? value.filter((v) => v !== optionValue)\n : [...value, optionValue]\n );\n };\n\n return (\n <span className=\"inline-flex\">\n <DropdownMenu>\n <DropdownMenuTrigger\n ref={triggerRef}\n aria-label={`Filtrar por ${columnLabel}`}\n className={cn(\n 'flex items-center rounded-sm cursor-pointer hover:opacity-70',\n hasFilter ? 'text-primary-600' : 'text-text-600'\n )}\n >\n <CaretDownIcon\n size={16}\n weight={hasFilter ? 'fill' : 'regular'}\n aria-hidden=\"true\"\n />\n </DropdownMenuTrigger>\n\n {/*\n * Portal: the table body sits inside `overflow-x-auto`, which would clip\n * an absolutely positioned menu.\n *\n * The click guard is still required despite the portal — React bubbles\n * events through the component tree, not the DOM tree, so a click on an\n * item would otherwise reach whatever wraps this header. (The trigger\n * guards its own click; this covers the items.)\n */}\n <DropdownMenuContent\n portal\n triggerRef={triggerRef}\n align=\"start\"\n className=\"max-h-80 overflow-y-auto\"\n onClick={(event) => event.stopPropagation()}\n >\n {searchable && (\n <div className=\"p-2\">\n {/*\n * Two callbacks on purpose: `onChange` fires on every keystroke and\n * drives the local filtering (debouncing that would make typing feel\n * broken), while `onSearch` is debounced and is what hits the server.\n */}\n <Search\n options={[]}\n value={query}\n placeholder={searchPlaceholder}\n debounceMs={300}\n onChange={(event) => setQuery(event.target.value)}\n onSearch={onSearch}\n onClear={() => {\n setQuery('');\n onSearch?.('');\n }}\n aria-label={`Buscar ${columnLabel}`}\n />\n </div>\n )}\n\n <DropdownMenuItem\n onClick={() => onChange([])}\n className={cn(!hasFilter && 'font-bold')}\n >\n {allLabel}\n </DropdownMenuItem>\n\n {loading && (\n <div className=\"px-3 py-2\">\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando...\n </Text>\n </div>\n )}\n\n {!loading && visibleOptions.length === 0 && (\n <div className=\"px-3 py-2\">\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhum resultado encontrado\n </Text>\n </div>\n )}\n\n {!loading &&\n visibleOptions.map((option) => {\n const selected = value.includes(option.value);\n\n return (\n <DropdownMenuItem\n key={option.value}\n preventClose={multiple}\n onClick={() => toggle(option.value)}\n iconRight={\n selected ? (\n <CheckIcon size={16} weight=\"bold\" aria-hidden=\"true\" />\n ) : undefined\n }\n className={cn(selected && 'font-bold')}\n >\n {option.label}\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </span>\n );\n};\n\nColumnFilterMenu.displayName = 'ColumnFilterMenu';\n\nexport default ColumnFilterMenu;\n","import {\n ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nexport interface ColumnFilterOption {\n value: string;\n label: ReactNode;\n /**\n * Text a local search matches against. Needed because `label` is a ReactNode\n * (a badge, an icon + text) and `value` is often an opaque id — neither is\n * searchable. Ignored when the config supplies `onSearch`, since the server\n * does the matching then.\n */\n searchText?: string;\n}\n\nexport interface ColumnFilterConfig {\n /** Choices shown in the header menu. */\n options: ColumnFilterOption[];\n /** Allow more than one value at a time (default: false). */\n multiple?: boolean;\n /** Label of the \"clear filter\" entry at the top of the menu (default: \"Todos\"). */\n allLabel?: string;\n /**\n * Key this filter is emitted under in the table params — i.e. the field name\n * the API expects. Defaults to the column's `key`.\n */\n paramKey?: string;\n /**\n * Show a search box at the top of the menu.\n *\n * Needed whenever the option list is too long to scan — a school manager sees\n * over a thousand schools, and a plain list of those is unusable.\n */\n searchable?: boolean;\n /** Placeholder of the search box (default: \"Buscar...\"). */\n searchPlaceholder?: string;\n /**\n * Called (debounced) as the user types, for options fetched from a server.\n *\n * The consumer keeps owning `options`: it refreshes them as results arrive.\n * When omitted, a `searchable` menu filters the given `options` in place.\n */\n onSearch?: (query: string) => void;\n /** Show a loading state in the menu while the search is in flight. */\n loading?: boolean;\n}\n\n/** Minimum shape `useColumnFilters` needs out of a column definition. */\nexport interface FilterableColumn {\n key: string;\n filter?: ColumnFilterConfig;\n}\n\nexport interface UseColumnFiltersOptions {\n /** Persist the active filters in the query string. */\n syncWithUrl?: boolean;\n /** Prefix for the URL keys, so two tables on one page don't collide. */\n urlKeyPrefix?: string;\n /** Called on every change (the TableProvider uses it to go back to page 1). */\n onFiltersChange?: () => void;\n}\n\nexport interface UseColumnFiltersReturn {\n /** Active values per paramKey. Columns with no selection are absent. */\n columnFilters: Record<string, string[]>;\n /**\n * The same thing, shaped for the request: single-value filters unwrapped,\n * multi-value ones left as arrays. Stable identity — safe to spread into a\n * memo that feeds `onParamsChange`.\n */\n columnFilterParams: Record<string, string | string[]>;\n setColumnFilter: (paramKey: string, values: string[]) => void;\n}\n\nconst urlKeyOf = (paramKey: string, prefix?: string) =>\n prefix ? `${prefix}_colfilter_${paramKey}` : `colfilter_${paramKey}`;\n\n/**\n * State for the per-column filter menus rendered in the table header.\n *\n * Single-phase on purpose: picking a value applies it immediately. (The modal\n * filters in `useTableFilter` are two-phase — draft, then \"Apply\" — which is\n * the wrong shape for a dropdown that closes on selection.)\n */\nexport function useColumnFilters(\n columns: FilterableColumn[],\n options: UseColumnFiltersOptions = {}\n): UseColumnFiltersReturn {\n const { syncWithUrl = false, urlKeyPrefix, onFiltersChange } = options;\n\n const onFiltersChangeRef = useRef(onFiltersChange);\n onFiltersChangeRef.current = onFiltersChange;\n\n // Callers rarely memoize their column array, so keying off its identity would\n // re-run the effects below on every render. Key off what actually matters\n // instead: which columns filter, under which param, single or multiple.\n const signature = columns\n .filter((column) => column.filter)\n .map(\n (column) =>\n `${column.filter?.paramKey ?? column.key}:${column.filter?.multiple ? 'm' : 's'}`\n )\n .join('|');\n\n const filterSpecs = useMemo(\n () =>\n signature\n ? signature.split('|').map((entry) => {\n const [paramKey, mode] = entry.split(':');\n return { paramKey, multiple: mode === 'm' };\n })\n : [],\n [signature]\n );\n\n const readFromUrl = useCallback((): Record<string, string[]> => {\n if (!syncWithUrl || globalThis.window === undefined) return {};\n\n const params = new URLSearchParams(globalThis.location.search);\n const result: Record<string, string[]> = {};\n\n for (const { paramKey, multiple } of filterSpecs) {\n const raw = params.get(urlKeyOf(paramKey, urlKeyPrefix));\n const values = raw?.split(',').filter(Boolean) ?? [];\n if (values.length === 0) continue;\n\n // A hand-edited URL can carry several values for a single-select column.\n // The request would still go out with one (see columnFilterParams), but the\n // menu reads this state directly and would tick every one of them.\n result[paramKey] = multiple ? values : [values[0]];\n }\n\n return result;\n }, [syncWithUrl, urlKeyPrefix, filterSpecs]);\n\n const [columnFilters, setColumnFilters] = useState<Record<string, string[]>>(\n () => readFromUrl()\n );\n\n useEffect(() => {\n if (!syncWithUrl || globalThis.window === undefined) return;\n\n const url = new URL(globalThis.location.href);\n\n for (const { paramKey } of filterSpecs) {\n const urlKey = urlKeyOf(paramKey, urlKeyPrefix);\n const values = columnFilters[paramKey];\n\n if (values?.length) {\n url.searchParams.set(urlKey, values.join(','));\n } else {\n url.searchParams.delete(urlKey);\n }\n }\n\n // Only touch history when something actually changed: mounting with no\n // active filter must leave the URL — and anyone else's params — alone.\n const next = url.toString();\n if (next !== globalThis.location.href) {\n globalThis.history.replaceState({}, '', next);\n }\n }, [columnFilters, filterSpecs, syncWithUrl, urlKeyPrefix]);\n\n useEffect(() => {\n if (!syncWithUrl || globalThis.window === undefined) return;\n\n const handlePopState = () => setColumnFilters(readFromUrl());\n\n globalThis.addEventListener('popstate', handlePopState);\n return () => globalThis.removeEventListener('popstate', handlePopState);\n }, [syncWithUrl, readFromUrl]);\n\n const setColumnFilter = useCallback((paramKey: string, values: string[]) => {\n setColumnFilters((prev) => {\n const next = { ...prev };\n if (values.length > 0) {\n next[paramKey] = values;\n } else {\n // An empty selection means \"all\": the key must vanish from the request\n // rather than go out as an empty string.\n delete next[paramKey];\n }\n return next;\n });\n\n onFiltersChangeRef.current?.();\n }, []);\n\n const columnFilterParams = useMemo(() => {\n const params: Record<string, string | string[]> = {};\n\n for (const { paramKey, multiple } of filterSpecs) {\n const values = columnFilters[paramKey];\n if (!values?.length) continue;\n params[paramKey] = multiple ? values : values[0];\n }\n\n return params;\n }, [columnFilters, filterSpecs]);\n\n return { columnFilters, columnFilterParams, setColumnFilter };\n}\n","/**\n * Filter category keys enum\n */\nexport enum FILTER_CATEGORY {\n SCHOOL = 'school',\n SCHOOL_YEAR = 'schoolYear',\n CLASS = 'class',\n SUBJECT = 'subject',\n STATUS = 'status',\n}\n\n/**\n * Filter group keys enum\n */\nexport enum FILTER_GROUP {\n ACADEMIC = 'academic',\n CONTENT = 'content',\n STATUS = 'status',\n}\n","import Modal from '../Modal/Modal';\nimport { CheckboxGroup } from '../CheckBoxGroup/CheckBoxGroup';\nimport Button from '../Button/Button';\nimport type { FilterConfig } from './useTableFilter';\nimport { FILTER_GROUP } from '../../enums/FilterEnums';\n\nexport type FilterModalProps = {\n /**\n * Controls modal visibility\n */\n isOpen: boolean;\n\n /**\n * Callback when modal should close\n */\n onClose: () => void;\n\n /**\n * Filter configurations with categories\n */\n filterConfigs: FilterConfig[];\n\n /**\n * Callback when filters change (temporary, before applying)\n */\n onFiltersChange: (configs: FilterConfig[]) => void;\n\n /**\n * Callback when \"Aplicar\" button is clicked\n */\n onApply: () => void;\n\n /**\n * Callback when \"Limpar filtros\" button is clicked\n */\n onClear: () => void;\n\n /**\n * Modal title\n * @default \"Filtros\"\n */\n title?: string;\n\n /**\n * Modal size\n * @default \"md\"\n */\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n /**\n * Apply button label\n * @default \"Aplicar\"\n */\n applyLabel?: string;\n\n /**\n * Clear button label\n * @default \"Limpar filtros\"\n */\n clearLabel?: string;\n};\n\n/**\n * FilterModal component - A modal for table filtering with CheckboxGroup\n *\n * Integrates Modal, CheckboxGroup, and Button components to create a\n * complete filtering interface. Works with useTableFilter hook for URL synchronization.\n *\n * @example\n * ```tsx\n * const { filterConfigs, updateFilters, applyFilters, clearFilters } = useTableFilter(\n * initialConfigs,\n * { syncWithUrl: true }\n * );\n *\n * <FilterModal\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * filterConfigs={filterConfigs}\n * onFiltersChange={updateFilters}\n * onApply={() => {\n * applyFilters();\n * setIsOpen(false);\n * }}\n * onClear={clearFilters}\n * />\n * ```\n */\nexport const FilterModal = ({\n isOpen,\n onClose,\n filterConfigs,\n onFiltersChange,\n onApply,\n onClear,\n title = 'Filtros',\n size = 'md',\n applyLabel = 'Aplicar',\n clearLabel = 'Limpar filtros',\n}: FilterModalProps) => {\n const handleCategoryChange = (\n configIndex: number,\n updatedCategories: (typeof filterConfigs)[0]['categories']\n ) => {\n const newConfigs = [...filterConfigs];\n newConfigs[configIndex] = {\n ...newConfigs[configIndex],\n categories: updatedCategories,\n };\n onFiltersChange(newConfigs);\n };\n\n const handleApply = () => {\n onApply();\n onClose();\n };\n\n const handleClear = () => {\n onClear();\n };\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size={size}\n footer={\n <div className=\"flex gap-3 justify-end w-full\">\n <Button variant=\"outline\" onClick={handleClear}>\n {clearLabel}\n </Button>\n <Button onClick={handleApply}>{applyLabel}</Button>\n </div>\n }\n >\n <div className=\"flex flex-col gap-6\">\n {filterConfigs.map((config, index) => (\n <div key={config.key} className=\"flex flex-col gap-4\">\n {/* Section Header */}\n <div className=\"flex items-center gap-2 text-text-400 text-sm font-medium uppercase\">\n {config.key === FILTER_GROUP.ACADEMIC && (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"text-text-400\"\n >\n <path\n d=\"M8 2L2 5.33333L8 8.66667L14 5.33333L8 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 10.6667L8 14L14 10.6667\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 8L8 11.3333L14 8\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n {config.key === FILTER_GROUP.CONTENT && (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className=\"text-text-400\"\n >\n <path\n d=\"M3.33333 2H12.6667C13.403 2 14 2.59695 14 3.33333V12.6667C14 13.403 13.403 14 12.6667 14H3.33333C2.59695 14 2 13.403 2 12.6667V3.33333C2 2.59695 2.59695 2 3.33333 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M2 6H14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M6 2V14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n <span>{config.label}</span>\n </div>\n\n {/* CheckboxGroup */}\n <CheckboxGroup\n categories={config.categories}\n onCategoriesChange={(updatedCategories) =>\n handleCategoryChange(index, updatedCategories)\n }\n />\n </div>\n ))}\n </div>\n </Modal>\n );\n};\n","import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport Modal from '../Modal/Modal';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport {\n CheckboxGroup,\n type CategoryConfig,\n} from '../CheckBoxGroup/CheckBoxGroup';\nimport { GraduationCapIcon } from '@phosphor-icons/react';\nimport { useUserAccessData, useStudentsFilter } from './hooks';\nimport { StudentsFilterSection } from './StudentsFilterSection';\nimport type { SimulatedFiltersModalProps, SimulatedFilters } from './types';\n\n/**\n * Extracts selected IDs from categories\n */\nfunction extractFiltersFromCategories(\n categories: CategoryConfig[]\n): Omit<SimulatedFilters, 'studentsIds'> {\n const filters = {\n schoolIds: [] as string[],\n schoolYearIds: [] as string[],\n classIds: [] as string[],\n };\n\n for (const category of categories) {\n const selectedIds = category.selectedIds || [];\n switch (category.key) {\n case 'school':\n filters.schoolIds = selectedIds;\n break;\n case 'schoolYear':\n filters.schoolYearIds = selectedIds;\n break;\n case 'class':\n filters.classIds = selectedIds;\n break;\n }\n }\n\n return filters;\n}\n\n/**\n * SimulatedFiltersModal - Filters modal exclusive for Simulated exams\n *\n * Uses data from user access endpoint and CheckboxGroup component with cascading.\n * Filters: School → Grade → Class → Students\n *\n * Receives the api client and calls the endpoints directly:\n * - GET /auth/me - for schools, schoolYears, classes\n * - POST /students/filters - for students list\n *\n * @example\n * ```tsx\n * import api from '@/services/apiService';\n *\n * <SimulatedFiltersModal\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * onApply={(filters) => handleApply(filters)}\n * initialFilters={currentFilters}\n * api={api}\n * />\n * ```\n */\nexport function SimulatedFiltersModal({\n isOpen,\n onClose,\n onApply,\n initialFilters,\n api,\n}: Readonly<SimulatedFiltersModalProps>) {\n const { schools, schoolYears, classes, isLoading } = useUserAccessData(api);\n\n const {\n groupedStudents,\n isLoading: studentsLoading,\n fetchStudents,\n clearStudents,\n } = useStudentsFilter(api);\n\n // State for CheckboxGroup categories\n const [categories, setCategories] = useState<CategoryConfig[]>([]);\n\n // State for selected students\n const [selectedStudentIds, setSelectedStudentIds] = useState<string[]>(\n initialFilters?.studentsIds || []\n );\n const [studentSearchQuery, setStudentSearchQuery] = useState('');\n\n // Track if already initialized to avoid re-initialization\n const hasInitialized = useRef(false);\n // Track first filter run to preserve initial student selection\n const isFirstFilterRun = useRef(true);\n\n // Reset initialization flag when modal closes\n useEffect(() => {\n if (!isOpen) {\n hasInitialized.current = false;\n isFirstFilterRun.current = true;\n }\n }, [isOpen]);\n\n // Initialize categories when data loads and modal opens\n // If there's only 1 school, auto-select it\n useEffect(() => {\n if (\n isOpen &&\n !hasInitialized.current &&\n (schools.length > 0 || schoolYears.length > 0 || classes.length > 0)\n ) {\n hasInitialized.current = true;\n\n // Auto-select school if there's only one\n const getAutoSelectedSchoolIds = (): string[] => {\n const hasInitialSchools =\n initialFilters?.schoolIds && initialFilters.schoolIds.length > 0;\n\n if (hasInitialSchools) {\n return initialFilters?.schoolIds || [];\n }\n\n if (schools.length === 1) {\n return [schools[0].id];\n }\n\n return [];\n };\n\n const autoSelectedSchoolIds = getAutoSelectedSchoolIds();\n\n setCategories([\n {\n key: 'school',\n label: 'Escola',\n itens: schools,\n selectedIds: autoSelectedSchoolIds,\n },\n {\n key: 'schoolYear',\n label: 'Série',\n dependsOn: ['school'],\n filteredBy: [{ key: 'school', internalField: 'schoolId' }],\n itens: schoolYears,\n selectedIds: initialFilters?.schoolYearIds || [],\n },\n {\n key: 'class',\n label: 'Turma',\n dependsOn: ['school', 'schoolYear'],\n filteredBy: [\n { key: 'school', internalField: 'schoolId' },\n { key: 'schoolYear', internalField: 'schoolYearId' },\n ],\n itens: classes,\n selectedIds: initialFilters?.classIds || [],\n },\n ]);\n\n // Reset selected students to initial filters\n setSelectedStudentIds(initialFilters?.studentsIds || []);\n setStudentSearchQuery('');\n }\n }, [isOpen, schools, schoolYears, classes, initialFilters]);\n\n // Extract current filters from categories\n const currentFilters = useMemo(\n () => extractFiltersFromCategories(categories),\n [categories]\n );\n\n // Check if there's at least one class selected to load students\n // Requires class selection to avoid backend inconsistency with empty classIds\n const hasClassSelected = useMemo(\n () => currentFilters.classIds.length > 0,\n [currentFilters]\n );\n\n // Fetch students when school/grade/class filters change\n useEffect(() => {\n if (hasClassSelected) {\n fetchStudents(currentFilters);\n // Clear student selection only on subsequent filter changes, not on initial load\n if (!isFirstFilterRun.current) {\n setSelectedStudentIds([]);\n }\n } else {\n clearStudents();\n if (!isFirstFilterRun.current) {\n setSelectedStudentIds([]);\n }\n }\n isFirstFilterRun.current = false;\n }, [\n currentFilters.schoolIds.join(','),\n currentFilters.schoolYearIds.join(','),\n currentFilters.classIds.join(','),\n ]);\n\n const handleCategoriesChange = useCallback(\n (updatedCategories: CategoryConfig[]) => {\n setCategories(updatedCategories);\n },\n []\n );\n\n const handleApply = useCallback(() => {\n const filters: SimulatedFilters = {\n ...currentFilters,\n studentsIds: selectedStudentIds,\n };\n onApply(filters);\n onClose();\n }, [currentFilters, selectedStudentIds, onApply, onClose]);\n\n const handleClear = useCallback(() => {\n setCategories((prev) => prev.map((cat) => ({ ...cat, selectedIds: [] })));\n setSelectedStudentIds([]);\n setStudentSearchQuery('');\n clearStudents();\n }, [clearStudents]);\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Filtros\"\n size=\"md\"\n footer={\n <div className=\"flex gap-3 justify-end w-full\">\n <Button variant=\"outline\" onClick={handleClear}>\n Limpar filtros\n </Button>\n <Button onClick={handleApply}>Aplicar</Button>\n </div>\n }\n >\n <div className=\"flex flex-col gap-6\">\n {/* Section Header - Academic Data */}\n <div className=\"flex items-center gap-2 text-text-400 text-sm font-medium uppercase\">\n <GraduationCapIcon size={16} className=\"text-text-400\" />\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-400 uppercase\">\n DADOS ACADÊMICOS\n </Text>\n </div>\n\n {/* Loading state */}\n {isLoading && (\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-text-500\">\n Carregando filtros...\n </Text>\n </div>\n )}\n\n {/* CheckboxGroup with cascading */}\n {!isLoading && categories.length > 0 && (\n <CheckboxGroup\n categories={categories}\n onCategoriesChange={handleCategoriesChange}\n />\n )}\n\n {/* Divider */}\n <div className=\"border-t border-border-200\" />\n\n {/* Students Section */}\n <StudentsFilterSection\n groupedStudents={groupedStudents}\n selectedIds={selectedStudentIds}\n searchQuery={studentSearchQuery}\n isLoading={studentsLoading}\n hasFilters={hasClassSelected}\n onSearchChange={setStudentSearchQuery}\n onSelectionChange={setSelectedStudentIds}\n />\n </div>\n </Modal>\n );\n}\n\nexport default SimulatedFiltersModal;\n","import { useState, useCallback, useEffect, useRef } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n UseUserAccessDataReturn,\n UseUserAccessDataState,\n UseStudentsFilterReturn,\n UseStudentsFilterState,\n StudentsFilterParams,\n StudentFilterItem,\n StudentGroup,\n UserAccessDataApiResponse,\n StudentsFilterApiResponse,\n} from './types';\n\n// ============================================================================\n// UTILITY FUNCTIONS\n// ============================================================================\n\n/**\n * Groups students by school/year/class\n */\nfunction groupStudents(students: StudentFilterItem[]): StudentGroup[] {\n const groups = new Map<string, StudentGroup>();\n\n for (const student of students) {\n const key = `${student.schoolId}-${student.schoolYearId}-${student.classId}`;\n const label = `${student.schoolName} > ${student.schoolYearName} > ${student.className}`;\n\n if (!groups.has(key)) {\n groups.set(key, { key, label, students: [] });\n }\n groups.get(key)!.students.push(student);\n }\n\n // Sort groups by label and students by name\n const sortedGroups = Array.from(groups.values()).sort((a, b) =>\n a.label.localeCompare(b.label, 'pt-BR')\n );\n\n for (const group of sortedGroups) {\n group.students.sort((a, b) => a.name.localeCompare(b.name, 'pt-BR'));\n }\n\n return sortedGroups;\n}\n\n// ============================================================================\n// useUserAccessData HOOK\n// ============================================================================\n\n/**\n * Hook to fetch user access data (schools, schoolYears, classes)\n *\n * Receives the api client and calls /auth/me endpoint directly.\n *\n * @example\n * ```tsx\n * const { schools, schoolYears, classes, isLoading, error } = useUserAccessData(api);\n * ```\n */\nexport function useUserAccessData(api: BaseApiClient): UseUserAccessDataReturn {\n const [state, setState] = useState<UseUserAccessDataState>({\n isLoading: false,\n error: null,\n schools: [],\n schoolYears: [],\n classes: [],\n });\n\n const hasFetched = useRef(false);\n\n const fetchData = useCallback(async () => {\n setState((prev) => ({ ...prev, isLoading: true, error: null }));\n\n try {\n const response = await api.get<UserAccessDataApiResponse>('/auth/me');\n const data = response.data.data;\n\n setState({\n isLoading: false,\n error: null,\n schools: data.schools.map((s) => ({\n id: s.id,\n name: s.name,\n })),\n schoolYears: data.schoolYears.map((sy) => ({\n id: sy.id,\n name: sy.name,\n schoolId: sy.schoolId,\n })),\n classes: data.classes.map((c) => ({\n id: c.id,\n name: c.name,\n schoolId: c.schoolId,\n schoolYearId: c.schoolYearId,\n })),\n });\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Falha ao carregar dados';\n setState((prev) => ({\n ...prev,\n isLoading: false,\n error: errorMessage,\n }));\n }\n }, [api]);\n\n // Fetch on mount\n useEffect(() => {\n if (!hasFetched.current) {\n hasFetched.current = true;\n fetchData();\n }\n }, [fetchData]);\n\n return {\n ...state,\n refetch: fetchData,\n };\n}\n\n// ============================================================================\n// useStudentsFilter HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage students for filtering\n *\n * Receives the api client and calls /students/filters endpoint directly.\n *\n * @example\n * ```tsx\n * const { groupedStudents, isLoading, fetchStudents, clearStudents } = useStudentsFilter(api);\n *\n * // Fetch students when filters change\n * useEffect(() => {\n * if (schoolIds.length > 0) {\n * fetchStudents({ schoolIds, schoolYearIds, classIds });\n * }\n * }, [schoolIds, schoolYearIds, classIds]);\n * ```\n */\nexport function useStudentsFilter(api: BaseApiClient): UseStudentsFilterReturn {\n const [state, setState] = useState<UseStudentsFilterState>({\n isLoading: false,\n error: null,\n students: [],\n groupedStudents: [],\n });\n\n const fetchStudents = useCallback(\n async (filters: StudentsFilterParams) => {\n // Don't fetch if there are no filters\n const hasFilters =\n (filters.schoolIds && filters.schoolIds.length > 0) ||\n (filters.schoolYearIds && filters.schoolYearIds.length > 0) ||\n (filters.classIds && filters.classIds.length > 0);\n\n if (!hasFilters) {\n setState({\n isLoading: false,\n error: null,\n students: [],\n groupedStudents: [],\n });\n return;\n }\n\n setState((prev) => ({ ...prev, isLoading: true, error: null }));\n\n try {\n const response = await api.post<StudentsFilterApiResponse>(\n '/students/filters',\n {\n schoolIds: filters.schoolIds || [],\n schoolYearIds: filters.schoolYearIds || [],\n classIds: filters.classIds || [],\n }\n );\n\n const rawStudents = response.data.data.students;\n\n // Transform and filter students with complete data\n const students: StudentFilterItem[] = rawStudents\n .filter((s) => s.school && s.schoolYear && s.class)\n .map((s) => ({\n id: s.userInstitutionId,\n name: s.name,\n schoolId: s.school!.id,\n schoolYearId: s.schoolYear!.id,\n classId: s.class!.id,\n schoolName: s.school!.name,\n schoolYearName: s.schoolYear!.name,\n className: s.class!.name,\n }));\n\n const groupedStudents = groupStudents(students);\n\n setState({\n isLoading: false,\n error: null,\n students,\n groupedStudents,\n });\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Falha ao carregar estudantes';\n setState((prev) => ({\n ...prev,\n isLoading: false,\n error: errorMessage,\n }));\n }\n },\n [api]\n );\n\n const clearStudents = useCallback(() => {\n setState({\n isLoading: false,\n error: null,\n students: [],\n groupedStudents: [],\n });\n }, []);\n\n return {\n ...state,\n fetchStudents,\n clearStudents,\n };\n}\n","import { useMemo, useCallback } from 'react';\nimport Text from '../Text/Text';\nimport CheckBox from '../CheckBox/CheckBox';\nimport type { StudentGroup, StudentsFilterSectionProps } from './types';\nimport Input from '../Input/Input';\nimport { UsersIcon } from '@phosphor-icons/react/dist/csr/Users';\nimport { MagnifyingGlassIcon } from '@phosphor-icons/react/dist/csr/MagnifyingGlass';\n\n/**\n * Students filter section with search and grouping by school/year/class\n *\n * @example\n * ```tsx\n * <StudentsFilterSection\n * groupedStudents={groupedStudents}\n * selectedIds={selectedIds}\n * searchQuery={searchQuery}\n * isLoading={isLoading}\n * hasFilters={hasFilters}\n * onSearchChange={setSearchQuery}\n * onSelectionChange={setSelectedIds}\n * />\n * ```\n */\nexport function StudentsFilterSection({\n groupedStudents,\n selectedIds,\n searchQuery,\n isLoading,\n hasFilters,\n onSearchChange,\n onSelectionChange,\n}: Readonly<StudentsFilterSectionProps>) {\n // Filter students by name based on search\n const filteredGroups = useMemo(() => {\n if (!searchQuery.trim()) {\n return groupedStudents;\n }\n\n const query = searchQuery.toLowerCase().trim();\n\n return groupedStudents\n .map((group) => ({\n ...group,\n students: group.students.filter((student) =>\n student.name.toLowerCase().includes(query)\n ),\n }))\n .filter((group) => group.students.length > 0);\n }, [groupedStudents, searchQuery]);\n\n // Total students (after search filter)\n const totalStudents = useMemo(\n () => filteredGroups.reduce((acc, group) => acc + group.students.length, 0),\n [filteredGroups]\n );\n\n // All visible student IDs (after search filter)\n const allVisibleIds = useMemo(\n () => filteredGroups.flatMap((group) => group.students.map((s) => s.id)),\n [filteredGroups]\n );\n\n // Check if all are selected\n const allSelected =\n totalStudents > 0 && selectedIds.length === allVisibleIds.length;\n\n // Toggle individual student\n const handleStudentToggle = useCallback(\n (studentId: string) => {\n const newSelected = selectedIds.includes(studentId)\n ? selectedIds.filter((id) => id !== studentId)\n : [...selectedIds, studentId];\n onSelectionChange(newSelected);\n },\n [selectedIds, onSelectionChange]\n );\n\n // Toggle all students\n const handleSelectAll = useCallback(() => {\n if (allSelected) {\n onSelectionChange([]);\n } else {\n onSelectionChange(allVisibleIds);\n }\n }, [allSelected, allVisibleIds, onSelectionChange]);\n\n // Toggle entire group\n const handleGroupToggle = useCallback(\n (group: StudentGroup) => {\n const groupIds = group.students.map((s) => s.id);\n const allGroupSelected = groupIds.every((id) => selectedIds.includes(id));\n\n if (allGroupSelected) {\n // Deselect all from group\n onSelectionChange(selectedIds.filter((id) => !groupIds.includes(id)));\n } else {\n // Select all from group\n const newSelected = new Set([...selectedIds, ...groupIds]);\n onSelectionChange(Array.from(newSelected));\n }\n },\n [selectedIds, onSelectionChange]\n );\n\n // Check if group is fully selected\n const isGroupSelected = useCallback(\n (group: StudentGroup) => {\n return group.students.every((s) => selectedIds.includes(s.id));\n },\n [selectedIds]\n );\n\n // Check if group is partially selected\n const isGroupPartiallySelected = useCallback(\n (group: StudentGroup) => {\n const selected = group.students.filter((s) =>\n selectedIds.includes(s.id)\n ).length;\n return selected > 0 && selected < group.students.length;\n },\n [selectedIds]\n );\n\n // Message when no filters selected\n if (!hasFilters) {\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-2 text-text-400 text-sm font-medium uppercase\">\n <UsersIcon size={16} className=\"text-text-400\" />\n <span>ESTUDANTES</span>\n </div>\n <div className=\"flex items-center justify-center py-6 px-4 bg-background-100 rounded-lg\">\n <Text size=\"sm\" className=\"text-text-500 text-center\">\n Selecione uma escola, série ou turma para ver os estudantes\n </Text>\n </div>\n </div>\n );\n }\n\n // No students (and not loading)\n if (totalStudents === 0 && !searchQuery && !isLoading) {\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-2 text-text-400 text-sm font-medium uppercase\">\n <UsersIcon size={16} className=\"text-text-400\" />\n <span>ESTUDANTES</span>\n </div>\n <div className=\"flex items-center justify-center py-6 px-4 bg-background-100 rounded-lg\">\n <Text size=\"sm\" className=\"text-text-500 text-center\">\n Nenhum estudante encontrado para os filtros selecionados\n </Text>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n {/* Header */}\n <div className=\"flex items-center gap-2 text-text-400 text-sm font-medium uppercase\">\n <UsersIcon size={16} className=\"text-text-400\" />\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-400 uppercase\">\n ESTUDANTES\n </Text>\n {totalStudents > 0 && !isLoading && (\n <Text size=\"sm\" className=\"text-text-500 lowercase\">\n ({totalStudents})\n </Text>\n )}\n {isLoading && (\n <Text size=\"sm\" className=\"text-text-500 lowercase\">\n Carregando estudantes...\n </Text>\n )}\n </div>\n\n {/* Search input */}\n <Input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => onSearchChange(e.target.value)}\n placeholder=\"Buscar\"\n disabled={isLoading}\n iconLeft={<MagnifyingGlassIcon size={18} />}\n />\n\n {/* Select all checkbox */}\n <div\n className={`hover:bg-background-50 p-2 rounded-lg -mx-2 ${isLoading ? 'opacity-50 pointer-events-none' : ''}`}\n >\n <CheckBox\n checked={allSelected}\n onChange={handleSelectAll}\n disabled={isLoading}\n label=\"Todos os estudantes\"\n size=\"medium\"\n />\n </div>\n\n {/* Students list grouped */}\n <div\n className={`flex flex-col gap-2 max-h-64 overflow-y-auto min-h-[100px] ${isLoading ? 'opacity-50 pointer-events-none' : ''}`}\n >\n {filteredGroups.map((group) => (\n <div key={group.key} className=\"flex flex-col\">\n {/* Group header */}\n <div className=\"hover:bg-background-50 p-2 rounded-lg\">\n <CheckBox\n checked={isGroupSelected(group)}\n indeterminate={isGroupPartiallySelected(group)}\n onChange={() => handleGroupToggle(group)}\n disabled={isLoading}\n size=\"medium\"\n label={\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-600\">\n {group.label}\n </span>\n <span className=\"text-xs text-text-400\">\n ({group.students.length})\n </span>\n </span>\n }\n />\n </div>\n\n {/* Students in group */}\n <div className=\"ml-6 flex flex-col\">\n {group.students.map((student) => (\n <div\n key={student.id}\n className=\"hover:bg-background-50 p-2 rounded-lg\"\n >\n <CheckBox\n checked={selectedIds.includes(student.id)}\n onChange={() => handleStudentToggle(student.id)}\n disabled={isLoading}\n size=\"medium\"\n label={student.name}\n />\n </div>\n ))}\n </div>\n </div>\n ))}\n\n {/* No results from search */}\n {filteredGroups.length === 0 && searchQuery && !isLoading && (\n <div className=\"flex items-center justify-center py-4\">\n <Text size=\"sm\" className=\"text-text-500\">\n Nenhum estudante encontrado para \"{searchQuery}\"\n </Text>\n </div>\n )}\n\n {/* Loading placeholder when no previous data */}\n {filteredGroups.length === 0 && !searchQuery && isLoading && (\n <div className=\"flex items-center justify-center py-4\">\n <Text size=\"sm\" className=\"text-text-500\">\n Carregando estudantes...\n </Text>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nexport default StudentsFilterSection;\n","import { useEffect, useMemo } from 'react';\nimport { MenuItem, MenuOverflow } from '../Menu/Menu';\nimport IconRender from '../IconRender/IconRender';\nimport Text from '../Text/Text';\nimport { cn, getSubjectColorWithOpacity } from '../../utils/utils';\nimport { useTheme } from '../../hooks/useTheme';\nimport { GridFourIcon } from '@phosphor-icons/react/dist/csr/GridFour';\nimport { useSimulatedSubjects } from './useSimulatedSubjects';\nimport type { SimulatedSubjectMenuProps, SimulatedSubjectItem } from './types';\n\n/**\n * SimulatedSubjectMenu - Menu component for selecting subjects in simulated exams\n *\n * Fetches subjects that have questions in simulated exams.\n * Subjects can be filtered by area of knowledge (Área de Conhecimento).\n *\n * @example\n * ```tsx\n * import api from '@/services/apiService';\n *\n * <SimulatedSubjectMenu\n * api={api}\n * areaKnowledgeId={selectedAreaKnowledgeId}\n * selectedSubjectId={selectedSubjectId}\n * onSubjectChange={handleSubjectChange}\n * loading={generalOverviewLoading}\n * />\n * ```\n */\nexport function SimulatedSubjectMenu({\n api,\n areaKnowledgeId,\n relatedIds,\n selectedSubjectId,\n onSubjectChange,\n loading: externalLoading = false,\n label = 'Disciplina',\n}: SimulatedSubjectMenuProps) {\n const {\n subjects,\n loading: subjectsLoading,\n fetchSubjects,\n } = useSimulatedSubjects(api);\n const { isDark } = useTheme();\n\n useEffect(() => {\n // Use relatedIds when available (for merged areas), otherwise fallback to single ID\n const idsToFetch =\n relatedIds ?? (areaKnowledgeId ? [areaKnowledgeId] : null);\n fetchSubjects(idsToFetch);\n }, [fetchSubjects, areaKnowledgeId, relatedIds]);\n\n const loading = externalLoading || subjectsLoading;\n\n // Add \"Todos\" option at the beginning\n // Note: color field is not used for \"all\" option (handled separately in render)\n const allOption: SimulatedSubjectItem = useMemo(\n () => ({\n id: 'all',\n name: 'Todos',\n color: '', // Not used - \"all\" option has special styling\n icon: 'shapes',\n }),\n []\n );\n\n const menuItems = useMemo(\n () => [allOption, ...subjects],\n [allOption, subjects]\n );\n\n const effectiveValue = selectedSubjectId || 'all';\n\n return (\n <div className=\"space-y-2\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-700\">\n {label}\n </Text>\n {/* Keep MenuOverflow mounted to preserve state, use opacity for loading */}\n <div\n className={cn(\n 'relative transition-opacity',\n loading && 'opacity-50 pointer-events-none'\n )}\n >\n <MenuOverflow\n defaultValue=\"all\"\n value={effectiveValue}\n className=\"max-w-full min-h-fit\"\n onValueChange={onSubjectChange}\n >\n {menuItems.map((subject: SimulatedSubjectItem) => (\n <MenuItem\n key={subject.id}\n variant=\"menu-overflow\"\n value={subject.id}\n className=\"whitespace-nowrap\"\n >\n <span\n className={cn(\n 'w-[21px] h-[21px] flex items-center justify-center [&>svg]:w-[17px] [&>svg]:h-[17px] rounded-sm text-text-950',\n subject.id === 'all' && 'bg-border-200'\n )}\n style={\n subject.id === 'all'\n ? undefined\n : {\n backgroundColor: getSubjectColorWithOpacity(\n subject?.color ?? undefined,\n isDark\n ),\n }\n }\n >\n {subject.id === 'all' ? (\n <GridFourIcon size={17} weight=\"bold\" />\n ) : (\n <IconRender\n iconName={subject.icon ?? 'Shapes'}\n size={17}\n color=\"currentColor\"\n />\n )}\n </span>\n <span className=\"whitespace-nowrap\">{subject.name}</span>\n </MenuItem>\n ))}\n </MenuOverflow>\n </div>\n </div>\n );\n}\n\nexport default SimulatedSubjectMenu;\n","import { useState, useCallback } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n SimulatedSubjectsApiResponse,\n UseSimulatedSubjectsState,\n UseSimulatedSubjectsReturn,\n} from './types';\n\n/**\n * Initial state for the hook\n */\nconst initialState: UseSimulatedSubjectsState = {\n subjects: [],\n loading: false,\n error: null,\n};\n\n/**\n * Build the API endpoint for subjects\n * Supports filtering by areaKnowledgeId (single) or areaKnowledgeIds (array for merged areas)\n */\nfunction buildEndpoint(areaKnowledgeIds?: string[] | null): string {\n if (areaKnowledgeIds && areaKnowledgeIds.length > 0) {\n return `/performance/simulated/subjects?areaKnowledgeIds=${encodeURIComponent(areaKnowledgeIds.join(','))}`;\n }\n return '/performance/simulated/subjects';\n}\n\n/**\n * Hook for fetching subjects available in simulated exams\n *\n * Receives the API client and calls the endpoint directly.\n * Endpoint: GET /performance/simulated/subjects\n *\n * @param api - API client instance (axios-like)\n * @returns Hook state and methods\n *\n * @example\n * ```tsx\n * import api from '@/services/apiService';\n *\n * const { subjects, loading, error, fetchSubjects } = useSimulatedSubjects(api);\n *\n * useEffect(() => {\n * fetchSubjects(areaKnowledgeId);\n * }, [fetchSubjects, areaKnowledgeId]);\n * ```\n */\nexport function useSimulatedSubjects(\n api: BaseApiClient\n): UseSimulatedSubjectsReturn {\n const [state, setState] = useState<UseSimulatedSubjectsState>(initialState);\n\n /**\n * Fetch subjects, optionally filtered by area(s)\n * @param areaKnowledgeIds - Optional array of area knowledge IDs to filter subjects\n * (supports multiple IDs for merged areas)\n */\n const fetchSubjects = useCallback(\n async (areaKnowledgeIds?: string[] | null) => {\n setState((prev) => ({\n ...prev,\n loading: true,\n error: null,\n }));\n\n try {\n const endpoint = buildEndpoint(areaKnowledgeIds);\n const response = await api.get<SimulatedSubjectsApiResponse>(endpoint);\n\n setState({\n subjects: response.data.data,\n loading: false,\n error: null,\n });\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Erro ao carregar disciplinas';\n\n console.error('Error fetching simulated subjects:', err);\n\n setState((prev) => ({\n ...prev,\n subjects: [],\n loading: false,\n error: errorMessage,\n }));\n }\n },\n [api]\n );\n\n /**\n * Reset state to initial values\n */\n const reset = useCallback(() => {\n setState(initialState);\n }, []);\n\n return {\n ...state,\n fetchSubjects,\n reset,\n };\n}\n\nexport default useSimulatedSubjects;\n","import Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport { MedalIcon } from '@phosphor-icons/react/dist/csr/Medal';\nimport { WarningIcon } from '@phosphor-icons/react/dist/csr/Warning';\nimport { ScoreType } from '../../types/common';\nimport { formatScore } from './utils';\nimport {\n type RankingVariant,\n type SimulatedStudentRankingItem,\n type SimulatedStudentRankingProps,\n type SimulatedRankingCardProps,\n} from './types';\n\n/**\n * Position background colors by variant\n */\nconst POSITION_BACKGROUNDS: Record<RankingVariant, Record<number, string>> = {\n highlight: {\n 1: 'bg-success-100',\n 2: 'bg-success-50',\n 3: 'bg-success-25',\n },\n attention: {\n 1: 'bg-error-100',\n 2: 'bg-error-50',\n 3: 'bg-error-25',\n },\n};\n\n/**\n * Badge background colors by variant\n */\nconst BADGE_BACKGROUNDS: Record<RankingVariant, string> = {\n highlight: 'bg-warning-300',\n attention: 'bg-error-300',\n};\n\n/**\n * Score badge classes by variant\n */\nconst SCORE_BADGE_CLASSES: Record<RankingVariant, string> = {\n highlight: 'bg-success-200 text-success-900',\n attention: 'bg-error-200 text-error-900',\n};\n\n/**\n * Individual student card\n */\nfunction StudentCard({\n student,\n variant,\n scoreType = ScoreType.PERCENTAGE,\n}: Readonly<{\n student: SimulatedStudentRankingItem;\n variant: RankingVariant;\n scoreType?: ScoreType;\n}>) {\n const backgroundClass =\n POSITION_BACKGROUNDS[variant][student.position] || 'bg-background-50';\n\n return (\n <div\n className={cn(\n 'flex flex-row items-center w-full p-4 gap-2 rounded-xl',\n backgroundClass\n )}\n >\n {/* Position badge */}\n <Text\n size=\"xs\"\n weight=\"bold\"\n className={cn(\n 'w-5 h-5 rounded-full flex items-center justify-center text-text',\n BADGE_BACKGROUNDS[variant]\n )}\n >\n {student.position}\n </Text>\n\n {/* Name and optional subtitle */}\n <div className=\"flex-1 min-w-0 flex flex-col\">\n <Text\n size=\"sm\"\n weight=\"bold\"\n className=\"text-text-950 tracking-[0.2px] truncate\"\n >\n {student.name}\n </Text>\n {student.subtitle && (\n <Text size=\"xs\" className=\"text-text-500 truncate\">\n {student.subtitle}\n </Text>\n )}\n </div>\n\n {/* Score badge */}\n <Text\n size=\"xs\"\n weight=\"bold\"\n className={cn(\n 'flex flex-row items-center h-[22px] px-2 rounded text-text',\n SCORE_BADGE_CLASSES[variant]\n )}\n >\n {formatScore(student.average, scoreType)}\n </Text>\n </div>\n );\n}\n\n/**\n * Ranking card component\n */\nexport function SimulatedRankingCard({\n title,\n variant,\n students,\n icon,\n scoreType = ScoreType.PERCENTAGE,\n}: SimulatedRankingCardProps) {\n return (\n <div className=\"flex flex-col gap-4 p-5 bg-background border border-border-50 rounded-xl flex-1\">\n <div className=\"flex items-center justify-between\">\n <Text size=\"md\" weight=\"semibold\" className=\"text-text-950\">\n {title}\n </Text>\n {icon}\n </div>\n <div className=\"flex flex-col gap-2\">\n {students.map((student) => (\n <StudentCard\n key={student.userInstitutionId ?? `${variant}-${student.position}`}\n student={student}\n variant={variant}\n scoreType={scoreType}\n />\n ))}\n {students.length === 0 && (\n <Text size=\"sm\" className=\"text-text-500 text-center py-4\">\n Nenhum estudante encontrado\n </Text>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * SimulatedStudentRanking - Student ranking component for simulated exams\n *\n * Displays two cards side by side:\n * - Highlight: Top performing students (with medal icon)\n * - Attention: Students needing attention (with warning icon)\n *\n * Supports both percentage (0-100%) and TRI (0-1000) score display.\n *\n * @example\n * ```tsx\n * <SimulatedStudentRanking\n * highlightStudents={[\n * { position: 1, name: 'João', average: 85.5 },\n * { position: 2, name: 'Maria', average: 82.3 },\n * ]}\n * attentionStudents={[\n * { position: 1, name: 'Pedro', average: 45.2 },\n * { position: 2, name: 'Ana', average: 48.7 },\n * ]}\n * scoreType=\"percentage\"\n * />\n * ```\n */\nexport function SimulatedStudentRanking({\n highlightTitle = 'Estudantes em destaque',\n attentionTitle = 'Estudantes com maior dificuldade',\n highlightStudents,\n attentionStudents,\n scoreType = ScoreType.PERCENTAGE,\n}: SimulatedStudentRankingProps) {\n return (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <SimulatedRankingCard\n title={highlightTitle}\n variant=\"highlight\"\n students={highlightStudents}\n icon={\n <MedalIcon size={20} weight=\"fill\" className=\"text-warning-500\" />\n }\n scoreType={scoreType}\n />\n <SimulatedRankingCard\n title={attentionTitle}\n variant=\"attention\"\n students={attentionStudents}\n icon={\n <WarningIcon size={20} weight=\"fill\" className=\"text-error-500\" />\n }\n scoreType={scoreType}\n />\n </div>\n );\n}\n\nexport default SimulatedStudentRanking;\n","import { useMemo, useState } from 'react';\nimport Text from '../Text/Text';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport { SimplePieChart, type PieSlice } from '../shared/ChartComponents';\nimport type {\n SimulatedPerformanceCounters,\n SliceData,\n PerformanceDistributionChartProps,\n} from './types';\n\n// ============================================================================\n// PIE CHART CONFIGURATION\n// ============================================================================\n\n/** SVG viewBox size in pixels */\nconst CHART_SIZE = 180;\n\n/** Minimum percentage threshold to display label inside slice */\nconst MIN_PERCENTAGE_FOR_LABEL = 8;\n\n/** Radius multiplier for positioning labels inside slices (0.65 = 65% from center) */\nconst LABEL_RADIUS_RATIO = 0.65;\n\n/** Hover overlay opacity */\nconst HOVER_OPACITY = 0.3;\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Build slices from counters\n */\nfunction buildSlices(counters: SimulatedPerformanceCounters): SliceData[] {\n const total =\n counters.attentionPoint +\n counters.belowAverage +\n counters.aboveAverage +\n counters.highlight;\n\n if (total === 0) return [];\n\n return [\n {\n key: 'attentionPoint',\n label: 'Ponto de atenção',\n value: counters.attentionPoint,\n percentage: (counters.attentionPoint / total) * 100,\n colorClass: 'bg-error-600',\n },\n {\n key: 'belowAverage',\n label: 'Abaixo da média',\n value: counters.belowAverage,\n percentage: (counters.belowAverage / total) * 100,\n colorClass: 'bg-warning-400',\n },\n {\n key: 'aboveAverage',\n label: 'Acima da média',\n value: counters.aboveAverage,\n percentage: (counters.aboveAverage / total) * 100,\n colorClass: 'bg-success-400',\n },\n {\n key: 'highlight',\n label: 'Destaque da turma',\n value: counters.highlight,\n percentage: (counters.highlight / total) * 100,\n colorClass: 'bg-success-700',\n },\n ];\n}\n\n/**\n * Convert SliceData to PieSlice format for SimplePieChart\n */\nfunction toPieSlices(slices: SliceData[]): PieSlice[] {\n return slices.map((s) => ({\n key: s.key,\n label: s.label,\n value: s.value,\n colorClass: s.colorClass,\n }));\n}\n\n/**\n * Pie chart showing performance distribution with percentages\n */\nexport function PerformanceDistributionChart({\n counters,\n totalStudents,\n loading = false,\n title = 'Proficiência por quantidade de estudante',\n}: PerformanceDistributionChartProps) {\n const [hoveredSlice, setHoveredSlice] = useState<string | null>(null);\n\n const slices = useMemo(() => {\n if (!counters) return [];\n return buildSlices(counters);\n }, [counters]);\n\n const pieSlices = useMemo(() => toPieSlices(slices), [slices]);\n\n const total = slices.reduce((sum, s) => sum + s.value, 0);\n\n if (loading) {\n return <SkeletonCard className=\"min-h-[280px]\" />;\n }\n\n return (\n <div className=\"bg-background border border-border-50 rounded-xl p-5\">\n <Text as=\"h3\" size=\"md\" weight=\"semibold\" className=\"text-text-950 mb-4\">\n {title}\n </Text>\n\n <div className=\"flex items-center justify-between gap-6\">\n {/* Legend */}\n <div className=\"flex flex-col gap-3\">\n {slices.map((slice) => (\n <div\n key={slice.key}\n className={`flex items-center gap-3 transition-opacity ${\n hoveredSlice && hoveredSlice !== slice.key ? 'opacity-50' : ''\n }`}\n onMouseEnter={() => setHoveredSlice(slice.key)}\n onMouseLeave={() => setHoveredSlice(null)}\n >\n <span\n className={`w-3 h-3 rounded-full ${slice.colorClass} shrink-0`}\n />\n <div className=\"flex flex-col\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-950\">\n {slice.label}\n </Text>\n <Text size=\"xs\" className=\"text-text-500\">\n {slice.value} {slice.value === 1 ? 'aluno' : 'alunos'} (\n {slice.percentage.toFixed(1)}%)\n </Text>\n </div>\n </div>\n ))}\n\n {/* Total */}\n <div className=\"pt-2 border-t border-border-100\">\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-950\">\n Total: {totalStudents ?? total}{' '}\n {(totalStudents ?? total) === 1 ? 'aluno' : 'alunos'}\n </Text>\n </div>\n </div>\n\n {/* Pie Chart */}\n <div className=\"shrink-0\">\n <SimplePieChart\n slices={pieSlices}\n size={CHART_SIZE}\n emptyText=\"Sem dados\"\n minPercentageForLabel={MIN_PERCENTAGE_FOR_LABEL}\n labelRadiusRatio={LABEL_RADIUS_RATIO}\n labelColor=\"white\"\n labelFontWeight={600}\n labelTextShadow=\"0 1px 2px rgba(0,0,0,0.3)\"\n hoverOpacity={HOVER_OPACITY}\n hoveredSlice={hoveredSlice}\n onSliceHover={setHoveredSlice}\n />\n </div>\n </div>\n </div>\n );\n}\n","import { useEffect, useCallback } from 'react';\nimport Modal from '../Modal/Modal';\nimport Text from '../Text/Text';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { SkeletonRounded } from '../Skeleton/Skeleton';\nimport { TableProvider, type TableParams } from '../TableProvider';\nimport { ArrowLeftIcon } from '@phosphor-icons/react/dist/csr/ArrowLeft';\nimport { useSimulatedContentDetails } from './useSimulatedContentDetails';\nimport { formatPercentageRounded } from '../../utils/utils';\nimport type {\n SimulatedContentDetailsModalProps,\n ContentStudentItem,\n} from './types';\nimport Button from '../Button/Button';\n\n/**\n * Table columns configuration\n */\nconst TABLE_COLUMNS = [\n {\n key: 'name',\n label: 'Nome',\n className: 'py-3 px-4 text-start',\n },\n {\n key: 'school',\n label: 'Escola',\n className: 'py-3 px-4 text-start',\n },\n {\n key: 'schoolYear',\n label: 'Ano',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '80px',\n },\n {\n key: 'class',\n label: 'Turma',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '80px',\n },\n {\n key: 'average',\n label: 'Média',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '100px',\n render: (_value: unknown, row: Record<string, unknown>) => {\n const student = row as unknown as ContentStudentItem;\n return (\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-950\">\n {Math.round(student.average)}\n </Text>\n );\n },\n },\n {\n key: 'performance',\n label: 'Desempenho',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '160px',\n render: (_value: unknown, row: Record<string, unknown>) => {\n const student = row as unknown as ContentStudentItem;\n return (\n <div className=\"flex items-center justify-center gap-2\">\n <div className=\"w-20 shrink-0\">\n <ProgressBar\n value={student.performance}\n variant=\"green\"\n size=\"small\"\n />\n </div>\n <Text size=\"sm\" weight=\"semibold\" className=\"w-10 text-text-600\">\n {formatPercentageRounded(student.performance)}\n </Text>\n </div>\n );\n },\n },\n];\n\n/**\n * Modal for displaying content (habilidade) performance details in simulated exams\n * Shows table of students with their individual performance for the selected content\n */\nexport function SimulatedContentDetailsModal({\n api,\n isOpen,\n onClose,\n activityFilters,\n contentId,\n contentName,\n period,\n filters,\n}: SimulatedContentDetailsModalProps) {\n const { data, loading, error, fetchDetails, reset } =\n useSimulatedContentDetails(api);\n\n // Fetch content details when modal opens\n useEffect(() => {\n if (isOpen && contentId) {\n fetchDetails({\n activityFilters,\n contentId,\n period,\n schoolIds: filters?.schoolIds,\n schoolYearIds: filters?.schoolYearIds,\n classIds: filters?.classIds,\n page: 1,\n limit: 10,\n });\n }\n }, [isOpen, contentId, activityFilters, period, filters, fetchDetails]);\n\n // Reset state when modal closes\n useEffect(() => {\n if (!isOpen) {\n reset();\n }\n }, [isOpen, reset]);\n\n // Handle table params change (pagination)\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n if (!contentId) return;\n fetchDetails({\n activityFilters,\n contentId,\n period,\n schoolIds: filters?.schoolIds,\n schoolYearIds: filters?.schoolYearIds,\n classIds: filters?.classIds,\n page: params.page,\n limit: params.limit,\n });\n },\n [contentId, activityFilters, period, filters, fetchDetails]\n );\n\n // Build modal title\n const modalTitle = (\n <div className=\"flex items-center gap-2\">\n <Button\n onClick={onClose}\n variant=\"raw\"\n className=\"p-1 hover:bg-background-100 rounded-md transition-colors\"\n aria-label=\"Fechar modal\"\n >\n <ArrowLeftIcon size={20} className=\"text-text-600\" />\n </Button>\n <Text>Desempenho competência</Text>\n </div>\n );\n\n // Render loading state\n if (loading && !data) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex flex-col gap-4\">\n {/* Header skeleton */}\n <div className=\"p-4 bg-background-50 rounded-xl\">\n <SkeletonRounded className=\"h-5 w-48 mb-2\" />\n <SkeletonRounded className=\"h-4 w-64\" />\n </div>\n\n {/* Counters skeleton */}\n <div className=\"grid grid-cols-3 gap-4\">\n {[1, 2, 3].map((i) => (\n <SkeletonRounded key={i} className=\"h-20 rounded-xl\" />\n ))}\n </div>\n\n {/* Table skeleton */}\n <div className=\"flex flex-col gap-2\">\n <SkeletonRounded className=\"h-10 rounded-lg\" />\n {[1, 2, 3, 4, 5].map((i) => (\n <SkeletonRounded key={i} className=\"h-12 rounded-lg\" />\n ))}\n </div>\n </div>\n </Modal>\n );\n }\n\n // Render error state\n if (error) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-error-500\">\n {error}\n </Text>\n </div>\n </Modal>\n );\n }\n\n // Render empty state\n if (!data) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-text-500\">\n Nenhum dado encontrado\n </Text>\n </div>\n </Modal>\n );\n }\n\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex flex-col gap-6\">\n {/* Content header */}\n <div className=\"flex flex-col gap-1 p-4 bg-background-50 rounded-xl\">\n <Text size=\"md\" weight=\"semibold\" className=\"text-text-950\">\n {data.content.name || contentName}\n </Text>\n <div className=\"flex items-center gap-2\">\n {data.content.bnccCode && (\n <Text size=\"sm\" className=\"text-primary-600\">\n {data.content.bnccCode}\n </Text>\n )}\n <Text size=\"sm\" className=\"text-text-500\">\n {data.content.subject.name} • {data.content.questionsCount}{' '}\n questões • {data.content.studentsCount} alunos\n </Text>\n </div>\n </div>\n\n {/* Performance counters */}\n <div className=\"grid grid-cols-3 gap-3\">\n <CounterCard\n label=\"Acima da média\"\n count={data.counters.aboveAverage}\n variant=\"success\"\n />\n <CounterCard\n label=\"Na média\"\n count={data.counters.atAverage}\n variant=\"info\"\n />\n <CounterCard\n label=\"Abaixo da média\"\n count={data.counters.belowAverage}\n variant=\"error\"\n />\n </div>\n\n {/* Students table */}\n <TableProvider\n data={data.students.data as unknown as Record<string, unknown>[]}\n headers={TABLE_COLUMNS}\n variant=\"borderless\"\n loading={loading}\n enablePagination\n rowKey=\"userInstitutionId\"\n paginationConfig={{\n itemLabel: 'estudantes',\n itemsPerPageOptions: [10, 20, 50],\n defaultItemsPerPage: 10,\n totalItems: data.students.total,\n totalPages: Math.ceil(data.students.total / data.students.limit),\n }}\n onParamsChange={handleParamsChange}\n />\n </div>\n </Modal>\n );\n}\n\n/**\n * Counter card component\n */\nfunction CounterCard({\n label,\n count,\n variant,\n}: {\n readonly label: string;\n readonly count: number;\n readonly variant: 'success' | 'info' | 'error';\n}) {\n const bgColors = {\n success: 'bg-success-50',\n info: 'bg-primary-50',\n error: 'bg-error-50',\n };\n\n const textColors = {\n success: 'text-success-700',\n info: 'text-primary-700',\n error: 'text-error-700',\n };\n\n return (\n <div\n className={`flex-1 flex flex-col items-center justify-center p-4 rounded-xl ${bgColors[variant]}`}\n >\n <Text size=\"2xl\" weight=\"bold\" className={textColors[variant]}>\n {count}\n </Text>\n <Text size=\"sm\" className=\"text-text-600 text-center\">\n {label}\n </Text>\n </div>\n );\n}\n","import {\n useState,\n useEffect,\n useMemo,\n useCallback,\n useRef,\n ReactNode,\n ChangeEvent,\n} from 'react';\nimport Table, {\n TableBody,\n TableHead,\n TableRow,\n TableCell,\n useTableSort,\n TablePagination,\n type SortMode,\n type SortState,\n} from '../Table/Table';\nimport ColumnFilterMenu from '../Table/ColumnFilterMenu';\nimport {\n useColumnFilters,\n type ColumnFilterConfig,\n} from '../Table/useColumnFilters';\nimport { useTableFilter, FilterConfig } from '../Filter/useTableFilter';\n\n// Re-exported here (not only from the barrel) because the `table-provider`\n// subpath maps to this file, and consumers set `ColumnConfig.filter` with it.\nexport type {\n ColumnFilterConfig,\n ColumnFilterOption,\n} from '../Table/useColumnFilters';\nimport Search from '../Search/Search';\nimport { FilterModal } from '../Filter/FilterModal';\nimport Button from '../Button/Button';\nimport { FunnelIcon } from '@phosphor-icons/react/dist/csr/Funnel';\nimport { cn } from '../../utils/utils';\n\n/**\n * Column configuration with flexible rendering options\n */\nexport interface ColumnConfig<T = Record<string, unknown>> {\n /** Column key (must match data object key) */\n key: string;\n /** Column label - can be string or JSX */\n label: string | ReactNode;\n /**\n * Enable sorting for this column. Overrides `sortableColumns`; when omitted,\n * `sortableColumns` decides.\n */\n sortable?: boolean;\n /**\n * Field to sort by, when it differs from the rendered one. A column showing\n * `totalTimeFormatted` (\"2h 05min\") must sort by `totalTime` — otherwise the\n * API gets a field it doesn't know, and a client-side sort would compare the\n * formatted strings. Defaults to `key`.\n */\n sortKey?: string;\n /** Turns this column's header into a filter dropdown. */\n filter?: ColumnFilterConfig;\n /** Custom render function for cell content */\n render?: (value: unknown, row: T, index: number) => ReactNode;\n /** Column width */\n width?: string;\n /** Additional CSS classes */\n className?: string;\n /** Text alignment */\n align?: 'left' | 'center' | 'right';\n}\n\n/**\n * Which columns can be sorted, when a column doesn't say so itself.\n *\n * Defaults to none: turning `enableTableSort` on must not silently make every\n * column clickable, since most tables here are paginated server-side and a\n * client-side sort would only reorder the page you're looking at.\n */\nexport function isColumnSortable<T>(\n header: ColumnConfig<T>,\n enableTableSort: boolean,\n sortableColumns: SortableColumns | undefined\n): boolean {\n if (!enableTableSort) return false;\n if (header.sortable !== undefined) return header.sortable;\n if (sortableColumns === 'all') return true;\n if (Array.isArray(sortableColumns))\n return sortableColumns.includes(header.key);\n return false;\n}\n\nexport type SortableColumns = 'all' | string[];\n\n/**\n * Combined parameters sent via onParamsChange\n */\nexport interface TableParams {\n /** Current page number */\n page: number;\n /** Items per page */\n limit: number;\n /** Search query */\n search?: string;\n /** Active filters (dynamic keys based on filter configs) */\n [key: string]: unknown;\n /** Sort column */\n sortBy?: string;\n /** Sort direction */\n sortOrder?: 'asc' | 'desc';\n}\n\n/**\n * Pagination configuration\n */\nexport interface PaginationConfig {\n /** Label for items (e.g., \"atividades\") */\n itemLabel?: string;\n /** Items per page options */\n itemsPerPageOptions?: number[];\n /** Default items per page */\n defaultItemsPerPage?: number;\n /** Total items (for displaying pagination info) */\n totalItems?: number;\n /** Total pages (if known from backend) */\n totalPages?: number;\n}\n\n/**\n * Empty state configuration\n */\nexport interface EmptyStateConfig {\n /** Custom component to render when table is empty (no data and no search active) */\n component?: ReactNode;\n /** Image to display in empty state (path from project) */\n image?: string;\n /** Title text for empty state */\n title?: string;\n /** Description text for empty state */\n description?: string;\n /** Button text for empty state action (optional) */\n buttonText?: string;\n /** Icon to display on button (optional) */\n buttonIcon?: ReactNode;\n /** Callback when empty state button is clicked */\n onButtonClick?: () => void;\n /** Button variant (solid, outline, or link) */\n buttonVariant?: 'solid' | 'outline' | 'link';\n /** Button action color (primary, positive, or negative) */\n buttonAction?: 'primary' | 'positive' | 'negative';\n}\n\n/**\n * Loading state configuration\n */\nexport interface LoadingStateConfig {\n /** Custom component to render when table is loading */\n component?: ReactNode;\n}\n\n/**\n * No search result state configuration\n */\nexport interface NoSearchResultConfig {\n /** Custom component to render when search returns no results */\n component?: ReactNode;\n /** Title for no search result state */\n title?: string;\n /** Description for no search result state */\n description?: string;\n /** Image to display in no search result state */\n image?: string;\n}\n\n/**\n * Table components exposed via render prop\n */\nexport interface TableComponents {\n /** Search and filter controls */\n controls: ReactNode;\n /** Table with data */\n table: ReactNode;\n /** Pagination controls */\n pagination: ReactNode;\n}\n\n/**\n * TableProvider Props\n */\nexport interface TableProviderProps<T = Record<string, unknown>> {\n /** Data to display in the table */\n readonly data: T[];\n /** Column configurations */\n readonly headers: ColumnConfig<T>[];\n /** Loading state */\n readonly loading?: boolean;\n /** Table variant */\n readonly variant?: 'default' | 'borderless';\n\n /** Enable search functionality */\n readonly enableSearch?: boolean;\n /** Enable filters functionality */\n readonly enableFilters?: boolean;\n /** Enable table sorting */\n readonly enableTableSort?: boolean;\n /**\n * Which columns are sortable: `'all'`, or an explicit list of column keys.\n * A column's own `sortable` flag wins over this. Defaults to none.\n */\n readonly sortableColumns?: SortableColumns;\n /**\n * `'client'` (default) sorts the rows it was handed. `'server'` leaves them\n * in the order they arrived and just reports `sortBy`/`sortOrder` through\n * `onParamsChange`, for the API to sort.\n */\n readonly sortMode?: SortMode;\n /** Sort applied when the URL doesn't specify one. */\n readonly defaultSort?: SortState;\n /**\n * Namespaces this table's URL params (`vendas_sortBy` instead of `sortBy`).\n * Required whenever two tables can share a URL — otherwise one reads the\n * other's sort column, which the API will reject.\n */\n readonly tableId?: string;\n /** Enable pagination */\n readonly enablePagination?: boolean;\n /** Enable row click functionality */\n readonly enableRowClick?: boolean;\n\n /** Initial filter configurations */\n readonly initialFilters?: FilterConfig[];\n /** Pagination configuration */\n readonly paginationConfig?: PaginationConfig;\n /** Search placeholder text */\n readonly searchPlaceholder?: string;\n /** Additional CSS classes for the search container */\n readonly searchContainerClassName?: string;\n /** Empty state configuration (when table is empty with no search) */\n readonly emptyState?: EmptyStateConfig;\n /** Loading state configuration (when table is loading) */\n readonly loadingState?: LoadingStateConfig;\n /** No search result state configuration (when search returns no results) */\n readonly noSearchResultState?: NoSearchResultConfig;\n /** Key field name to use for unique row identification (recommended for better performance) */\n readonly rowKey?: keyof T;\n\n /** Callback when any parameter changes */\n readonly onParamsChange?: (params: TableParams) => void;\n /** Callback when row is clicked */\n readonly onRowClick?: (row: T, index: number) => void;\n\n /**\n * Content to display in the header area (e.g., action buttons)\n * Rendered above the search/filter controls\n */\n readonly headerContent?: ReactNode;\n\n /**\n * Additional CSS classes for the container wrapper\n */\n readonly containerClassName?: string;\n\n /**\n * Render prop for custom layout control\n * When provided, gives full control over component positioning\n * @param components - Table components (controls, table, pagination)\n * @returns Custom layout JSX\n *\n * @example\n * ```tsx\n * <TableProvider {...props}>\n * {({ controls, table, pagination }) => (\n * <>\n * <div className=\"mb-4\">{controls}</div>\n * <div className=\"bg-white p-6\">\n * {table}\n * {pagination}\n * </div>\n * </>\n * )}\n * </TableProvider>\n * ```\n */\n readonly children?: (components: TableComponents) => ReactNode;\n}\n\n/**\n * TableProvider - Self-contained table component with search, filters, sorting, and pagination\n *\n * @example\n * ```tsx\n * <TableProvider\n * data={activities}\n * headers={[\n * { key: 'title', label: 'Título', sortable: true },\n * { key: 'status', label: 'Status', render: (value) => <Badge>{value}</Badge> }\n * ]}\n * loading={loading}\n * variant=\"borderless\"\n * enableSearch\n * enableFilters\n * enableTableSort\n * enablePagination\n * enableRowClick\n * initialFilters={filterConfigs}\n * paginationConfig={{ itemLabel: 'atividades' }}\n * onParamsChange={handleParamsChange}\n * onRowClick={handleRowClick}\n * />\n * ```\n */\nexport function TableProvider<T extends Record<string, unknown>>({\n data,\n headers,\n loading = false,\n variant = 'default',\n enableSearch = false,\n enableFilters = false,\n enableTableSort = false,\n sortableColumns,\n sortMode = 'client',\n defaultSort,\n tableId,\n enablePagination = false,\n enableRowClick = false,\n initialFilters = [],\n paginationConfig = {},\n searchPlaceholder = 'Buscar...',\n searchContainerClassName,\n emptyState,\n loadingState,\n noSearchResultState,\n rowKey,\n onParamsChange,\n onRowClick,\n headerContent,\n containerClassName,\n children,\n}: TableProviderProps<T>) {\n // Search state\n const [searchQuery, setSearchQuery] = useState('');\n const [inputValue, setInputValue] = useState('');\n\n // Sorting and filtering both need to send the table back to page 1, but the\n // pagination state is declared further down. Go through a ref instead of\n // reshuffling the whole component.\n const resetPageRef = useRef<() => void>(() => {});\n\n /** A column sorts by `sortKey` when it has one — see ColumnConfig.sortKey. */\n const sortKeyOf = useCallback(\n (header: ColumnConfig<T>) => header.sortKey ?? header.key,\n []\n );\n\n // The only values this table accepts as sortBy — anything else in the URL is\n // discarded rather than forwarded to the API.\n const sortableKeys = useMemo(\n () =>\n headers\n .filter((header) =>\n isColumnSortable(header, enableTableSort, sortableColumns)\n )\n .map(sortKeyOf),\n [headers, enableTableSort, sortableColumns, sortKeyOf]\n );\n\n // Always call the hook (React Rules of Hooks); `syncWithUrl` carries the gate,\n // so a table with sorting off doesn't touch the URL at all.\n const { sortedData, sortColumn, sortDirection, handleSort } = useTableSort(\n data,\n {\n syncWithUrl: enableTableSort,\n mode: sortMode,\n urlKeyPrefix: tableId,\n allowedColumns: sortableKeys,\n defaultSort,\n onSortChange: () => resetPageRef.current(),\n }\n );\n\n // Per-column header filters\n const { columnFilters, columnFilterParams, setColumnFilter } =\n useColumnFilters(headers, {\n syncWithUrl: true,\n urlKeyPrefix: tableId,\n onFiltersChange: () => resetPageRef.current(),\n });\n\n // Filter state - always call hook (React Rules of Hooks)\n const filterResultRaw = useTableFilter(initialFilters, { syncWithUrl: true });\n\n // Memoize disabled filter result to prevent recreating object on every render\n const disabledFilterResult = useMemo(\n () => ({\n filterConfigs: [],\n activeFilters: {},\n hasActiveFilters: false,\n activeFiltersCount: 0,\n updateFilters: () => {},\n applyFilters: () => {},\n clearFilters: () => {},\n }),\n []\n );\n\n const filterResult = enableFilters ? filterResultRaw : disabledFilterResult;\n\n const {\n filterConfigs,\n activeFilters,\n activeFiltersCount,\n updateFilters,\n applyFilters,\n clearFilters,\n } = filterResult;\n\n // Pagination state (only if enabled)\n const {\n defaultItemsPerPage = 10,\n itemsPerPageOptions = [10, 20, 50, 100],\n itemLabel = 'itens',\n totalItems,\n totalPages,\n } = paginationConfig;\n\n const [currentPage, setCurrentPage] = useState(1);\n const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);\n\n // Sorting/filtering by a column invalidates the current page: page 3 of the\n // old ordering has nothing to do with page 3 of the new one.\n resetPageRef.current = () => setCurrentPage(1);\n\n // Filter modal state\n const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);\n\n // Combine all parameters\n const combinedParams = useMemo((): TableParams => {\n const params: TableParams = {\n page: currentPage,\n limit: itemsPerPage,\n };\n\n if (enableSearch && searchQuery) {\n params.search = searchQuery;\n }\n\n if (enableFilters) {\n Object.assign(params, activeFilters);\n }\n\n if (enableTableSort && sortColumn && sortDirection) {\n params.sortBy = sortColumn;\n params.sortOrder = sortDirection;\n }\n\n // Header filters go out under each column's paramKey — the field name the\n // API expects.\n Object.assign(params, columnFilterParams);\n\n return params;\n }, [\n currentPage,\n itemsPerPage,\n searchQuery,\n activeFilters,\n sortColumn,\n sortDirection,\n enableSearch,\n enableFilters,\n enableTableSort,\n columnFilterParams,\n ]);\n\n // Notify parent when parameters change\n // Note: onParamsChange is omitted from dependencies intentionally to prevent infinite loops\n useEffect(() => {\n onParamsChange?.(combinedParams);\n }, [combinedParams]);\n\n // Handle search changes\n const handleSearchChange = useCallback((value: string) => {\n setSearchQuery(value);\n setCurrentPage(1); // Reset to first page on search\n }, []);\n\n const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n setInputValue(e.target.value);\n }, []);\n\n // Handle filter apply\n const handleFilterApply = useCallback(() => {\n applyFilters();\n setIsFilterModalOpen(false);\n setCurrentPage(1); // Reset to first page on filter\n }, [applyFilters]);\n\n // Handle pagination change\n const handlePageChange = useCallback((page: number) => {\n setCurrentPage(page);\n }, []);\n\n const handleItemsPerPageChange = useCallback((items: number) => {\n setItemsPerPage(items);\n setCurrentPage(1); // Reset to first page when changing items per page\n }, []);\n\n // Handle row click\n const handleRowClickInternal = useCallback(\n (row: T, index: number) => {\n if (enableRowClick && onRowClick) {\n onRowClick(row, index);\n }\n },\n [enableRowClick, onRowClick]\n );\n\n // Detect if pagination should be managed internally\n const useInternalPagination = useMemo(\n () =>\n enablePagination &&\n !onParamsChange &&\n totalItems === undefined &&\n totalPages === undefined,\n [enablePagination, onParamsChange, totalItems, totalPages]\n );\n\n // Calculate total pages from data if not provided\n const calculatedTotalPages =\n totalPages ??\n Math.ceil(\n (totalItems ??\n (useInternalPagination ? sortedData.length : data.length)) /\n itemsPerPage\n );\n const calculatedTotalItems =\n totalItems ?? (useInternalPagination ? sortedData.length : data.length);\n\n // Apply pagination to data when managed internally\n const displayData = useMemo(() => {\n if (!useInternalPagination) {\n return sortedData;\n }\n\n const start = (currentPage - 1) * itemsPerPage;\n return sortedData.slice(start, start + itemsPerPage);\n }, [useInternalPagination, sortedData, currentPage, itemsPerPage]);\n\n // Empty state check\n const isEmpty = data.length === 0;\n\n // Calculate state control booleans - Table is responsible for rendering, TableProvider controls WHEN\n const showLoading = loading;\n const showNoSearchResult =\n !loading && data.length === 0 && searchQuery.trim() !== '';\n const showEmpty = !loading && data.length === 0 && searchQuery.trim() === '';\n\n // A header filter that returns nothing replaces the whole header row — carets\n // included — with the empty state, leaving no way to undo it. When the table is\n // empty *because* of an active column filter, surface a one-click reset in the\n // empty state itself.\n const hasActiveColumnFilters = Object.keys(columnFilterParams).length > 0;\n\n const clearAllColumnFilters = useCallback(() => {\n for (const paramKey of Object.keys(columnFilters)) {\n setColumnFilter(paramKey, []);\n }\n }, [columnFilters, setColumnFilter]);\n\n const effectiveEmptyState = useMemo<EmptyStateConfig | undefined>(() => {\n if (!hasActiveColumnFilters) return emptyState;\n // Don't override a consumer that already defined its own action or body.\n if (emptyState?.component || emptyState?.buttonText) return emptyState;\n return {\n ...emptyState,\n buttonText: 'Limpar filtros',\n buttonVariant: 'outline',\n buttonAction: 'primary',\n onButtonClick: clearAllColumnFilters,\n };\n }, [emptyState, hasActiveColumnFilters, clearAllColumnFilters]);\n\n // Extract components for render prop pattern\n const controls = (enableSearch || enableFilters) && (\n <div className=\"flex items-center gap-4\">\n {/* Filter Button */}\n {enableFilters && (\n <Button\n variant=\"outline\"\n size=\"medium\"\n onClick={() => setIsFilterModalOpen(true)}\n >\n <FunnelIcon size={20} />\n Filtros\n {activeFiltersCount > 0 && (\n <span className=\"ml-2 rounded-full bg-primary-500 px-2 py-0.5 text-xs text-white\">\n {activeFiltersCount}\n </span>\n )}\n </Button>\n )}\n\n {/* Search */}\n {enableSearch && (\n <div\n className={cn('flex-1 flex justify-end', searchContainerClassName)}\n >\n <Search\n value={inputValue}\n onChange={handleInputChange}\n onSearch={handleSearchChange}\n onClear={() => {\n setInputValue('');\n handleSearchChange('');\n }}\n options={[]}\n placeholder={searchPlaceholder}\n debounceMs={300}\n />\n </div>\n )}\n </div>\n );\n\n // Header with content and controls\n const headerSection = (headerContent || controls) && (\n <div className=\"flex flex-col lg:flex-row items-stretch lg:items-center justify-between gap-4\">\n {/* Header Content (e.g., action buttons) */}\n {headerContent && <div>{headerContent}</div>}\n\n {/* Controls (search and filters) */}\n {controls && <div className=\"flex-1 lg:flex-none\">{controls}</div>}\n </div>\n );\n\n const table = (\n <div className=\"w-full overflow-x-auto\">\n <Table\n variant={variant}\n showLoading={showLoading}\n loadingState={loadingState}\n showNoSearchResult={showNoSearchResult}\n noSearchResultState={noSearchResultState}\n showEmpty={showEmpty}\n emptyState={effectiveEmptyState}\n >\n {/* Table Header */}\n <thead>\n <TableRow\n variant={variant === 'borderless' ? 'defaultBorderless' : 'default'}\n >\n {headers.map((header, index) => {\n const sortable = isColumnSortable(\n header,\n enableTableSort,\n sortableColumns\n );\n const sortKey = sortKeyOf(header);\n const filterParamKey = header.filter?.paramKey ?? header.key;\n\n return (\n <TableHead\n key={`header-${header.key}-${index}`}\n sortable={sortable}\n sortDirection={sortColumn === sortKey ? sortDirection : null}\n onSort={() => handleSort(sortKey)}\n filterSlot={\n header.filter && (\n <ColumnFilterMenu\n columnLabel={\n typeof header.label === 'string'\n ? header.label\n : header.key\n }\n config={header.filter}\n value={columnFilters[filterParamKey] ?? []}\n onChange={(values) =>\n setColumnFilter(filterParamKey, values)\n }\n />\n )\n }\n className={header.className}\n style={header.width ? { width: header.width } : undefined}\n >\n {header.label}\n </TableHead>\n );\n })}\n </TableRow>\n </thead>\n\n {/* Table Body */}\n <TableBody>\n {loading ? (\n <TableRow>\n <TableCell colSpan={headers.length} className=\"text-center py-8\">\n <span className=\"text-text-400 text-sm\">Carregando...</span>\n </TableCell>\n </TableRow>\n ) : (\n displayData.map((row, rowIndex) => {\n // Calculate effective index for row click and keys\n const effectiveIndex = useInternalPagination\n ? (currentPage - 1) * itemsPerPage + rowIndex\n : rowIndex;\n\n const rowKeyValue = rowKey\n ? (() => {\n const keyValue = row[rowKey];\n if (keyValue === null || keyValue === undefined) {\n return `row-${effectiveIndex}`;\n }\n if (typeof keyValue === 'object') {\n return JSON.stringify(keyValue);\n }\n return String(keyValue);\n })()\n : `row-${effectiveIndex}`;\n return (\n <TableRow\n key={rowKeyValue}\n variant={\n variant === 'borderless' ? 'defaultBorderless' : 'default'\n }\n clickable={enableRowClick}\n onClick={() => handleRowClickInternal(row, effectiveIndex)}\n >\n {headers.map((header, cellIndex) => {\n const value = row[header.key];\n\n let defaultContent = '';\n\n if (value !== null && value !== undefined) {\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'bigint'\n ) {\n // Only convert primitives directly to string\n defaultContent = String(value);\n } else if (typeof value === 'object') {\n // Serialize objects and arrays with JSON\n defaultContent = JSON.stringify(value);\n } else if (typeof value === 'function') {\n // Handle functions - don't expose function code\n defaultContent = '[Function]';\n } else if (typeof value === 'symbol') {\n // Handle symbols\n defaultContent = String(value);\n }\n // All possible types covered - no else needed\n }\n\n const content = header.render\n ? header.render(value, row, effectiveIndex)\n : defaultContent;\n\n return (\n <TableCell\n key={`cell-${effectiveIndex}-${cellIndex}`}\n className={header.className}\n style={{\n textAlign: header.align,\n }}\n >\n {content}\n </TableCell>\n );\n })}\n </TableRow>\n );\n })\n )}\n </TableBody>\n </Table>\n </div>\n );\n\n const pagination = enablePagination && !isEmpty && (\n <div className=\"flex justify-end\">\n <TablePagination\n currentPage={currentPage}\n totalPages={calculatedTotalPages}\n totalItems={calculatedTotalItems}\n itemsPerPage={itemsPerPage}\n itemsPerPageOptions={itemsPerPageOptions}\n onPageChange={handlePageChange}\n onItemsPerPageChange={handleItemsPerPageChange}\n itemLabel={itemLabel}\n />\n </div>\n );\n\n // If children prop provided, use render props pattern\n if (children) {\n return (\n <>\n {children({\n controls: headerSection || controls || null,\n table,\n pagination,\n })}\n {/* Filter Modal */}\n {enableFilters && (\n <FilterModal\n isOpen={isFilterModalOpen}\n onClose={() => setIsFilterModalOpen(false)}\n filterConfigs={filterConfigs}\n onFiltersChange={updateFilters}\n onApply={handleFilterApply}\n onClear={clearFilters}\n />\n )}\n </>\n );\n }\n\n // Default layout (backward compatible)\n const wrapperClassName = containerClassName || 'w-full space-y-4';\n return (\n <div className={wrapperClassName}>\n {headerSection}\n {table}\n {pagination}\n\n {/* Filter Modal */}\n {enableFilters && (\n <FilterModal\n isOpen={isFilterModalOpen}\n onClose={() => setIsFilterModalOpen(false)}\n filterConfigs={filterConfigs}\n onFiltersChange={updateFilters}\n onApply={handleFilterApply}\n onClear={clearFilters}\n />\n )}\n </div>\n );\n}\n\nexport default TableProvider;\n","import { useState, useCallback, useRef } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n ContentDetailsData,\n ContentDetailsParams,\n ContentDetailsApiResponse,\n UseSimulatedContentDetailsReturn,\n ActivityFilters,\n} from './types';\n\n/**\n * Build the API endpoint with activity filters as query params\n */\nfunction buildEndpoint(activityFilters: ActivityFilters): string {\n const params = new URLSearchParams();\n\n activityFilters.types?.forEach((t) => params.append('types', t));\n activityFilters.subtypes?.forEach((s) => params.append('subtypes', s));\n activityFilters.statuses?.forEach((s) => params.append('statuses', s));\n\n const queryString = params.toString();\n const endpoint = '/performance/simulated/activities/content-details';\n\n if (!queryString) {\n return endpoint;\n }\n\n return `${endpoint}?${queryString}`;\n}\n\n/**\n * Hook for fetching content details in simulated exams\n * Shows list of students with their performance for a specific content/habilidade\n *\n * @param api - API client with post method\n *\n * @example\n * ```tsx\n * const { data, loading, error, fetchDetails, reset } = useSimulatedContentDetails(api);\n *\n * fetchDetails({\n * activityFilters: { types: ['SIMULADO'], statuses: ['CONCLUIDA'] },\n * contentId: 'content-uuid',\n * period: '1_MONTH',\n * });\n * ```\n */\nexport function useSimulatedContentDetails(\n api: BaseApiClient\n): UseSimulatedContentDetailsReturn {\n const [data, setData] = useState<ContentDetailsData | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const requestVersionRef = useRef(0);\n\n const fetchDetails = useCallback(\n async (params: ContentDetailsParams) => {\n const currentRequestVersion = ++requestVersionRef.current;\n\n try {\n if (currentRequestVersion === requestVersionRef.current) {\n setLoading(true);\n setError(null);\n }\n\n const endpoint = buildEndpoint(params.activityFilters);\n\n const response = await api.post<ContentDetailsApiResponse>(endpoint, {\n contentId: params.contentId,\n period: params.period,\n schoolIds: params.schoolIds,\n schoolYearIds: params.schoolYearIds,\n classIds: params.classIds,\n studentsIds: params.studentsIds,\n page: params.page ?? 1,\n limit: params.limit ?? 20,\n orderBy: params.orderBy,\n order: params.order,\n });\n\n if (currentRequestVersion === requestVersionRef.current) {\n setData(response.data.data);\n }\n } catch (err) {\n if (currentRequestVersion !== requestVersionRef.current) {\n return;\n }\n\n const errorMessage =\n err instanceof Error\n ? err.message\n : 'Não foi possível carregar os detalhes da habilidade';\n setError(errorMessage);\n } finally {\n if (currentRequestVersion === requestVersionRef.current) {\n setLoading(false);\n }\n }\n },\n [api]\n );\n\n const reset = useCallback(() => {\n requestVersionRef.current++;\n setData(null);\n setLoading(false);\n setError(null);\n }, []);\n\n return { data, loading, error, fetchDetails, reset };\n}\n","import { useState, useCallback, useEffect, useMemo } from 'react';\nimport { TableProvider } from '../TableProvider';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\nimport { useEssayCompetenciesOverview } from './useEssayCompetenciesOverview';\nimport { EssayCompetenceDetailsModal } from './EssayCompetenceDetailsModal';\nimport type {\n EssayCompetenciesTableProps,\n EssayCompetencyOverviewItem,\n} from './types';\n\n/**\n * Proficiency cell component with progress bar\n */\nfunction ProficiencyCell({ percentage }: { readonly percentage: number }) {\n // Clamp percentage to valid range\n const clampedPercentage = Math.max(0, Math.min(100, percentage));\n\n const getProgressColorClass = (): string => {\n if (clampedPercentage >= 70) {\n return 'bg-success-500';\n }\n\n if (clampedPercentage >= 50) {\n return 'bg-warning-500';\n }\n\n return 'bg-error-500';\n };\n\n const progressColorClass = getProgressColorClass();\n\n return (\n <div className=\"flex items-center justify-center gap-2\">\n <Text size=\"sm\" className=\"min-w-[40px] text-right text-text-950\">\n {Math.round(clampedPercentage)}%\n </Text>\n <div className=\"w-16 h-2 bg-background-100 rounded-full overflow-hidden\">\n <div\n className={cn('h-full rounded-full', progressColorClass)}\n style={{ width: `${clampedPercentage}%` }}\n />\n </div>\n </div>\n );\n}\n\n/**\n * Table columns configuration\n */\nconst TABLE_COLUMNS = [\n {\n key: 'name',\n label: 'Competência',\n sortable: true,\n className: 'py-3 px-4 text-start',\n render: (_value: unknown, row: Record<string, unknown>, _index: number) => {\n const item = row as unknown as EssayCompetencyOverviewItem;\n return `C${item.competencyNumber} - ${item.name}`;\n },\n },\n {\n key: 'essaysCount',\n label: 'Redações',\n sortable: true,\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '100px',\n },\n {\n key: 'studentsCount',\n label: 'Estudantes',\n sortable: true,\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '100px',\n },\n {\n key: 'averagePercentage',\n label: 'Proficiência',\n sortable: true,\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '160px',\n render: (_value: unknown, row: Record<string, unknown>, _index: number) => {\n const item = row as unknown as EssayCompetencyOverviewItem;\n return <ProficiencyCell percentage={item.averagePercentage} />;\n },\n },\n];\n\n/**\n * Table showing the 5 ENEM essay competencies with stats\n * Clicking on a competency opens the EssayCompetenceDetailsModal\n */\nexport function EssayCompetenciesTable({\n api,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n}: EssayCompetenciesTableProps) {\n const { data, loading, error, fetchOverview } =\n useEssayCompetenciesOverview(api);\n const [selectedCompetence, setSelectedCompetence] = useState<{\n number: number;\n name: string;\n } | null>(null);\n const [isModalOpen, setIsModalOpen] = useState(false);\n\n // Fetch data when filters change\n useEffect(() => {\n fetchOverview({\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n });\n }, [period, schoolIds, schoolYearIds, classIds, fetchOverview]);\n\n const handleRowClick = useCallback(\n (row: Record<string, unknown>, _index: number) => {\n const item = row as unknown as EssayCompetencyOverviewItem;\n setSelectedCompetence({\n number: item.competencyNumber,\n name: item.name,\n });\n setIsModalOpen(true);\n },\n []\n );\n\n const handleCloseModal = useCallback(() => {\n setIsModalOpen(false);\n setSelectedCompetence(null);\n }, []);\n\n const competencies = useMemo(() => data?.competencies ?? [], [data]);\n\n // Render error state\n if (error) {\n return (\n <div className=\"bg-background border border-border-50 rounded-xl p-5\">\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950 mb-4\">\n Proficiência por competência\n </Text>\n <div className=\"flex items-center justify-center py-8 bg-error-50 rounded-lg\">\n <Text size=\"sm\" className=\"text-error-500\">\n {error}\n </Text>\n </div>\n </div>\n );\n }\n\n return (\n <>\n <TableProvider\n data={competencies as unknown as Record<string, unknown>[]}\n headers={TABLE_COLUMNS}\n variant=\"borderless\"\n loading={loading}\n enableSearch\n enableTableSort\n enablePagination\n enableRowClick\n onRowClick={handleRowClick}\n rowKey=\"competencyNumber\"\n paginationConfig={{\n itemLabel: 'competências',\n itemsPerPageOptions: [5],\n defaultItemsPerPage: 5,\n totalItems: competencies.length,\n }}\n searchPlaceholder=\"Buscar\"\n headerContent={\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Proficiência por competência\n </Text>\n }\n />\n\n {/* Competence Details Modal */}\n <EssayCompetenceDetailsModal\n api={api}\n isOpen={isModalOpen}\n onClose={handleCloseModal}\n competenceNumber={selectedCompetence?.number ?? null}\n competenceName={selectedCompetence?.name}\n period={period}\n schoolIds={schoolIds}\n schoolYearIds={schoolYearIds}\n classIds={classIds}\n />\n </>\n );\n}\n","import { useState, useCallback, useRef } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n EssayCompetenciesOverviewData,\n EssayCompetenciesOverviewParams,\n EssayCompetenciesOverviewApiResponse,\n UseEssayCompetenciesOverviewReturn,\n} from './types';\n\n/**\n * Hook for fetching essay competencies overview\n * Returns stats for all 5 ENEM competencies\n *\n * @param api - API client with post method\n *\n * @example\n * ```tsx\n * const { data, loading, error, fetchOverview, reset } = useEssayCompetenciesOverview(api);\n *\n * fetchOverview({\n * period: '1_MONTH',\n * });\n * ```\n */\nexport function useEssayCompetenciesOverview(\n api: BaseApiClient\n): UseEssayCompetenciesOverviewReturn {\n const [data, setData] = useState<EssayCompetenciesOverviewData | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const requestIdRef = useRef(0);\n\n const fetchOverview = useCallback(\n async (params: EssayCompetenciesOverviewParams) => {\n const currentRequestId = ++requestIdRef.current;\n\n try {\n setLoading(true);\n setError(null);\n\n const response = await api.post<EssayCompetenciesOverviewApiResponse>(\n '/performance/simulated/essays/competencies-overview',\n {\n period: params.period,\n schoolIds: params.schoolIds ?? [],\n schoolYearIds: params.schoolYearIds ?? [],\n classIds: params.classIds ?? [],\n }\n );\n\n // Ignore stale responses\n if (currentRequestId !== requestIdRef.current) return;\n\n setData(response.data.data);\n } catch (err) {\n // Ignore errors from stale requests\n if (currentRequestId !== requestIdRef.current) return;\n\n const errorMessage =\n err instanceof Error\n ? err.message\n : 'Não foi possível carregar o resumo das competências';\n setError(errorMessage);\n } finally {\n // Only update loading for current request\n if (currentRequestId === requestIdRef.current) {\n setLoading(false);\n }\n }\n },\n [api]\n );\n\n const reset = useCallback(() => {\n // Invalidate any in-flight requests\n requestIdRef.current++;\n setData(null);\n setLoading(false);\n setError(null);\n }, []);\n\n return { data, loading, error, fetchOverview, reset };\n}\n","import { useEffect, useCallback } from 'react';\nimport Modal from '../Modal/Modal';\nimport Text from '../Text/Text';\nimport Badge from '../Badge/Badge';\nimport { SkeletonRounded } from '../Skeleton/Skeleton';\nimport { TableProvider, type TableParams } from '../TableProvider';\nimport { useEssayCompetenceDetails } from './useEssayCompetenceDetails';\nimport {\n SIMULATED_PERFORMANCE_TAG_CONFIG,\n PERFORMANCE_TAG_TO_BADGE_ACTION,\n PerformanceBadgeAction,\n} from '../SimulatedStudentDetailsModal/types';\nimport { BadgeActionType } from '../../types/common';\nimport type {\n EssayCompetenceDetailsModalProps,\n EssayCompetenceStudentItem,\n} from './types';\n\n/**\n * Table columns configuration\n */\nconst TABLE_COLUMNS = [\n {\n key: 'name',\n label: 'Nome',\n className: 'py-3 px-4 text-start',\n },\n {\n key: 'school',\n label: 'Escola',\n className: 'py-3 px-4 text-start',\n },\n {\n key: 'schoolYear',\n label: 'Ano',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '80px',\n },\n {\n key: 'class',\n label: 'Turma',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '80px',\n },\n {\n key: 'averageScore',\n label: 'Média',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '100px',\n render: (_value: unknown, row: Record<string, unknown>) => {\n const student = row as unknown as EssayCompetenceStudentItem;\n return (\n <Text size=\"sm\" color=\"text-text-950\">\n {Math.round(student.averageScore)}/200\n </Text>\n );\n },\n },\n {\n key: 'performance',\n label: 'Proficiência',\n className: 'py-3 px-4 text-center',\n align: 'center' as const,\n width: '140px',\n render: (_value: unknown, row: Record<string, unknown>) => {\n const student = row as unknown as EssayCompetenceStudentItem;\n const badgeAction =\n PERFORMANCE_TAG_TO_BADGE_ACTION[student.performance] ??\n PerformanceBadgeAction.INFO;\n const tagConfig = SIMULATED_PERFORMANCE_TAG_CONFIG[student.performance];\n const label = tagConfig?.label ?? 'Desconhecido';\n return (\n <Badge variant=\"solid\" action={badgeAction} size=\"small\">\n {label}\n </Badge>\n );\n },\n },\n];\n\n/**\n * Modal for displaying essay competence performance details\n * Shows table of students with their scores for a specific competency\n */\nexport function EssayCompetenceDetailsModal({\n api,\n isOpen,\n onClose,\n competenceNumber,\n competenceName,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n}: EssayCompetenceDetailsModalProps) {\n const { data, loading, error, fetchDetails, reset } =\n useEssayCompetenceDetails(api);\n\n // Fetch details when modal opens\n useEffect(() => {\n if (isOpen && competenceNumber) {\n fetchDetails({\n competenceNumber,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n page: 1,\n limit: 10,\n });\n }\n }, [\n isOpen,\n competenceNumber,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n fetchDetails,\n ]);\n\n // Reset state when modal closes\n useEffect(() => {\n if (!isOpen) {\n reset();\n }\n }, [isOpen, reset]);\n\n // Handle table params change (pagination)\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n if (!competenceNumber) return;\n fetchDetails({\n competenceNumber,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n page: params.page,\n limit: params.limit,\n });\n },\n [competenceNumber, period, schoolIds, schoolYearIds, classIds, fetchDetails]\n );\n\n const fallbackCompetenceName = `Competência ${competenceNumber}`;\n const resolvedCompetenceName = competenceName || fallbackCompetenceName;\n const modalTitle = competenceNumber\n ? `C${competenceNumber} - ${resolvedCompetenceName}`\n : 'Detalhes da Competência';\n\n // Render loading state\n if (loading && !data) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex flex-col gap-4\">\n {/* Header skeleton */}\n <div className=\"flex items-center gap-2\">\n <SkeletonRounded className=\"h-4 w-32\" />\n </div>\n\n {/* Counters skeleton */}\n <div className=\"flex gap-3\">\n {[1, 2, 3].map((i) => (\n <SkeletonRounded key={i} className=\"flex-1 h-20 rounded-xl\" />\n ))}\n </div>\n\n {/* Table skeleton */}\n <div className=\"flex flex-col gap-2\">\n <SkeletonRounded className=\"h-10 rounded-lg\" />\n {[1, 2, 3, 4, 5].map((i) => (\n <SkeletonRounded key={i} className=\"h-12 rounded-lg\" />\n ))}\n </div>\n </div>\n </Modal>\n );\n }\n\n // Render error state\n if (error) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-error-500\">\n {error}\n </Text>\n </div>\n </Modal>\n );\n }\n\n // Render empty state\n if (!data) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-text-500\">\n Nenhum dado encontrado\n </Text>\n </div>\n </Modal>\n );\n }\n\n // Calculate combined counter values\n const aboveAverageCount =\n data.counters.highlight + data.counters.aboveAverage;\n const belowAverageCount = data.counters.belowAverage;\n const attentionCount = data.counters.attentionPoint;\n\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"xl\">\n <div className=\"flex flex-col gap-6\">\n {/* Subtitle */}\n <Text size=\"sm\" className=\"text-text-500\">\n Redação • {data.totalEssays}{' '}\n {data.totalEssays === 1 ? 'redação' : 'redações'} •{' '}\n {data.totalStudents} {data.totalStudents === 1 ? 'aluno' : 'alunos'}\n </Text>\n\n {/* Performance counters - 3 cards */}\n <div className=\"flex gap-3\">\n <CounterCard\n label=\"Acima da média\"\n count={aboveAverageCount}\n variant={BadgeActionType.SUCCESS}\n />\n <CounterCard\n label=\"Abaixo da média\"\n count={belowAverageCount}\n variant={BadgeActionType.WARNING}\n />\n <CounterCard\n label=\"Ponto de atenção\"\n count={attentionCount}\n variant={BadgeActionType.ERROR}\n />\n </div>\n\n {/* Students table */}\n <TableProvider\n data={data.students.data as unknown as Record<string, unknown>[]}\n headers={TABLE_COLUMNS}\n variant=\"borderless\"\n loading={loading}\n enablePagination\n rowKey=\"userInstitutionId\"\n paginationConfig={{\n itemLabel: 'estudantes',\n itemsPerPageOptions: [10, 20, 50],\n defaultItemsPerPage: 10,\n totalItems: data.students.total,\n totalPages:\n data.students.limit > 0\n ? Math.ceil(data.students.total / data.students.limit)\n : 1,\n }}\n onParamsChange={handleParamsChange}\n />\n </div>\n </Modal>\n );\n}\n\n/**\n * Counter card component\n */\nfunction CounterCard({\n label,\n count,\n variant,\n}: {\n readonly label: string;\n readonly count: number;\n readonly variant: BadgeActionType;\n}) {\n const bgColors: Record<BadgeActionType, string> = {\n [BadgeActionType.SUCCESS]: 'bg-success-50',\n [BadgeActionType.WARNING]: 'bg-warning-50',\n [BadgeActionType.ERROR]: 'bg-error-50',\n [BadgeActionType.INFO]: 'bg-info-50',\n };\n\n const textColors: Record<BadgeActionType, string> = {\n [BadgeActionType.SUCCESS]: 'text-success-700',\n [BadgeActionType.WARNING]: 'text-warning-700',\n [BadgeActionType.ERROR]: 'text-error-700',\n [BadgeActionType.INFO]: 'text-info-700',\n };\n\n return (\n <div\n className={`flex-1 flex flex-col items-center justify-center p-4 rounded-xl ${bgColors[variant]}`}\n >\n <Text size=\"2xl\" weight=\"bold\" className={textColors[variant]}>\n {count}\n </Text>\n <Text size=\"sm\" className=\"text-text-600 text-center\">\n {label}\n </Text>\n </div>\n );\n}\n","import { useState, useCallback } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n EssayCompetenceDetailsData,\n EssayCompetenceDetailsParams,\n EssayCompetenceDetailsApiResponse,\n UseEssayCompetenceDetailsReturn,\n} from './types';\n\n/**\n * Hook for fetching essay competence details\n * Returns competence info, class average, counters, and paginated students list\n *\n * @param api - API client with post method\n *\n * @example\n * ```tsx\n * const { data, loading, error, fetchDetails, reset } = useEssayCompetenceDetails(api);\n *\n * fetchDetails({\n * competenceNumber: 1,\n * period: '1_MONTH',\n * page: 1,\n * limit: 20,\n * });\n * ```\n */\nexport function useEssayCompetenceDetails(\n api: BaseApiClient\n): UseEssayCompetenceDetailsReturn {\n const [data, setData] = useState<EssayCompetenceDetailsData | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const fetchDetails = useCallback(\n async (params: EssayCompetenceDetailsParams) => {\n try {\n setLoading(true);\n setError(null);\n\n const response = await api.post<EssayCompetenceDetailsApiResponse>(\n '/performance/simulated/essays/competence-details',\n {\n competenceNumber: params.competenceNumber,\n period: params.period,\n schoolIds: params.schoolIds ?? [],\n schoolYearIds: params.schoolYearIds ?? [],\n classIds: params.classIds ?? [],\n page: params.page ?? 1,\n limit: params.limit ?? 20,\n orderBy: params.orderBy ?? 'averageScore',\n order: params.order ?? 'desc',\n }\n );\n\n setData(response.data.data);\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : 'Não foi possível carregar os detalhes da competência';\n setError(errorMessage);\n } finally {\n setLoading(false);\n }\n },\n [api]\n );\n\n const reset = useCallback(() => {\n setData(null);\n setLoading(false);\n setError(null);\n }, []);\n\n return { data, loading, error, fetchDetails, reset };\n}\n","import { useEffect } from 'react';\nimport Modal from '../Modal/Modal';\nimport Text from '../Text/Text';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport Badge from '../Badge/Badge';\nimport { SkeletonRounded } from '../Skeleton/Skeleton';\nimport { useEssayStudentDetails } from './useEssayStudentDetails';\nimport { formatPercentageRounded } from '../../utils/utils';\nimport { SIMULATED_PERFORMANCE_TAG_CONFIG } from '../SimulatedStudentDetailsModal/types';\nimport {\n type EssayStudentDetailsModalProps,\n type EssayCompetencyPerformance,\n} from './types';\nimport { PERFORMANCE_TAG_TO_BADGE_ACTION } from '../SimulatedStudentDetailsModal';\n\n/**\n * Modal for displaying essay student performance details\n * Shows 5 ENEM competencies with their average scores\n */\nexport function EssayStudentDetailsModal({\n api,\n isOpen,\n onClose,\n userInstitutionId,\n studentName,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n labels: customLabels,\n}: EssayStudentDetailsModalProps) {\n const DEFAULT_LABELS = {\n loading: 'Carregando...',\n noData: 'Nenhum dado encontrado',\n competencies: 'Competências',\n noCompetencies: 'Nenhuma competência encontrada',\n essays: 'redações',\n };\n\n const labels = { ...DEFAULT_LABELS, ...customLabels };\n const { data, loading, error, fetchDetails, reset } =\n useEssayStudentDetails(api);\n\n // Fetch details when modal opens\n useEffect(() => {\n if (isOpen && userInstitutionId) {\n fetchDetails({\n userInstitutionId,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n });\n }\n }, [\n isOpen,\n userInstitutionId,\n period,\n schoolIds,\n schoolYearIds,\n classIds,\n fetchDetails,\n ]);\n\n // Reset state when modal closes\n useEffect(() => {\n if (!isOpen) {\n reset();\n }\n }, [isOpen, reset]);\n\n const modalTitle = `Desempenho de ${studentName || 'Estudante'}`;\n\n // Render loading state\n if (loading) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"lg\">\n <div className=\"flex flex-col gap-4\">\n {/* Header skeleton */}\n <div className=\"flex items-center justify-between p-4 bg-background-50 rounded-xl\">\n <div className=\"flex flex-col gap-2\">\n <SkeletonRounded className=\"h-5 w-40\" />\n <SkeletonRounded className=\"h-4 w-32\" />\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <SkeletonRounded className=\"h-6 w-16\" />\n <SkeletonRounded className=\"h-5 w-24\" />\n </div>\n </div>\n\n {/* Competencies skeleton */}\n <div className=\"flex flex-col gap-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div\n key={i}\n className=\"flex items-center gap-4 p-4 bg-background border border-border-50 rounded-xl\"\n >\n <SkeletonRounded className=\"h-8 w-8 rounded-full\" />\n <div className=\"flex-1\">\n <SkeletonRounded className=\"h-4 w-3/4\" />\n </div>\n <SkeletonRounded className=\"h-3 w-32\" />\n <SkeletonRounded className=\"h-5 w-12\" />\n </div>\n ))}\n </div>\n </div>\n </Modal>\n );\n }\n\n // Render error state\n if (error) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"lg\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-error-500\">\n {error}\n </Text>\n </div>\n </Modal>\n );\n }\n\n // Render empty state\n if (!data) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"lg\">\n <div className=\"flex items-center justify-center py-8\">\n <Text size=\"sm\" className=\"text-text-500\">\n {labels.noData}\n </Text>\n </div>\n </Modal>\n );\n }\n\n return (\n <Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size=\"lg\">\n <div className=\"flex flex-col gap-4\">\n {/* Student header card */}\n <div className=\"flex items-center justify-between p-4 bg-background-50 rounded-xl\">\n <div className=\"flex flex-col gap-1\">\n <Text size=\"md\" weight=\"semibold\" className=\"text-text-950\">\n {data.student.name}\n </Text>\n <Text size=\"sm\" className=\"text-text-500\">\n {data.student.school} - {data.student.class}\n </Text>\n <Text size=\"xs\" className=\"text-text-400\">\n {data.essaysCount}{' '}\n {data.essaysCount === 1 ? 'redação' : labels.essays}\n </Text>\n </div>\n <div className=\"flex flex-col items-end gap-1\">\n <div className=\"flex items-baseline gap-1\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {Math.round(data.overallAverage)}\n </Text>\n <Text size=\"xs\" className=\"text-text-400\">\n / 1000\n </Text>\n </div>\n <Text size=\"sm\" className=\"text-text-500\">\n {formatPercentageRounded(data.overallPercentage)}\n </Text>\n <Badge\n variant=\"solid\"\n action={PERFORMANCE_TAG_TO_BADGE_ACTION[data.performance]}\n size=\"small\"\n >\n {SIMULATED_PERFORMANCE_TAG_CONFIG[data.performance].label}\n </Badge>\n </div>\n </div>\n\n {/* Section title */}\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-700\">\n {labels.competencies}\n </Text>\n\n {/* List of competencies */}\n <div className=\"flex flex-col gap-2 max-h-[400px] overflow-y-auto\">\n {data.competencies.length > 0 ? (\n data.competencies.map((competency) => (\n <CompetencyItem key={competency.number} competency={competency} />\n ))\n ) : (\n <Text size=\"sm\" className=\"text-text-500 text-center py-4\">\n {labels.noCompetencies}\n </Text>\n )}\n </div>\n </div>\n </Modal>\n );\n}\n\n/**\n * Competency item component\n */\nfunction CompetencyItem({\n competency,\n}: {\n readonly competency: EssayCompetencyPerformance;\n}) {\n return (\n <div className=\"flex items-center gap-4 p-4 bg-background border border-border-50 rounded-xl\">\n {/* Competency number badge */}\n <div className=\"w-8 h-8 flex items-center justify-center rounded-full bg-rose-100 flex-shrink-0\">\n <Text size=\"sm\" weight=\"bold\" className=\"text-rose-600\">\n {competency.number}\n </Text>\n </div>\n\n {/* Competency info */}\n <div className=\"flex-1 min-w-0\">\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-950 truncate\">\n {competency.name}\n </Text>\n </div>\n\n {/* Progress bar */}\n <div className=\"w-32 flex-shrink-0\">\n <ProgressBar\n value={competency.averagePercentage}\n variant=\"green\"\n size=\"small\"\n showPercentage\n />\n </div>\n\n {/* Score */}\n <div className=\"flex items-baseline gap-1 flex-shrink-0 min-w-[60px] justify-end\">\n <Text size=\"sm\" weight=\"semibold\" className=\"text-success-600\">\n {Math.round(competency.averageScore)}\n </Text>\n <Text size=\"xs\" className=\"text-text-500\">\n / 200\n </Text>\n </div>\n </div>\n );\n}\n","import { useState, useCallback } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type {\n EssayStudentDetailsData,\n EssayStudentDetailsParams,\n EssayStudentDetailsApiResponse,\n UseEssayStudentDetailsState,\n UseEssayStudentDetailsReturn,\n} from './types';\n\n/**\n * Initial state for the hook\n */\nconst initialState: UseEssayStudentDetailsState = {\n data: null,\n loading: false,\n error: null,\n};\n\n/**\n * Hook for fetching essay student details\n * Returns student info, overall average, performance classification, and 5 competencies\n *\n * @param api - API client with post method\n *\n * @example\n * ```tsx\n * const { data, loading, error, fetchDetails, reset } = useEssayStudentDetails(api);\n *\n * fetchDetails({\n * userInstitutionId: 'uuid',\n * period: '1_MONTH',\n * });\n * ```\n */\nexport function useEssayStudentDetails(\n api: BaseApiClient\n): UseEssayStudentDetailsReturn {\n const [data, setData] = useState<EssayStudentDetailsData | null>(\n initialState.data\n );\n const [loading, setLoading] = useState(initialState.loading);\n const [error, setError] = useState<string | null>(initialState.error);\n\n const fetchDetails = useCallback(\n async (params: EssayStudentDetailsParams) => {\n try {\n setLoading(true);\n setError(null);\n\n const response = await api.post<EssayStudentDetailsApiResponse>(\n '/performance/simulated/essays/student-details',\n {\n userInstitutionId: params.userInstitutionId,\n period: params.period,\n schoolIds: params.schoolIds ?? [],\n schoolYearIds: params.schoolYearIds ?? [],\n classIds: params.classIds ?? [],\n }\n );\n\n setData(response.data.data);\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : 'Não foi possível carregar os detalhes do estudante';\n setError(errorMessage);\n } finally {\n setLoading(false);\n }\n },\n [api]\n );\n\n const reset = useCallback(() => {\n setData(initialState.data);\n setLoading(initialState.loading);\n setError(initialState.error);\n }, []);\n\n return { data, loading, error, fetchDetails, reset };\n}\n","import { useState, useEffect, useMemo, useCallback, useRef } from 'react';\nimport {\n Text,\n Chips,\n CheckboxGroup,\n type CategoryConfig,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '../..';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseActivityFiltersData } from '../../hooks/useActivityFiltersData';\nimport { useQuestionFiltersStore } from '../../store/questionFiltersStore';\nimport { questionTypeLabels } from '../../types/questionTypes';\nimport type {\n ActivityFiltersData,\n Bank,\n BankYear,\n} from '../../types/activityFilters';\nimport {\n getSelectedIdsFromCategories,\n toggleArrayItem,\n areFiltersEqual,\n} from '../../utils/activityFilters';\nimport {\n SubjectsFilter,\n KnowledgeStructureFilter,\n FilterActions,\n} from './components';\nimport {\n useInitialFiltersLoader,\n useKnowledgeStructureInitialFilters,\n} from './utils';\n\nconst questionTypesFallback = [\n QUESTION_TYPE.ALTERNATIVA,\n QUESTION_TYPE.VERDADEIRO_FALSO,\n QUESTION_TYPE.DISSERTATIVA,\n QUESTION_TYPE.IMAGEM,\n QUESTION_TYPE.MULTIPLA_ESCOLHA,\n QUESTION_TYPE.RELACIONAR,\n QUESTION_TYPE.PREENCHER_LACUNAS,\n];\n\n/**\n * Type guard to check if an item has a valid bankId\n * @param item - The item to validate\n * @param bankIds - Array of valid bank IDs to check against\n * @returns True if item has valid id and bankId that matches bankIds\n */\nconst isValidBankYearItem = (\n item: unknown,\n bankIds: string[]\n): item is { id: string; bankId: string } => {\n return (\n !!item &&\n typeof (item as { id?: unknown }).id === 'string' &&\n typeof (item as { bankId?: unknown }).bankId === 'string' &&\n bankIds.includes((item as { bankId: string }).bankId)\n );\n};\n\n/**\n * Derives year IDs from bank IDs when explicit year IDs are not provided\n * @param yearItens - Array of year items to filter\n * @param bankIds - Array of bank IDs to filter by\n * @param explicitYearIds - Explicitly provided year IDs (takes precedence)\n * @returns Array of year IDs\n */\nconst deriveYearIdsFromBankIds = (\n yearItens: unknown[],\n bankIds: string[],\n explicitYearIds: string[]\n): string[] => {\n if (explicitYearIds.length > 0) {\n return explicitYearIds;\n }\n return yearItens\n .filter((item) => isValidBankYearItem(item, bankIds))\n .map((item) => item.id);\n};\n\n/**\n * Checks if bank matches exist in the provided categories\n * @param bankIds - Array of bank IDs to check\n * @param bankCategories - Array of category configurations\n * @returns True if bankIds is empty or matches are found\n */\nconst hasBankMatches = (\n bankIds: string[],\n bankCategories: CategoryConfig[]\n): boolean => {\n if (bankIds.length === 0) {\n return true;\n }\n return bankCategories.some(\n (category) =>\n category.key === 'banca' &&\n (category.itens ?? []).some((item) => bankIds.includes(item.id))\n );\n};\n\n/**\n * Checks if year matches exist in the provided categories\n * @param derivedYearIds - Array of year IDs to check\n * @param bankCategories - Array of category configurations\n * @returns True if derivedYearIds is empty or matches are found\n */\nconst hasYearMatches = (\n derivedYearIds: string[],\n bankCategories: CategoryConfig[]\n): boolean => {\n if (derivedYearIds.length === 0) {\n return true;\n }\n return bankCategories.some(\n (category) =>\n category.key === 'ano' &&\n (category.itens ?? []).some((item) => derivedYearIds.includes(item.id))\n );\n};\n\n/**\n * Updates a bank category with selected IDs based on provided bank IDs\n * @param category - The category configuration to update\n * @param bankIds - Array of bank IDs to select\n * @returns Updated category with selectedIds\n */\nconst updateBankCategory = (\n category: CategoryConfig,\n bankIds: string[]\n): CategoryConfig => {\n const itens = category.itens ?? [];\n const selectedIds = itens\n .filter((item) => bankIds.includes(item.id))\n .map((item) => item.id);\n return { ...category, selectedIds };\n};\n\n/**\n * Updates a year category with selected IDs based on provided year IDs\n * @param category - The category configuration to update\n * @param derivedYearIds - Array of year IDs to select\n * @returns Updated category with selectedIds\n */\nconst updateYearCategory = (\n category: CategoryConfig,\n derivedYearIds: string[]\n): CategoryConfig => {\n const itens = category.itens ?? [];\n const selectedIds = itens\n .filter((item) => derivedYearIds.includes(item.id))\n .map((item) => item.id);\n return { ...category, selectedIds };\n};\n\n/**\n * Updates bank categories with initial filter selections\n * @param prevCategories - Previous category configurations\n * @param bankIds - Array of bank IDs to select\n * @param derivedYearIds - Array of year IDs to select\n * @returns Updated array of category configurations\n */\nconst updateBankCategoriesWithInitialFilters = (\n prevCategories: CategoryConfig[],\n bankIds: string[],\n derivedYearIds: string[]\n): CategoryConfig[] => {\n return prevCategories.map((category) => {\n if (category.key === 'banca') {\n return updateBankCategory(category, bankIds);\n }\n if (category.key === 'ano') {\n return updateYearCategory(category, derivedYearIds);\n }\n return category;\n });\n};\ninterface QuestionTypeFilterProps {\n selectedTypes: QUESTION_TYPE[];\n onToggleType: (type: QUESTION_TYPE) => void;\n allowedQuestionTypes?: QUESTION_TYPE[];\n}\n\n/**\n * QuestionTypeFilter component for selecting question types\n * @param props - Component props\n * @returns JSX element\n */\nconst QuestionTypeFilter = ({\n selectedTypes,\n onToggleType,\n allowedQuestionTypes,\n}: QuestionTypeFilterProps) => {\n const availableQuestionTypes = allowedQuestionTypes || questionTypesFallback;\n\n return (\n <div>\n <Text size=\"sm\" weight=\"bold\" className=\"mb-3 block\">\n Tipo de questão\n </Text>\n <div className=\"grid grid-cols-2 gap-2\">\n {availableQuestionTypes.map((questionType) => (\n <Chips\n key={questionType}\n selected={selectedTypes.includes(questionType)}\n onClick={() => onToggleType(questionType)}\n >\n {questionTypeLabels[questionType]}\n </Chips>\n ))}\n </div>\n </div>\n );\n};\n\ninterface BanksAndYearsFilterProps {\n banks: Bank[];\n bankYears: BankYear[];\n bankCategories: CategoryConfig[];\n onBankCategoriesChange: (updatedCategories: CategoryConfig[]) => void;\n loading?: boolean;\n error?: string | null;\n}\n\n/**\n * BanksAndYearsFilter component for selecting banks and years\n * @param props - Component props\n * @returns JSX element\n */\nconst BanksAndYearsFilter = ({\n banks,\n bankYears,\n bankCategories,\n onBankCategoriesChange,\n loading = false,\n error = null,\n}: BanksAndYearsFilterProps) => {\n if (loading) {\n return (\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando bancas...\n </Text>\n );\n }\n\n if (error) {\n return (\n <Text size=\"sm\" className=\"text-text-600\">\n {error}\n </Text>\n );\n }\n\n if (banks.length === 0 && bankYears.length === 0) {\n return (\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhuma banca encontrada\n </Text>\n );\n }\n\n if (bankCategories.length > 0) {\n return (\n <CheckboxGroup\n categories={bankCategories}\n onCategoriesChange={onBankCategoriesChange}\n compactSingleItem={true}\n showSingleItem={true}\n />\n );\n }\n\n return null;\n};\nexport interface ActivityFiltersProps {\n apiClient: BaseApiClient;\n onFiltersChange: (filters: ActivityFiltersData) => void;\n variant?: 'default' | 'popover';\n institutionId?: string | null;\n initialFilters?: ActivityFiltersData | null;\n allowedQuestionTypes?: QUESTION_TYPE[];\n onClearFilters?: () => void;\n onApplyFilters?: () => void;\n}\n\n/**\n * ActivityFilters component for filtering questions\n * Manages question types, banks, subjects, and knowledge structure selections\n */\nexport const ActivityFilters = ({\n apiClient,\n onFiltersChange,\n variant = 'default',\n institutionId = null,\n initialFilters = null,\n allowedQuestionTypes,\n onClearFilters,\n onApplyFilters,\n}: ActivityFiltersProps) => {\n const useActivityFiltersData = createUseActivityFiltersData(apiClient);\n\n const [selectedQuestionTypes, setSelectedQuestionTypes] = useState<\n QUESTION_TYPE[]\n >([]);\n const [selectedSubjectIds, setSelectedSubjectIds] = useState<string[]>([]);\n\n const {\n banks,\n bankYears,\n loadingBanks,\n banksError,\n knowledgeAreas,\n loadingSubjects,\n subjectsError,\n knowledgeStructure,\n knowledgeCategories,\n handleCategoriesChange,\n loadBanks,\n loadKnowledgeAreas,\n loadQuestionTypes,\n loadTopics,\n loadSubtopics,\n loadContents,\n questionTypes,\n loadingQuestionTypes,\n questionTypesError,\n } = useActivityFiltersData({\n // Tema/subtema/assunto only make sense for a single subject, so only load\n // the knowledge structure when exactly one subject is selected.\n selectedSubjects: selectedSubjectIds.length === 1 ? selectedSubjectIds : [],\n institutionId,\n });\n\n const prevAllowedQuestionTypesRef = useRef<string | null>(null);\n const hasAppliedBasicInitialFiltersRef = useRef(false);\n const hasAppliedBankInitialFiltersRef = useRef(false);\n const bankCategoriesRef = useRef<CategoryConfig[]>([]);\n\n // Use shared hook for loading topics/subtopics/contents\n useInitialFiltersLoader({\n initialFilters,\n loadTopics,\n loadSubtopics,\n loadContents,\n });\n\n // Use shared hook for knowledge structure initial filters\n useKnowledgeStructureInitialFilters({\n initialFilters,\n knowledgeCategories,\n handleCategoriesChange,\n });\n\n useEffect(() => {\n if (!allowedQuestionTypes || allowedQuestionTypes.length === 0) {\n prevAllowedQuestionTypesRef.current = null;\n return;\n }\n\n const currentKey = allowedQuestionTypes\n .slice()\n .sort((a, b) => {\n return a === b\n ? 0\n : allowedQuestionTypes.indexOf(a) - allowedQuestionTypes.indexOf(b);\n })\n .join(',');\n const prevKey = prevAllowedQuestionTypesRef.current;\n\n if (currentKey === prevKey) {\n return;\n }\n\n prevAllowedQuestionTypesRef.current = currentKey;\n\n setSelectedQuestionTypes((prev) => {\n const filtered = prev.filter((type) =>\n allowedQuestionTypes.includes(type)\n );\n if (filtered.length !== prev.length) {\n return filtered;\n }\n const prevSet = new Set(prev);\n const filteredSet = new Set(filtered);\n if (prevSet.size !== filteredSet.size) {\n return filtered;\n }\n for (const item of prevSet) {\n if (!filteredSet.has(item)) {\n return filtered;\n }\n }\n return prev;\n });\n }, [allowedQuestionTypes]);\n\n const [bankCategories, setBankCategories] = useState<CategoryConfig[]>([]);\n\n useEffect(() => {\n bankCategoriesRef.current = bankCategories;\n }, [bankCategories]);\n\n // Whether every available subject is currently selected — drives the\n // \"Todas as matérias\" card's checked state.\n const allSubjectsSelected =\n knowledgeAreas.length > 0 &&\n knowledgeAreas.every((area) => selectedSubjectIds.includes(area.id));\n\n const toggleQuestionType = (questionType: QUESTION_TYPE) => {\n setSelectedQuestionTypes((prev) => toggleArrayItem(prev, questionType));\n };\n\n const handleToggleSubject = (subjectId: string) => {\n setSelectedSubjectIds((prev) => toggleArrayItem(prev, subjectId));\n };\n\n const handleToggleAllSubjects = () => {\n setSelectedSubjectIds(\n allSubjectsSelected ? [] : knowledgeAreas.map((area) => area.id)\n );\n };\n\n const handleBankCategoriesChange = (updatedCategories: CategoryConfig[]) => {\n setBankCategories(updatedCategories);\n };\n\n useEffect(() => {\n setBankCategories((prevCategories) => {\n const bankCategory: CategoryConfig = {\n key: 'banca',\n label: 'Banca',\n itens: banks.map((bank) => ({\n id: bank.id,\n name: bank.name || bank.examInstitution,\n })),\n selectedIds:\n prevCategories.find((c) => c.key === 'banca')?.selectedIds || [],\n };\n\n const yearCategory: CategoryConfig = {\n key: 'ano',\n label: 'Ano',\n dependsOn: ['banca'],\n itens: bankYears.map((year) => ({\n id: year.id,\n name: year.name,\n bankId: year.bankId,\n })),\n filteredBy: [{ key: 'banca', internalField: 'bankId' }],\n selectedIds:\n prevCategories.find((c) => c.key === 'ano')?.selectedIds || [],\n };\n\n return [bankCategory, yearCategory];\n });\n }, [banks, bankYears]);\n\n useEffect(() => {\n hasAppliedBasicInitialFiltersRef.current = false;\n hasAppliedBankInitialFiltersRef.current = false;\n }, [initialFilters]);\n\n useEffect(() => {\n if (!initialFilters || hasAppliedBasicInitialFiltersRef.current) {\n return;\n }\n\n if (initialFilters.types && initialFilters.types.length > 0) {\n setSelectedQuestionTypes(initialFilters.types);\n }\n\n if (initialFilters.subjectIds && initialFilters.subjectIds.length > 0) {\n setSelectedSubjectIds(initialFilters.subjectIds);\n }\n\n hasAppliedBasicInitialFiltersRef.current = true;\n }, [initialFilters]);\n\n useEffect(() => {\n if (\n !initialFilters ||\n hasAppliedBankInitialFiltersRef.current ||\n bankCategoriesRef.current.length === 0\n ) {\n return;\n }\n\n const bankIds = initialFilters.bankIds || [];\n const yearIds = initialFilters.yearIds || [];\n\n if (bankIds.length === 0 && yearIds.length === 0) {\n hasAppliedBankInitialFiltersRef.current = true;\n return;\n }\n\n const currentBankCategories = bankCategoriesRef.current;\n const yearCategory = currentBankCategories.find(\n (category) => category.key === 'ano'\n );\n const yearItens = Array.isArray(yearCategory?.itens)\n ? yearCategory.itens\n : [];\n\n const derivedYearIds = deriveYearIdsFromBankIds(\n yearItens,\n bankIds,\n yearIds\n );\n\n if (\n !hasBankMatches(bankIds, currentBankCategories) ||\n !hasYearMatches(derivedYearIds, currentBankCategories)\n ) {\n return;\n }\n\n setBankCategories((prevCategories) =>\n updateBankCategoriesWithInitialFilters(\n prevCategories,\n bankIds,\n derivedYearIds\n )\n );\n\n hasAppliedBankInitialFiltersRef.current = true;\n }, [initialFilters, bankCategories]);\n\n useEffect(() => {\n if (loadBanks) {\n loadBanks();\n }\n if (loadKnowledgeAreas) {\n loadKnowledgeAreas();\n }\n if (loadQuestionTypes) {\n loadQuestionTypes();\n }\n }, [loadBanks, loadKnowledgeAreas, loadQuestionTypes, institutionId]);\n\n const availableQuestionTypes = useMemo(() => {\n const source =\n questionTypes && questionTypes.length > 0\n ? questionTypes\n : questionTypesFallback;\n\n if (!allowedQuestionTypes || allowedQuestionTypes.length === 0) {\n return source;\n }\n\n return source.filter((type) => allowedQuestionTypes.includes(type));\n }, [questionTypes, allowedQuestionTypes]);\n\n const getSelectedKnowledgeIds = useCallback(() => {\n return getSelectedIdsFromCategories(knowledgeCategories, {\n topicIds: 'tema',\n subtopicIds: 'subtema',\n contentIds: 'assunto',\n });\n }, [knowledgeCategories]);\n\n const getSelectedBankIds = useCallback(() => {\n return getSelectedIdsFromCategories(bankCategories, {\n bankIds: 'banca',\n yearIds: 'ano',\n });\n }, [bankCategories]);\n\n const onFiltersChangeRef = useRef(onFiltersChange);\n useEffect(() => {\n onFiltersChangeRef.current = onFiltersChange;\n }, [onFiltersChange]);\n\n const prevFiltersRef = useRef<ActivityFiltersData | null>(null);\n\n useEffect(() => {\n const knowledgeIds = getSelectedKnowledgeIds();\n const bankIds = getSelectedBankIds();\n // Tema/subtema/assunto only apply to a single subject; with 0 or 2+ (or\n // \"Todas\") the knowledge selection is irrelevant and must not be sent.\n const isSingleSubject = selectedSubjectIds.length === 1;\n const filters: ActivityFiltersData = {\n types: selectedQuestionTypes,\n bankIds: bankIds.bankIds || [],\n yearIds: bankIds.yearIds || [],\n subjectIds: selectedSubjectIds,\n topicIds: isSingleSubject ? knowledgeIds.topicIds : [],\n subtopicIds: isSingleSubject ? knowledgeIds.subtopicIds : [],\n contentIds: isSingleSubject ? knowledgeIds.contentIds : [],\n };\n\n if (!areFiltersEqual(prevFiltersRef.current, filters)) {\n prevFiltersRef.current = filters;\n onFiltersChangeRef.current(filters);\n }\n }, [\n selectedQuestionTypes,\n selectedSubjectIds,\n knowledgeCategories,\n bankCategories,\n getSelectedKnowledgeIds,\n getSelectedBankIds,\n ]);\n\n const containerClassName =\n variant === 'popover'\n ? 'w-full bg-background'\n : 'w-[400px] flex-shrink-0 p-4 bg-background';\n\n const contentClassName = variant === 'popover' ? 'p-4' : '';\n\n return (\n <div className={containerClassName}>\n {variant === 'default' && (\n <section className=\"flex flex-row items-center gap-2 text-text-950 mb-4\">\n <Text size=\"lg\" weight=\"bold\">\n Filtro de questões\n </Text>\n </section>\n )}\n\n <div className={contentClassName}>\n <section className=\"flex flex-col gap-4\">\n <QuestionTypeFilter\n selectedTypes={selectedQuestionTypes}\n onToggleType={toggleQuestionType}\n allowedQuestionTypes={availableQuestionTypes}\n />\n {loadingQuestionTypes && (\n <Text\n size=\"sm\"\n className=\"text-text-600\"\n data-testid=\"question-types-loading\"\n >\n Carregando tipos de questão...\n </Text>\n )}\n {questionTypesError && (\n <Text\n size=\"sm\"\n className=\"text-error-500\"\n data-testid=\"question-types-error\"\n >\n {questionTypesError}\n </Text>\n )}\n\n <div>\n <Text size=\"sm\" weight=\"bold\" className=\"mb-3 block\">\n Banca de vestibular\n </Text>\n <BanksAndYearsFilter\n banks={banks}\n bankYears={bankYears}\n bankCategories={bankCategories}\n onBankCategoriesChange={handleBankCategoriesChange}\n loading={loadingBanks}\n error={banksError}\n />\n </div>\n\n <div>\n <div className=\"flex flex-row justify-between items-center mb-3\">\n <Text size=\"sm\" weight=\"bold\">\n Matéria\n </Text>\n {selectedSubjectIds.length > 0 && (\n <Button\n type=\"button\"\n variant=\"link\"\n onClick={() => setSelectedSubjectIds([])}\n size=\"small\"\n >\n Limpar\n </Button>\n )}\n </div>\n <SubjectsFilter\n multiple\n knowledgeAreas={knowledgeAreas}\n selectedSubjectIds={selectedSubjectIds}\n onToggleSubject={handleToggleSubject}\n showAllSubjectsOption\n allSubjectsSelected={allSubjectsSelected}\n onToggleAllSubjects={handleToggleAllSubjects}\n loading={loadingSubjects}\n error={subjectsError}\n />\n </div>\n\n {selectedSubjectIds.length === 1 && (\n <KnowledgeStructureFilter\n knowledgeStructure={knowledgeStructure}\n knowledgeCategories={knowledgeCategories}\n handleCategoriesChange={handleCategoriesChange}\n />\n )}\n\n {variant === 'popover' && (\n <FilterActions\n onClearFilters={onClearFilters}\n onApplyFilters={onApplyFilters}\n />\n )}\n </section>\n </div>\n </div>\n );\n};\n\nexport interface ActivityFiltersPopoverProps extends Omit<\n ActivityFiltersProps,\n 'variant' | 'onFiltersChange'\n> {\n onFiltersChange: (filters: ActivityFiltersData) => void;\n triggerLabel?: string;\n}\n\n/**\n * ActivityFiltersPopover component\n * Wraps ActivityFilters in a Popover/DropdownMenu triggered by a Button\n */\nexport const ActivityFiltersPopover = ({\n onFiltersChange,\n triggerLabel = 'Filtro de questões',\n initialFilters,\n ...activityFiltersProps\n}: ActivityFiltersPopoverProps) => {\n const [open, setOpen] = useState(false);\n const appliedFilters = useQuestionFiltersStore(\n (state) => state.appliedFilters\n );\n\n // Use appliedFilters from store if available, otherwise fall back to initialFilters\n const effectiveInitialFilters = appliedFilters ?? initialFilters;\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger>\n <Button variant=\"outline\">{triggerLabel}</Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[90vw] max-w-[400px] max-h-[calc(100vh-8rem)] overflow-y-auto p-0\"\n align=\"start\"\n >\n <ActivityFilters\n onFiltersChange={onFiltersChange}\n variant=\"popover\"\n {...activityFiltersProps}\n initialFilters={effectiveInitialFilters}\n />\n </DropdownMenuContent>\n </DropdownMenu>\n );\n};\n","import { create } from 'zustand';\nimport type { ActivityFiltersData } from '../types/activityFilters';\nimport type { Question, Pagination } from '../types/questions';\n\nexport interface QuestionFiltersState {\n // Filter states\n draftFilters: ActivityFiltersData | null;\n appliedFilters: ActivityFiltersData | null;\n setDraftFilters: (filters: ActivityFiltersData | null) => void;\n applyFilters: () => void;\n clearFilters: () => void;\n\n // Questions cache state\n cachedQuestions: Question[];\n cachedPagination: Pagination | null;\n cachedFilters: ActivityFiltersData | null;\n setCachedQuestions: (\n questions: Question[],\n pagination: Pagination | null,\n filters: ActivityFiltersData | null\n ) => void;\n clearCachedQuestions: () => void;\n}\n\n/**\n * Zustand store to manage question filters with draft vs applied separation\n * and cache for questions list to avoid unnecessary refetches\n */\nexport const useQuestionFiltersStore = create<QuestionFiltersState>((set) => ({\n // Filter states\n draftFilters: null,\n appliedFilters: null,\n setDraftFilters: (filters) => {\n set({ draftFilters: filters });\n },\n applyFilters: () => {\n set((state) => ({\n appliedFilters: state.draftFilters,\n }));\n },\n clearFilters: () => {\n set({\n draftFilters: null,\n appliedFilters: null,\n // Clear cached questions when filters are cleared\n cachedQuestions: [],\n cachedPagination: null,\n cachedFilters: null,\n });\n },\n\n // Questions cache state\n cachedQuestions: [],\n cachedPagination: null,\n cachedFilters: null,\n setCachedQuestions: (questions, pagination, filters) => {\n set({\n cachedQuestions: questions,\n cachedPagination: pagination,\n cachedFilters: filters,\n });\n },\n clearCachedQuestions: () => {\n set({\n cachedQuestions: [],\n cachedPagination: null,\n cachedFilters: null,\n });\n },\n}));\n","import { HtmlHTMLAttributes, useMemo, useId, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\nimport { parseContent } from '../../utils/parseContent';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport Badge from '../Badge/Badge';\nimport Text from '../Text/Text';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport { QuizVariant } from '../Quiz/Quiz.types';\n\nexport interface FillInBlanksOption {\n id: string;\n option: string;\n}\n\nexport interface FillInBlanksProps extends HtmlHTMLAttributes<HTMLDivElement> {\n /** The text content with {uuid} placeholders */\n content: string;\n /** Available options to fill in the blanks */\n options: FillInBlanksOption[];\n /** Current answers as a map of placeholderId -> selectedOptionId */\n answers?: Record<string, string>;\n /** Callback when an answer changes (only for interactive mode) */\n onAnswerChange?: (placeholderId: string, optionId: string) => void;\n /** Display mode */\n mode?: 'interactive' | 'readonly' | 'result';\n /** Whether component is disabled (only for interactive mode) */\n disabled?: boolean;\n}\n\n/**\n * FillInBlanks component for displaying fill-in-the-blanks questions\n *\n * @example\n * // Readonly mode - shows correct answers with green underlined text\n * <FillInBlanks\n * content=\"A {uuid-1} é uma {uuid-2}.\"\n * options={[{ id: 'uuid-1', option: 'Terra' }, { id: 'uuid-2', option: 'planeta' }]}\n * mode=\"readonly\"\n * />\n *\n * @example\n * // Interactive mode - shows select dropdowns\n * <FillInBlanks\n * content=\"A {uuid-1} é uma {uuid-2}.\"\n * options={[{ id: 'uuid-1', option: 'Terra' }, { id: 'uuid-2', option: 'planeta' }]}\n * mode=\"interactive\"\n * answers={answers}\n * onAnswerChange={(placeholderId, optionId) => setAnswers({...answers, [placeholderId]: optionId})}\n * />\n *\n * @example\n * // Result mode - shows badges with correct/incorrect indicators\n * <FillInBlanks\n * content=\"A {uuid-1} é uma {uuid-2}.\"\n * options={[{ id: 'uuid-1', option: 'Terra' }, { id: 'uuid-2', option: 'planeta' }]}\n * mode=\"result\"\n * answers={userAnswers}\n * />\n */\nconst FillInBlanks = ({\n content,\n options,\n answers = {},\n onAnswerChange,\n mode = QuizVariant.INTERACTIVE,\n disabled = false,\n className,\n ...props\n}: FillInBlanksProps) => {\n const baseId = useId();\n\n // Create a map of option IDs to their text\n const optionsMap = useMemo(() => {\n return new Map(options.map((opt) => [opt.id, opt.option]));\n }, [options]);\n\n // Parse the content into parts\n const parsedParts = useMemo(() => parseContent(content), [content]);\n\n // Get option text by ID\n const getOptionText = (optionId: string): string => {\n return optionsMap.get(optionId) || '';\n };\n\n // Check if an answer is correct (the placeholderId IS the correct option ID)\n const isAnswerCorrect = (placeholderId: string): boolean => {\n const selectedOptionId = answers[placeholderId];\n return selectedOptionId === placeholderId;\n };\n\n // Render select for interactive mode\n const renderSelect = (placeholderId: string, index: number) => {\n const selectedOptionId = answers[placeholderId];\n\n return (\n <span\n key={`${baseId}-select-${index}`}\n className={cn(\n 'inline-block align-middle mx-1',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <Select\n value={selectedOptionId || undefined}\n onValueChange={(value) => onAnswerChange?.(placeholderId, value)}\n >\n <SelectTrigger className=\"w-auto min-w-[120px] h-7 px-2 bg-background border-gray-300\">\n <SelectValue placeholder=\"Selecione\" />\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.id} value={option.id}>\n {option.option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </span>\n );\n };\n\n // Render correct answer for readonly mode\n const renderCorrectAnswer = (placeholderId: string, index: number) => {\n const correctText = getOptionText(placeholderId);\n\n return (\n <span\n key={`${baseId}-answer-${index}`}\n className=\"text-success-600 underline font-medium\"\n >\n {correctText}\n </span>\n );\n };\n\n // Render result badge\n const renderResultBadge = (placeholderId: string, index: number) => {\n const selectedOptionId = answers[placeholderId];\n const selectedText = getOptionText(selectedOptionId);\n const isCorrect = isAnswerCorrect(placeholderId);\n\n if (!selectedOptionId) {\n return (\n <span\n key={`${baseId}-result-${index}`}\n className=\"inline-block align-middle mx-1\"\n >\n <Badge\n variant=\"solid\"\n action=\"error\"\n iconRight={<XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n Não respondido\n </Badge>\n </span>\n );\n }\n\n return (\n <span\n key={`${baseId}-result-${index}`}\n className=\"inline-block align-middle mx-1\"\n >\n <Badge\n variant=\"solid\"\n action={isCorrect ? 'success' : 'error'}\n iconRight={isCorrect ? <CheckCircleIcon /> : <XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n <span className=\"text-text-900\">{selectedText}</span>\n </Badge>\n </span>\n );\n };\n\n // Render the appropriate element based on mode\n const renderPlaceholder = (\n placeholderId: string,\n index: number\n ): ReactNode => {\n switch (mode) {\n case QuizVariant.INTERACTIVE:\n return renderSelect(placeholderId, index);\n case QuizVariant.READONLY:\n return renderCorrectAnswer(placeholderId, index);\n case QuizVariant.RESULT:\n return renderResultBadge(placeholderId, index);\n default:\n return renderCorrectAnswer(placeholderId, index);\n }\n };\n\n // Build the rendered content\n const renderedContent = useMemo(() => {\n return parsedParts.map((part, index) => {\n if (part.type === 'text') {\n return <span key={`${baseId}-text-${index}`}>{part.value}</span>;\n }\n return renderPlaceholder(part.value, index);\n });\n }, [parsedParts, baseId, mode, answers, options, disabled]);\n\n return (\n <div className={cn('leading-relaxed', className)} {...props}>\n <Text size=\"sm\" className=\"text-text-900\">\n {renderedContent}\n </Text>\n </div>\n );\n};\n\nexport { FillInBlanks };\n","import { stripHtmlTags } from './stringUtils';\n\nexport interface ParsedContentPart {\n type: 'text' | 'placeholder';\n value: string;\n}\n\n/**\n * Parse content and extract text parts and placeholder IDs\n * Extracts {uuid} placeholders from text content, stripping HTML tags first\n *\n * @param content - String that may contain HTML and {uuid} placeholders\n * @returns Array of parsed parts with type ('text' or 'placeholder') and value\n *\n * @example\n * parseContent('Hello {uuid-1} world {uuid-2}')\n * // Returns: [\n * // { type: 'text', value: 'Hello ' },\n * // { type: 'placeholder', value: 'uuid-1' },\n * // { type: 'text', value: ' world ' },\n * // { type: 'placeholder', value: 'uuid-2' }\n * // ]\n */\nexport function parseContent(content: string): ParsedContentPart[] {\n const parts: ParsedContentPart[] = [];\n const regex = /\\{([a-zA-Z0-9-]+)\\}/g;\n let lastIndex = 0;\n let match;\n\n // Strip HTML tags for cleaner parsing\n const cleanContent = stripHtmlTags(content);\n\n while ((match = regex.exec(cleanContent)) !== null) {\n // Add text before the placeholder\n if (match.index > lastIndex) {\n parts.push({\n type: 'text',\n value: cleanContent.slice(lastIndex, match.index),\n });\n }\n\n // Add the placeholder\n parts.push({\n type: 'placeholder',\n value: match[1],\n });\n\n lastIndex = match.index + match[0].length;\n }\n\n // Add remaining text\n if (lastIndex < cleanContent.length) {\n parts.push({\n type: 'text',\n value: cleanContent.slice(lastIndex),\n });\n }\n\n return parts;\n}\n","import { HtmlHTMLAttributes, useMemo, useId, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport { ArrowRightIcon } from '@phosphor-icons/react/dist/csr/ArrowRight';\nimport Badge from '../Badge/Badge';\nimport Text from '../Text/Text';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport { QuizVariant } from '../Quiz/Quiz.types';\n\nexport interface ConnectDotsOption {\n id: string;\n option: string;\n correctValue: string;\n}\n\nexport interface ConnectDotsProps extends HtmlHTMLAttributes<HTMLDivElement> {\n /** Available options with their correct values */\n options: ConnectDotsOption[];\n /** Current answers as a map of optionId -> selectedValue */\n answers?: Record<string, string>;\n /** Callback when an answer changes (only for interactive mode) */\n onAnswerChange?: (optionId: string, selectedValue: string) => void;\n /** Display mode */\n mode?: 'interactive' | 'readonly' | 'result';\n /** Whether component is disabled (only for interactive mode) */\n disabled?: boolean;\n}\n\n/**\n * ConnectDots component for displaying matching/connect-the-dots questions\n *\n * @example\n * // Readonly mode - shows correct matches with arrows\n * <ConnectDots\n * options={[\n * { id: 'opt-1', option: 'I. Event A', correctValue: '2001' },\n * { id: 'opt-2', option: 'II. Event B', correctValue: '1989' },\n * ]}\n * mode=\"readonly\"\n * />\n *\n * @example\n * // Interactive mode - shows select dropdowns\n * <ConnectDots\n * options={[...]}\n * mode=\"interactive\"\n * answers={answers}\n * onAnswerChange={(optionId, value) => setAnswers({...answers, [optionId]: value})}\n * />\n *\n * @example\n * // Result mode - shows badges with correct/incorrect indicators\n * <ConnectDots\n * options={[...]}\n * mode=\"result\"\n * answers={userAnswers}\n * />\n */\nconst ConnectDots = ({\n options,\n answers = {},\n onAnswerChange,\n mode = QuizVariant.INTERACTIVE,\n disabled = false,\n className,\n ...props\n}: ConnectDotsProps) => {\n const baseId = useId();\n\n // Get all unique correct values for the dropdown options\n const availableValues = useMemo(() => {\n const values = options.map((opt) => opt.correctValue);\n // Remove duplicates and sort alphabetically using localeCompare\n return [...new Set(values)].sort((a, b) => a.localeCompare(b));\n }, [options]);\n\n // Check if an answer is correct\n const isAnswerCorrect = (optionId: string): boolean => {\n const option = options.find((opt) => opt.id === optionId);\n if (!option) return false;\n const selectedValue = answers[optionId];\n return selectedValue === option.correctValue;\n };\n\n // Render select for interactive mode\n const renderInteractiveItem = (option: ConnectDotsOption, index: number) => {\n const selectedValue = answers[option.id];\n\n return (\n <div\n key={`${baseId}-item-${index}`}\n className={cn(\n 'flex flex-row items-center gap-3 p-3 rounded-md border border-border-200 bg-background',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <div className=\"flex-1\">\n <HtmlMathRenderer\n content={option.option}\n className=\"text-text-900 text-sm\"\n />\n </div>\n\n <ArrowRightIcon size={20} className=\"text-text-400 flex-shrink-0\" />\n\n <div className=\"w-[150px] flex-shrink-0\">\n <Select\n value={selectedValue || undefined}\n onValueChange={(value) => {\n if (disabled) return;\n onAnswerChange?.(option.id, value);\n }}\n >\n <SelectTrigger\n disabled={disabled}\n className=\"w-full h-8 px-2 bg-background border-gray-300\"\n >\n <SelectValue placeholder=\"Selecione\" />\n </SelectTrigger>\n <SelectContent>\n {availableValues.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n );\n };\n\n // Render correct match for readonly mode\n const renderReadonlyItem = (option: ConnectDotsOption, index: number) => {\n return (\n <div\n key={`${baseId}-readonly-${index}`}\n className=\"flex flex-row items-center gap-3 p-3 rounded-md border border-success-300 bg-success-50\"\n >\n <div className=\"flex-1\">\n <HtmlMathRenderer\n content={option.option}\n className=\"text-text-900 text-sm\"\n />\n </div>\n\n <ArrowRightIcon size={20} className=\"text-success-600 flex-shrink-0\" />\n\n <div className=\"flex-shrink-0\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-success-700\">\n {option.correctValue}\n </Text>\n </div>\n </div>\n );\n };\n\n // Render result item with correct/incorrect indicator\n const renderResultItem = (option: ConnectDotsOption, index: number) => {\n const selectedValue = answers[option.id];\n const isCorrect = isAnswerCorrect(option.id);\n const hasAnswer = !!selectedValue;\n\n // Only show success styling when answered correctly\n const isSuccess = hasAnswer && isCorrect;\n const borderStyles = isSuccess\n ? 'border-success-300 bg-success-50'\n : 'border-error-300 bg-error-50';\n const arrowColor = isSuccess ? 'text-success-600' : 'text-error-600';\n\n return (\n <div\n key={`${baseId}-result-${index}`}\n className={cn(\n 'flex flex-row items-center gap-3 p-3 rounded-md border',\n borderStyles\n )}\n >\n <div className=\"flex-1\">\n <HtmlMathRenderer\n content={option.option}\n className=\"text-text-900 text-sm\"\n />\n </div>\n\n <ArrowRightIcon size={20} className={cn('flex-shrink-0', arrowColor)} />\n\n <div className=\"flex-shrink-0\">\n {hasAnswer ? (\n <Badge\n variant=\"solid\"\n action={isCorrect ? 'success' : 'error'}\n iconRight={isCorrect ? <CheckCircleIcon /> : <XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n {selectedValue}\n </Badge>\n ) : (\n <Badge\n variant=\"solid\"\n action=\"error\"\n iconRight={<XCircleIcon />}\n size=\"large\"\n className=\"py-1 px-2\"\n >\n Não respondido\n </Badge>\n )}\n </div>\n\n {/* Show correct answer if wrong */}\n {hasAnswer && !isCorrect && (\n <Text size=\"xs\" className=\"text-success-600 flex-shrink-0\">\n (Correto: {option.correctValue})\n </Text>\n )}\n </div>\n );\n };\n\n // Render the appropriate element based on mode\n const renderItem = (option: ConnectDotsOption, index: number): ReactNode => {\n switch (mode) {\n case QuizVariant.INTERACTIVE:\n return renderInteractiveItem(option, index);\n case QuizVariant.READONLY:\n return renderReadonlyItem(option, index);\n case QuizVariant.RESULT:\n return renderResultItem(option, index);\n default:\n return renderReadonlyItem(option, index);\n }\n };\n\n // Build the rendered content\n const renderedContent = useMemo(() => {\n return options.map((option, index) => renderItem(option, index));\n }, [options, baseId, mode, answers, disabled, availableValues]);\n\n return (\n <div className={cn('flex flex-col gap-2', className)} {...props}>\n {renderedContent}\n </div>\n );\n};\n\nexport { ConnectDots };\n","import { useCallback, useEffect, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport type { AxiosInstance } from 'axios';\nimport { useAuthStore } from '../store/authStore';\nimport { useModulesStore } from '../store/modulesStore';\nimport { useApiConfig, useUrlAuthentication, useTheme, useAuth } from '..';\n\n/**\n * Interface para as configurações do hook useAppContent\n */\ninterface UseAppContentConfig {\n /** API instance para configuração */\n api: {\n get: (endpoint: string, config: unknown) => Promise<unknown>;\n };\n /** ID da instituição obtido externamente */\n getInstitutionId: string | null | undefined;\n /** Função de inicialização obtida externamente */\n initialize: (institutionId: string) => void;\n /** Estado de inicialização obtido externamente */\n initialized: boolean;\n /** Endpoint para autenticação via URL */\n endpoint?: string;\n /** Número máximo de tentativas em caso de erro */\n maxRetries?: number;\n /** Delay entre tentativas em ms */\n retryDelay?: number;\n /** Callback para limpeza de parâmetros da URL */\n onClearParamsFromURL?: () => void;\n /** Callback para tratamento de erro */\n onError?: (error: unknown) => void;\n /** Callback para navegação quando página não encontrada */\n onNotFoundNavigation?: () => void;\n}\n\n/**\n * Hook que encapsula toda a lógica do componente AppContent\n * Centraliza a configuração de autenticação, tema, navegação e inicialização\n *\n * @param {UseAppContentConfig} config - Configurações do hook\n * @returns {object} Funções e estado necessários para o AppContent\n */\nexport function useAppContent(config: UseAppContentConfig) {\n const navigate = useNavigate();\n const { setTokens, setSessionInfo, setSelectedProfile } = useAuthStore();\n\n const {\n api,\n getInstitutionId,\n initialize,\n initialized,\n endpoint = '/auth/session-info',\n maxRetries = 1,\n retryDelay = 2000,\n onClearParamsFromURL,\n onError,\n onNotFoundNavigation,\n } = config;\n\n const apiConfig = useApiConfig(api);\n const { checkAuth } = useAuth();\n\n // Aplica o sistema de dark mode baseado nas preferências do sistema\n useTheme();\n\n // Helper function to handle NotFound navigation\n const handleNotFoundNavigation = () => {\n if (onNotFoundNavigation) {\n onNotFoundNavigation();\n } else {\n navigate('/painel');\n }\n };\n\n // Memoize all functions to prevent useEffect re-execution\n const handleSetSelectedProfile = useCallback(\n (profile: { id: string }) => {\n setSelectedProfile(profile);\n },\n [setSelectedProfile]\n );\n\n const handleClearParamsFromURL = useCallback(() => {\n if (onClearParamsFromURL) {\n onClearParamsFromURL();\n return;\n }\n // Strip ONLY the auth params injected by the login redirect, preserving any\n // other query the deep link carried (e.g. ?id=... on an edit page). Dropping\n // the whole query here used to lose those params after hydration.\n const params = new URLSearchParams(globalThis.location.search);\n params.delete('token');\n params.delete('refreshToken');\n params.delete('sessionId');\n const query = params.toString();\n navigate(`${globalThis.location.pathname}${query ? `?${query}` : ''}`, {\n replace: true,\n });\n }, [onClearParamsFromURL, navigate]);\n\n const handleError = useCallback(\n (error: unknown) => {\n if (onError) {\n onError(error);\n } else {\n console.error('Erro ao obter informações da sessão:', error);\n navigate('/', { replace: true });\n }\n },\n [navigate, onError]\n );\n\n // Re-runs the AuthProvider check after tokens are written into the store,\n // so guards re-evaluate before URL params are cleared (fixes first-login race).\n const handleAuthHydrated = useCallback(async () => {\n await checkAuth();\n }, [checkAuth]);\n\n // Memoize the entire configuration object to prevent re-execution\n const urlAuthConfig = useMemo(\n () => ({\n setTokens,\n setSessionInfo,\n setSelectedProfile: handleSetSelectedProfile,\n api: apiConfig,\n endpoint,\n clearParamsFromURL: handleClearParamsFromURL,\n maxRetries,\n retryDelay,\n onError: handleError,\n onAuthHydrated: handleAuthHydrated,\n }),\n [\n setTokens,\n setSessionInfo,\n handleSetSelectedProfile,\n apiConfig,\n endpoint,\n handleClearParamsFromURL,\n maxRetries,\n retryDelay,\n handleError,\n handleAuthHydrated,\n ]\n );\n\n // Enhanced URL authentication with retry logic and better error handling\n useUrlAuthentication(urlAuthConfig);\n\n const { sessionInfo } = useAuth();\n\n // Memoize the institutionId to use to prevent unnecessary re-executions\n const institutionIdToUse = useMemo(() => {\n return sessionInfo?.institutionId || getInstitutionId;\n }, [sessionInfo?.institutionId, getInstitutionId]);\n\n // Get the profile type from session info (e.g., STUDENT, TEACHER, UNIT_MANAGER)\n const profileType = useMemo(() => {\n return (sessionInfo as { profileName?: string })?.profileName ?? undefined;\n }, [sessionInfo]);\n\n useEffect(() => {\n if (institutionIdToUse && !initialized) {\n initialize(institutionIdToUse);\n }\n }, [institutionIdToUse, initialize, initialized]);\n\n // Fetch modules configuration when institutionId is available\n // Includes profileType for profile-specific feature flags\n useEffect(() => {\n if (institutionIdToUse) {\n useModulesStore\n .getState()\n .fetchModules(\n institutionIdToUse,\n apiConfig as AxiosInstance,\n profileType\n );\n }\n }, [institutionIdToUse, apiConfig, profileType]);\n\n return {\n handleNotFoundNavigation,\n urlAuthConfig,\n institutionIdToUse,\n };\n}\n","import {\n Button,\n getSubjectColorWithOpacity,\n IconRender,\n Text,\n} from '../../index';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport { prependLetterToHtml } from '../Quiz/Quiz.utils';\nimport {\n renderFromMap,\n type QuestionRendererMap,\n} from '../../utils/questionRenderer/index';\nimport { AlternativesList, type Alternative } from '../Alternative/Alternative';\nimport { OptionStatus } from '../../enums/Options';\nimport { MultipleChoiceList } from '../MultipleChoice/MultipleChoice';\nimport { FillInBlanks } from '../FillInBlanks/FillInBlanks';\nimport { ConnectDots } from '../ConnectDots/ConnectDots';\nimport { useMemo } from 'react';\nimport { questionTypeLabels } from '../../types/questionTypes';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\n\ninterface QuestionOption {\n id: string;\n option: string;\n isCorrect?: boolean;\n correctValue?: string | null;\n}\n\ninterface QuestionData {\n options: QuestionOption[];\n correctOptionIds?: string[];\n}\n\ninterface ActivityCardQuestionBanksProps {\n question?: QuestionData;\n questionType?: QUESTION_TYPE;\n iconName?: string;\n subjectColor?: string;\n isDark?: boolean;\n onAddToActivity?: () => void;\n content?: string;\n bank?: string;\n year?: string;\n statement?: string;\n additionalContent?: string | null;\n /** Enable exam mode - changes text labels from 'atividade' to 'prova' */\n enableExamMode?: boolean;\n}\n\nexport const ActivityCardQuestionBanks = ({\n question,\n questionType,\n iconName = 'BookOpen',\n subjectColor = '#000000',\n isDark = false,\n onAddToActivity,\n content,\n bank,\n year,\n statement,\n additionalContent,\n enableExamMode = false,\n}: ActivityCardQuestionBanksProps = {}) => {\n // Transform question options into Alternative format for teacher view\n const alternatives = useMemo(() => {\n if (!question?.options || questionType !== QUESTION_TYPE.ALTERNATIVA)\n return [];\n\n const correctOptionIds = question.correctOptionIds || [];\n\n return question.options.map((option) => {\n // Check isCorrect from option, fallback to correctOptionIds\n const isCorrect =\n option.isCorrect ?? correctOptionIds.includes(option.id);\n return {\n value: option.id,\n label: option.option,\n status: isCorrect ? OptionStatus.CORRECT : undefined,\n disabled: !isCorrect,\n } satisfies Alternative;\n });\n }, [question, questionType]);\n\n const correctOptionId = useMemo(() => {\n if (!question?.correctOptionIds || question.correctOptionIds.length === 0) {\n return undefined;\n }\n return question.correctOptionIds[0];\n }, [question]);\n\n // Transform question options into MultipleChoice format for teacher view\n const multipleChoices = useMemo(() => {\n if (!question?.options || questionType !== QUESTION_TYPE.MULTIPLA_ESCOLHA)\n return [];\n\n const correctOptionIds = question.correctOptionIds || [];\n\n return question.options.map((option) => {\n // Check isCorrect from option, fallback to correctOptionIds\n const isCorrect =\n option.isCorrect ?? correctOptionIds.includes(option.id);\n return {\n value: option.id,\n label: option.option,\n status: isCorrect ? OptionStatus.CORRECT : undefined,\n disabled: !isCorrect,\n };\n });\n }, [question, questionType]);\n\n const correctOptionIds = useMemo(() => {\n return question?.correctOptionIds || [];\n }, [question]);\n\n // Helper function to get letter by index\n const getLetterByIndex = (index: number) => String.fromCodePoint(97 + index); // 97 = 'a' in ASCII\n\n // Render functions for each question type\n const renderAlternative = () => {\n if (!question || alternatives.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <AlternativesList\n alternatives={alternatives}\n mode=\"readonly\"\n layout=\"compact\"\n selectedValue={correctOptionId}\n name=\"teacher-question-view\"\n />\n </div>\n );\n };\n\n const renderMultipleChoice = () => {\n if (!question || multipleChoices.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <MultipleChoiceList\n choices={multipleChoices}\n mode=\"readonly\"\n selectedValues={correctOptionIds}\n name=\"teacher-question-view-multiple\"\n />\n </div>\n );\n };\n\n const renderDissertative = () => {\n return (\n <div className=\"mt-4 px-2 py-4\">\n <Text size=\"sm\" className=\"text-text-600 italic\">\n Resposta do aluno\n </Text>\n </div>\n );\n };\n\n const renderTrueOrFalse = () => {\n if (!question || question.options.length === 0) return null;\n return (\n <div className=\"mt-4\">\n <div className=\"flex flex-col gap-3.5\">\n {question.options.map((option, index) => {\n // For VERDADEIRO_FALSO, use isCorrect from option\n const isCorrect =\n option.isCorrect ?? correctOptionIds.includes(option.id);\n const correctAnswer = isCorrect ? 'Verdadeiro' : 'Falso';\n const letter = getLetterByIndex(index);\n const contentWithLetter = prependLetterToHtml(\n letter,\n option.option\n );\n\n return (\n <section key={option.id} className=\"flex flex-col gap-2\">\n <div className=\"flex flex-row justify-between items-start gap-2 p-2 rounded-md border border-border-200\">\n <HtmlMathRenderer\n content={contentWithLetter}\n className=\"text-text-900 text-sm flex-1\"\n />\n\n <Text\n size=\"sm\"\n className=\"text-text-700 shrink-0 whitespace-nowrap\"\n >\n Resposta correta: {correctAnswer}\n </Text>\n </div>\n </section>\n );\n })}\n </div>\n </div>\n );\n };\n\n // Transform options for ConnectDots component\n const connectDotsOptions = useMemo(() => {\n if (!question?.options || questionType !== QUESTION_TYPE.RELACIONAR)\n return [];\n return question.options\n .filter((opt) => opt.correctValue)\n .map((opt) => ({\n id: opt.id,\n option: opt.option,\n correctValue: opt.correctValue as string,\n }));\n }, [question?.options, questionType]);\n\n const renderConnectDots = () => {\n if (connectDotsOptions.length === 0) return null;\n\n return (\n <ConnectDots\n options={connectDotsOptions}\n mode=\"readonly\"\n className=\"mt-4\"\n />\n );\n };\n\n // Transform options for FillInBlanks component\n const fillInBlanksOptions = useMemo(() => {\n if (!question?.options) return [];\n return question.options.map((opt) => ({\n id: opt.id,\n option: opt.option,\n }));\n }, [question?.options]);\n\n const renderFill = () => {\n // For PREENCHER_LACUNAS, additionalContent contains the fill-in text with placeholders\n if (!additionalContent || fillInBlanksOptions.length === 0) return null;\n\n return (\n <FillInBlanks\n content={additionalContent}\n options={fillInBlanksOptions}\n mode=\"readonly\"\n className=\"mt-4\"\n />\n );\n };\n\n // Parse image coordinates from options for IMAGEM questions\n const imageCoordinates = useMemo(() => {\n if (\n questionType !== QUESTION_TYPE.IMAGEM ||\n !question?.options?.[0]?.option\n )\n return null;\n\n try {\n const coords = JSON.parse(question.options[0].option);\n if (typeof coords.x === 'number' && typeof coords.y === 'number') {\n return { x: coords.x, y: coords.y };\n }\n } catch {\n // Invalid JSON, return null\n }\n return null;\n }, [question?.options, questionType]);\n\n const renderImage = () => {\n if (!additionalContent) return null;\n return (\n <div className=\"mt-4 relative inline-block\">\n <img\n src={additionalContent}\n alt=\"Imagem da questão\"\n className=\"max-w-full h-auto rounded-md border border-border-200\"\n />\n {imageCoordinates && (\n <div\n className=\"absolute w-5 h-5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-success-500 border-2 border-white shadow-md\"\n style={{\n left: `${imageCoordinates.x}%`,\n top: `${imageCoordinates.y}%`,\n }}\n />\n )}\n </div>\n );\n };\n\n // Map question types to render functions\n const questionRenderers: QuestionRendererMap = {\n [QUESTION_TYPE.ALTERNATIVA]: renderAlternative,\n [QUESTION_TYPE.MULTIPLA_ESCOLHA]: renderMultipleChoice,\n [QUESTION_TYPE.DISSERTATIVA]: renderDissertative,\n [QUESTION_TYPE.VERDADEIRO_FALSO]: renderTrueOrFalse,\n [QUESTION_TYPE.RELACIONAR]: renderConnectDots,\n [QUESTION_TYPE.PREENCHER_LACUNAS]: renderFill,\n [QUESTION_TYPE.IMAGEM]: renderImage,\n };\n\n return (\n <div className=\"w-full flex flex-col gap-2 px-4 py-6\">\n <section className=\"flex flex-row gap-2 text-text-650\">\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <span\n className=\"size-4 rounded-sm flex items-center justify-center shrink-0 text-text-950\"\n style={{\n backgroundColor: getSubjectColorWithOpacity(subjectColor, isDark),\n }}\n >\n <IconRender iconName={iconName} size={14} color=\"currentColor\" />\n </span>\n <Text size=\"sm\">{content || 'Assunto não informado'}</Text>\n </div>\n\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <Text size=\"sm\" className=\"\">\n {questionType\n ? questionTypeLabels[questionType]\n : 'Tipo de questão'}\n </Text>\n </div>\n\n {(bank || year) && (\n <div className=\"py-1 px-2 flex flex-row items-center gap-1\">\n <Text size=\"sm\">{[bank, year].filter(Boolean).join(' - ')}</Text>\n </div>\n )}\n </section>\n\n <section className=\"flex flex-col gap-1\">\n {statement ? (\n <HtmlMathRenderer\n content={statement}\n className=\"text-text-950 text-md font-medium\"\n />\n ) : (\n <Text size=\"md\" weight=\"medium\" className=\"text-text-950 text-md\">\n Enunciado não informado\n </Text>\n )}\n\n {renderFromMap(questionRenderers, questionType)}\n </section>\n\n <section>\n <Button\n size=\"small\"\n variant=\"outline\"\n iconLeft={<PlusIcon />}\n className=\"w-full\"\n onClick={() => {\n if (onAddToActivity) {\n onAddToActivity();\n }\n }}\n >\n {enableExamMode ? 'Adicionar à prova' : 'Adicionar à atividade'}\n </Button>\n </section>\n </div>\n );\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { FileIcon } from '@phosphor-icons/react/dist/csr/File';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { Button, EmptyState, IconButton, Text } from '../../index';\nimport { ActivityCardQuestionPreview } from '../ActivityCardQuestionPreview/ActivityCardQuestionPreview';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport { cn } from '../../utils/utils';\nimport {\n useQuestionsPdfPrint,\n QuestionsPdfContent,\n} from '../QuestionsPdfGenerator';\nimport Activities from '../../assets/icons/Activities';\n\ntype PreviewQuestion = {\n id: string;\n subjectName?: string;\n subjectColor?: string;\n iconName?: string;\n bank?: string;\n year?: string;\n questionType?: QUESTION_TYPE;\n questionTypeLabel?: string;\n statement?: string;\n question?: {\n options: { id: string; option: string }[];\n correctOptionIds?: string[];\n };\n solutionExplanation?: string | null;\n position?: number;\n};\n\ninterface ActivityPreviewProps {\n title?: string;\n questions?: PreviewQuestion[];\n onDownloadPdf?: () => void;\n onRemoveAll?: () => void;\n onRemoveQuestion?: (questionId: string) => void;\n className?: string;\n onReorder?: (orderedQuestions: PreviewQuestion[]) => void;\n /**\n * Emits the current ordered list (with positions) whenever it changes.\n */\n onPositionsChange?: (orderedQuestions: PreviewQuestion[]) => void;\n isDark?: boolean;\n}\n\nexport const ActivityPreview = ({\n title = 'Prévia da atividade',\n questions = [],\n onDownloadPdf,\n onRemoveAll,\n onRemoveQuestion,\n className,\n onReorder,\n onPositionsChange,\n isDark = false,\n}: ActivityPreviewProps) => {\n const onPositionsChangeRef = useRef(onPositionsChange);\n onPositionsChangeRef.current = onPositionsChange;\n\n const normalizeWithPositions = useMemo(\n () => (items: PreviewQuestion[]) =>\n items.map((item, index) => ({\n ...item,\n position: index + 1,\n })),\n []\n );\n\n const [orderedQuestions, setOrderedQuestions] = useState<PreviewQuestion[]>(\n () => normalizeWithPositions(questions)\n );\n\n // Sync when external questions change (e.g., reset from parent)\n useEffect(() => {\n const normalized = normalizeWithPositions(questions);\n setOrderedQuestions(normalized);\n onPositionsChangeRef.current?.(normalized);\n }, [questions, normalizeWithPositions]);\n\n const total = orderedQuestions.length;\n const totalLabel =\n total === 1 ? '1 questão adicionada' : `${total} questões adicionadas`;\n\n const { contentRef, handlePrint } = useQuestionsPdfPrint(\n orderedQuestions,\n onDownloadPdf\n );\n\n const handleDownloadPdf = () => {\n // Chama o callback opcional antes de imprimir\n onDownloadPdf?.();\n\n // Pequeno delay para garantir que o DOM está atualizado\n // e que o callback foi executado\n setTimeout(() => {\n if (!contentRef.current) {\n console.error('Elemento de PDF não encontrado no DOM');\n return;\n }\n\n // Verifica se handlePrint é uma função antes de chamar\n if (typeof handlePrint === 'function') {\n handlePrint();\n } else {\n console.error('handlePrint não é uma função:', handlePrint);\n }\n }, 100);\n };\n\n const handleReorder = (fromId: string, toId: string) => {\n if (fromId === toId) return;\n const current = [...orderedQuestions];\n const fromIndex = current.findIndex((q) => q.id === fromId);\n const toIndex = current.findIndex((q) => q.id === toId);\n if (fromIndex === -1 || toIndex === -1) return;\n\n const [moved] = current.splice(fromIndex, 1);\n current.splice(toIndex, 0, moved);\n const normalized = normalizeWithPositions(current);\n setOrderedQuestions(normalized);\n onReorder?.(normalized);\n onPositionsChange?.(normalized);\n };\n\n return (\n <div\n className={cn(\n 'w-full flex-shrink-0 p-4 rounded-lg bg-background flex flex-col gap-4',\n className\n )}\n >\n <section className=\"flex flex-row items-center gap-2 text-text-950\">\n <FileIcon size={24} />\n <Text size=\"lg\" weight=\"bold\">\n {title}\n </Text>\n </section>\n\n <section className=\"flex flex-row justify-between items-center\">\n <Text size=\"sm\" className=\"text-text-800\">\n {totalLabel}\n </Text>\n {orderedQuestions.length > 0 && (\n <Button\n size=\"small\"\n variant=\"outline\"\n iconLeft={<DownloadSimpleIcon />}\n onClick={handleDownloadPdf}\n >\n Baixar pdf\n </Button>\n )}\n </section>\n\n {orderedQuestions.length === 0 ? (\n <EmptyState\n image={<Activities />}\n title=\"Nenhuma questão adicionada ainda\"\n description=\"Utilize a coluna ao lado para adicionar questões à atividade.\"\n size=\"compact\"\n />\n ) : (\n <section className=\"flex flex-col gap-3\">\n {orderedQuestions.map(\n (\n {\n id,\n subjectName = 'Assunto não informado',\n subjectColor = '#000000',\n iconName = 'BookOpen',\n bank,\n year,\n questionType,\n questionTypeLabel,\n statement,\n question,\n solutionExplanation,\n position,\n },\n index\n ) => (\n <div\n key={id}\n draggable\n data-draggable=\"true\"\n role=\"button\"\n tabIndex={0}\n aria-label={`Mover questão ${statement ?? id}`}\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', id);\n if (e.currentTarget instanceof HTMLElement) {\n const preview = e.currentTarget.querySelector(\n '[data-drag-preview=\"true\"]'\n );\n if (preview) {\n e.dataTransfer.setDragImage(preview, 8, 8);\n } else {\n e.dataTransfer.setDragImage(e.currentTarget, 8, 8);\n }\n }\n }}\n onDragOver={(e) => {\n e.preventDefault();\n }}\n onDrop={(e) => {\n e.preventDefault();\n const fromId = e.dataTransfer.getData('text/plain');\n handleReorder(fromId, id);\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowUp' && index > 0) {\n e.preventDefault();\n const targetId = orderedQuestions[index - 1].id;\n handleReorder(id, targetId);\n } else if (\n e.key === 'ArrowDown' &&\n index < orderedQuestions.length - 1\n ) {\n e.preventDefault();\n const targetId = orderedQuestions[index + 1].id;\n handleReorder(id, targetId);\n } else if (e.key === 'Enter' || e.key === ' ') {\n // Keyboard grab/drop noop; prevent scroll on space\n e.preventDefault();\n }\n }}\n className=\"rounded-lg border border-border-200 bg-background relative group\"\n >\n {onRemoveQuestion && (\n <div className=\"absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity\">\n <IconButton\n size=\"sm\"\n icon={<TrashIcon size={16} />}\n onClick={(e) => {\n e.stopPropagation();\n onRemoveQuestion(id);\n }}\n aria-label={`Remover questão ${position ?? index + 1}`}\n />\n </div>\n )}\n <ActivityCardQuestionPreview\n subjectName={subjectName}\n subjectColor={subjectColor}\n iconName={iconName}\n isDark={isDark}\n bank={bank}\n year={year}\n questionType={questionType}\n questionTypeLabel={questionTypeLabel}\n statement={statement}\n defaultExpanded={false}\n question={question}\n solutionExplanation={solutionExplanation}\n value={id}\n position={position}\n ></ActivityCardQuestionPreview>\n </div>\n )\n )}\n </section>\n )}\n\n {orderedQuestions.length > 0 && onRemoveAll && (\n <Button\n variant=\"link\"\n action=\"negative\"\n iconLeft={<TrashIcon />}\n onClick={onRemoveAll}\n >\n Remover tudo\n </Button>\n )}\n\n <QuestionsPdfContent ref={contentRef} questions={orderedQuestions} />\n </div>\n );\n};\n\nexport type { ActivityPreviewProps, PreviewQuestion };\n","import React, { forwardRef, useRef, useCallback } from 'react';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport LatexRenderer from '../LatexRenderer/LatexRenderer';\nimport type { PreviewQuestion } from '../ActivityPreview/ActivityPreview';\n\ninterface QuestionsPdfGeneratorProps {\n questions: PreviewQuestion[];\n onPrint?: () => void;\n onPrintError?: (error: Error) => void;\n /**\n * Optional render prop to access the handlePrint function.\n * If not provided, the component will render the hidden PDF content only.\n * Consumers can use the hook directly (useQuestionsPdfPrint) for more control.\n */\n children?: (handlePrint: () => void) => React.ReactNode;\n}\n\n/**\n * Component that renders questions in a format suitable for PDF printing\n * This component is hidden from view and only used for printing\n */\nexport const QuestionsPdfContent = forwardRef<\n HTMLDivElement,\n { questions: PreviewQuestion[] }\n>(({ questions }, ref) => {\n const getLetterByIndex = (index: number): string => {\n return String.fromCodePoint(97 + index);\n };\n\n const renderAlternative = (question: PreviewQuestion) => {\n if (!question.question?.options || question.question.options.length === 0) {\n return null;\n }\n\n return (\n <ol\n type=\"a\"\n style={{\n marginTop: '12px',\n marginBottom: '12px',\n paddingLeft: '24px',\n }}\n >\n {question.question.options.map((option) => (\n <li\n key={option.id}\n style={{\n marginBottom: '8px',\n lineHeight: '1.6',\n }}\n >\n <LatexRenderer content={option.option} />\n </li>\n ))}\n </ol>\n );\n };\n\n const renderMultipleChoice = (question: PreviewQuestion) => {\n if (!question.question?.options || question.question.options.length === 0) {\n return null;\n }\n\n return (\n <div\n style={{\n marginTop: '12px',\n marginBottom: '12px',\n }}\n >\n {question.question.options.map((option, index) => {\n const letter = getLetterByIndex(index);\n return (\n <div\n key={option.id}\n style={{\n marginBottom: '12px',\n display: 'flex',\n alignItems: 'flex-start',\n gap: '8px',\n lineHeight: '1.6',\n }}\n >\n <span\n style={{\n marginTop: '2px',\n minWidth: '20px',\n fontSize: '16px',\n }}\n >\n ☐\n </span>\n <span\n style={{\n fontWeight: 'bold',\n marginRight: '4px',\n }}\n >\n {letter})\n </span>\n <div style={{ flex: 1 }}>\n <LatexRenderer content={option.option} />\n </div>\n </div>\n );\n })}\n </div>\n );\n };\n\n const renderDissertative = () => {\n return (\n <div\n style={{\n marginTop: '12px',\n marginBottom: '12px',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: '8px',\n }}\n >\n <span\n style={{\n fontWeight: 'bold',\n marginTop: '2px',\n }}\n >\n R:\n </span>\n <div\n style={{\n minHeight: '150px',\n flex: 1,\n }}\n />\n </div>\n </div>\n );\n };\n\n const renderTrueOrFalse = (question: PreviewQuestion) => {\n if (!question.question?.options || question.question.options.length === 0) {\n return null;\n }\n\n return (\n <div style={{ marginTop: '12px', marginBottom: '12px' }}>\n {question.question.options.map((option, index) => (\n <div\n key={option.id}\n style={{\n marginBottom: '16px',\n display: 'flex',\n alignItems: 'flex-start',\n gap: '12px',\n }}\n >\n <span\n style={{\n fontWeight: 'bold',\n minWidth: '20px',\n }}\n >\n {getLetterByIndex(index)})\n </span>\n <div style={{ flex: 1 }}>\n <LatexRenderer content={option.option} />\n </div>\n <span\n style={{\n marginLeft: '8px',\n whiteSpace: 'nowrap',\n fontSize: '14px',\n }}\n >\n ( ) V ( ) F\n </span>\n </div>\n ))}\n </div>\n );\n };\n\n const renderQuestionTypeContent = (question: PreviewQuestion) => {\n type RenderFunction = (\n question: PreviewQuestion\n ) => ReturnType<typeof renderAlternative> | null;\n\n const renderMap: Record<QUESTION_TYPE, RenderFunction> = {\n [QUESTION_TYPE.ALTERNATIVA]: renderAlternative,\n [QUESTION_TYPE.MULTIPLA_ESCOLHA]: renderMultipleChoice,\n [QUESTION_TYPE.DISSERTATIVA]: () => renderDissertative(),\n [QUESTION_TYPE.VERDADEIRO_FALSO]: renderTrueOrFalse,\n [QUESTION_TYPE.RELACIONAR]: () => null,\n [QUESTION_TYPE.PREENCHER_LACUNAS]: () => null,\n [QUESTION_TYPE.IMAGEM]: () => null,\n };\n\n if (!question.questionType) {\n return null;\n }\n\n const renderFunction = renderMap[question.questionType];\n return renderFunction ? renderFunction(question) : null;\n };\n\n const renderQuestionContent = (question: PreviewQuestion, index: number) => {\n const questionNumber = index + 1;\n\n return (\n <div\n key={question.id}\n style={{\n marginBottom: '32px',\n pageBreakInside: 'avoid',\n }}\n >\n <h3\n style={{\n fontSize: '16px',\n fontWeight: 'bold',\n marginBottom: '12px',\n color: '#000',\n }}\n >\n Questão {questionNumber}\n </h3>\n\n {(question.bank || question.year) && (\n <div\n style={{\n fontSize: '12px',\n marginBottom: '12px',\n color: '#555',\n }}\n >\n {[question.bank, question.year].filter(Boolean).join(' - ')}\n </div>\n )}\n\n {question.statement && (\n <div\n style={{\n marginBottom: '12px',\n lineHeight: '1.6',\n color: '#000',\n }}\n >\n <LatexRenderer content={question.statement} />\n </div>\n )}\n\n {renderQuestionTypeContent(question)}\n </div>\n );\n };\n\n return (\n <div\n ref={ref}\n className=\"questions-pdf-container\"\n style={{\n position: 'fixed',\n left: 0,\n top: 0,\n width: '210mm',\n minHeight: '297mm',\n visibility: 'hidden',\n pointerEvents: 'none',\n zIndex: -1,\n }}\n >\n <style>\n {`\n @media print {\n @page {\n margin: 20mm;\n size: A4;\n }\n \n body {\n margin: 0;\n padding: 0;\n }\n \n .questions-pdf-container {\n position: static !important;\n left: auto !important;\n top: auto !important;\n visibility: visible !important;\n pointer-events: auto !important;\n z-index: auto !important;\n padding: 0;\n margin: 0;\n font-family: Arial, sans-serif;\n font-size: 14px;\n line-height: 1.6;\n color: #000;\n width: auto;\n min-height: auto;\n }\n \n .questions-pdf-container h3 {\n page-break-after: avoid;\n }\n \n .questions-pdf-container > div {\n page-break-inside: avoid;\n }\n }\n `}\n </style>\n <div style={{ padding: '20px' }}>\n {questions.map((question, index) =>\n renderQuestionContent(question, index)\n )}\n </div>\n </div>\n );\n});\n\nQuestionsPdfContent.displayName = 'QuestionsPdfContent';\n\n/**\n * Collects relevant CSS styles from document stylesheets\n */\nconst collectRelevantStyles = (): string[] => {\n const styles: string[] = [];\n const styleSheets = Array.from(document.styleSheets);\n\n styleSheets.forEach((styleSheet) => {\n try {\n const cssRules = styleSheet.cssRules || [];\n Array.from(cssRules).forEach((rule) => {\n const ruleText = rule.cssText;\n if (ruleText.includes('katex') || ruleText.includes('questions-pdf')) {\n styles.push(ruleText);\n }\n });\n } catch (error) {\n if (typeof console !== 'undefined' && console.debug) {\n console.debug('Could not access stylesheet (likely CORS):', error);\n }\n }\n });\n\n return styles;\n};\n\n/**\n * Generates the HTML content for the print window\n *\n * Security note: contentHTML is already sanitized via DOMPurify in LatexRenderer\n * before reaching this function, so it's safe to inject into the template string.\n *\n * CSP note: This function loads KaTeX CSS from CDN. Ensure your Content Security Policy\n * allows: style-src 'self' https://cdn.jsdelivr.net;\n * Or consider serving KaTeX CSS locally for stricter CSP compliance.\n */\nconst generatePrintHTML = (contentHTML: string, styles: string[]): string => {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Questões</title>\n <meta charset=\"utf-8\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.min.css\">\n <style>\n @page {\n margin: 20mm;\n size: A4;\n }\n \n * {\n box-sizing: border-box;\n }\n \n body {\n margin: 0;\n padding: 0;\n font-family: Arial, sans-serif;\n font-size: 14px;\n line-height: 1.6;\n color: #000;\n }\n \n .questions-pdf-container {\n position: static !important;\n visibility: visible !important;\n pointer-events: auto !important;\n z-index: auto !important;\n left: auto !important;\n top: auto !important;\n width: auto !important;\n min-height: auto !important;\n padding: 0;\n margin: 0;\n }\n \n .questions-pdf-container h3 {\n page-break-after: avoid;\n font-size: 16px;\n font-weight: bold;\n margin-bottom: 12px;\n color: #000;\n }\n \n .questions-pdf-container > div {\n page-break-inside: avoid;\n }\n \n ol {\n margin-top: 12px;\n margin-bottom: 12px;\n padding-left: 24px;\n }\n \n li {\n margin-bottom: 8px;\n line-height: 1.6;\n }\n \n ${styles.join('\\n')}\n </style>\n </head>\n <body>\n <div class=\"questions-pdf-container\">\n ${contentHTML}\n </div>\n </body>\n </html>\n `;\n};\n\n/**\n * Sets up the print window onload handler with improved timing and cleanup\n */\nconst setupPrintWindowHandler = (printWindow: Window): void => {\n printWindow.onload = () => {\n const printAndCleanup = () => {\n try {\n printWindow.print();\n } finally {\n setTimeout(() => {\n if (printWindow && !printWindow.closed) {\n printWindow.close();\n }\n }, 1000);\n }\n };\n\n const fonts = printWindow.document.fonts;\n if (fonts && 'ready' in fonts) {\n fonts.ready\n .then(() => {\n setTimeout(printAndCleanup, 100);\n })\n .catch(() => {\n setTimeout(printAndCleanup, 500);\n });\n } else {\n setTimeout(printAndCleanup, 500);\n }\n };\n};\n\n/**\n * Hook to generate PDF from questions using native browser print API\n * Returns the content ref and print handler\n *\n * Security: Content is sanitized via DOMPurify in LatexRenderer before rendering.\n * CSP: Ensure your Content Security Policy allows loading KaTeX CSS from cdn.jsdelivr.net\n * or serve KaTeX CSS locally for stricter CSP compliance.\n */\nexport const useQuestionsPdfPrint = (\n questions: PreviewQuestion[],\n onPrint?: () => void,\n onPrintError?: (error: Error) => void\n) => {\n const contentRef = useRef<HTMLDivElement>(null);\n\n const handlePrint = useCallback(() => {\n try {\n onPrint?.();\n\n if (!contentRef.current) {\n throw new Error('Elemento de PDF não encontrado no DOM');\n }\n\n const printWindow = window.open('', '_blank');\n if (!printWindow) {\n throw new Error(\n 'Não foi possível abrir a janela de impressão. Verifique se os pop-ups estão bloqueados.'\n );\n }\n printWindow.opener = null;\n\n const contentHTML = contentRef.current.innerHTML;\n const styles = collectRelevantStyles();\n const htmlContent = generatePrintHTML(contentHTML, styles);\n\n const doc = printWindow.document;\n doc.open();\n doc.write(htmlContent);\n doc.close();\n\n setupPrintWindowHandler(printWindow);\n } catch (error) {\n const errorObj =\n error instanceof Error ? error : new Error(String(error));\n onPrintError?.(errorObj);\n console.error('Erro ao gerar PDF:', errorObj);\n }\n }, [onPrint, onPrintError]);\n\n return {\n contentRef,\n handlePrint,\n };\n};\n\n/**\n * QuestionsPdfGenerator Component\n *\n * Isolated and reusable component for generating PDFs of questions without answers.\n * Formats questions for school printing with specific layouts for each question type.\n *\n * This component renders hidden content and optionally exposes handlePrint via render prop.\n * For more control, consumers can use the hook directly (useQuestionsPdfPrint).\n *\n * @param questions - Array of questions to render in PDF\n * @param onPrint - Optional callback when print is triggered\n * @param onPrintError - Optional callback when print error occurs\n * @param children - Optional render prop function that receives handlePrint\n * @returns Component with hidden PDF content and optionally rendered children\n *\n * @example\n * // Without render prop (hidden content only)\n * <QuestionsPdfGenerator questions={questions} />\n *\n * @example\n * // With render prop (expose handlePrint)\n * <QuestionsPdfGenerator questions={questions}>\n * {(handlePrint) => <button onClick={handlePrint}>Print</button>}\n * </QuestionsPdfGenerator>\n */\nexport const QuestionsPdfGenerator = ({\n questions,\n onPrint,\n onPrintError,\n children,\n}: QuestionsPdfGeneratorProps) => {\n const { contentRef, handlePrint } = useQuestionsPdfPrint(\n questions,\n onPrint,\n onPrintError\n );\n\n return (\n <>\n {children?.(handlePrint)}\n <div style={{ display: 'none' }}>\n <QuestionsPdfContent ref={contentRef} questions={questions} />\n </div>\n </>\n );\n};\n\nexport type { QuestionsPdfGeneratorProps };\n","import { Fragment, ReactNode } from 'react';\nimport CheckBox from '../CheckBox/CheckBox';\nimport Divider from '../Divider/Divider';\nimport { cn } from '../../utils/utils';\n\n/**\n * Item (filho) do grupo hierarquico.\n */\nexport interface HierarchicalCheckboxItem {\n id: string;\n label: ReactNode;\n disabled?: boolean;\n}\n\n/**\n * Grupo (pai) que agrupa varios itens.\n */\nexport interface HierarchicalCheckboxGroupItem {\n id: string;\n label: ReactNode;\n items: HierarchicalCheckboxItem[];\n}\n\n/**\n * Layout dos itens (filhos) dentro de cada grupo.\n */\nexport type HierarchicalCheckboxItemsLayout = 'column' | 'row';\n\nexport interface HierarchicalCheckboxGroupProps {\n /** Lista de grupos, cada um com seus itens filhos */\n groups: HierarchicalCheckboxGroupItem[];\n /** Ids dos itens (filhos) atualmente selecionados */\n selectedIds: string[];\n /** Callback disparado quando a selecao muda */\n onChange: (selectedIds: string[]) => void;\n /**\n * Layout dos filhos de cada grupo.\n * - `column` (default): um abaixo do outro\n * - `row`: inline, com quebra de linha\n */\n itemsLayout?: HierarchicalCheckboxItemsLayout;\n /** Renderiza Divider entre grupos (default: true) */\n showGroupDividers?: boolean;\n /** Classes extras aplicadas ao container raiz */\n className?: string;\n /** Classes extras aplicadas ao label do grupo (pai) */\n groupLabelClassName?: string;\n}\n\n/**\n * HierarchicalCheckboxGroup - lista de grupos pai/filho com estado\n * `indeterminate` automatico no pai baseado na selecao dos filhos.\n *\n * Comportamento do pai:\n * - Todos os filhos selecionados -> `checked`\n * - Alguns filhos selecionados -> `indeterminate`\n * - Nenhum filho selecionado -> `unchecked`\n * - Click no pai: marca/desmarca todos os filhos daquele grupo\n *\n * @example\n * ```tsx\n * <HierarchicalCheckboxGroup\n * groups={[\n * {\n * id: 'ef',\n * label: 'Ensino fundamental',\n * items: [\n * { id: 'ef-6', label: '6º ano' },\n * { id: 'ef-7', label: '7º ano' },\n * ],\n * },\n * ]}\n * selectedIds={selected}\n * onChange={setSelected}\n * />\n * ```\n */\nconst HierarchicalCheckboxGroup = ({\n groups,\n selectedIds,\n onChange,\n itemsLayout = 'column',\n showGroupDividers = true,\n className,\n groupLabelClassName,\n}: HierarchicalCheckboxGroupProps) => {\n const toggleItem = (itemId: string) => {\n if (selectedIds.includes(itemId)) {\n onChange(selectedIds.filter((id) => id !== itemId));\n } else {\n onChange([...selectedIds, itemId]);\n }\n };\n\n const selectAllInGroup = (groupItemIds: string[]) => {\n const merged = new Set([...selectedIds, ...groupItemIds]);\n onChange(Array.from(merged));\n };\n\n const deselectAllInGroup = (groupItemIds: string[]) => {\n onChange(selectedIds.filter((id) => !groupItemIds.includes(id)));\n };\n\n const itemsContainerClasses =\n itemsLayout === 'row'\n ? 'flex flex-wrap gap-x-6 gap-y-2'\n : 'flex flex-col gap-2';\n\n return (\n <div className={cn('flex flex-col gap-4', className)}>\n {groups.map((group, index) => {\n const groupItemIds = group.items.map((item) => item.id);\n const selectedCount = groupItemIds.filter((id) =>\n selectedIds.includes(id)\n ).length;\n const allChecked =\n selectedCount === groupItemIds.length && groupItemIds.length > 0;\n const someChecked = selectedCount > 0 && !allChecked;\n\n return (\n <Fragment key={group.id}>\n {showGroupDividers && index > 0 && <Divider />}\n <div className=\"flex flex-col gap-3\">\n <CheckBox\n checked={allChecked}\n indeterminate={someChecked}\n onChange={() =>\n allChecked\n ? deselectAllInGroup(groupItemIds)\n : selectAllInGroup(groupItemIds)\n }\n label={\n <span\n className={cn(\n 'font-bold text-text-950',\n groupLabelClassName\n )}\n >\n {group.label}\n </span>\n }\n />\n <div className={itemsContainerClasses}>\n {group.items.map((item) => (\n <CheckBox\n key={item.id}\n checked={selectedIds.includes(item.id)}\n disabled={item.disabled}\n onChange={() => toggleItem(item.id)}\n label={item.label}\n />\n ))}\n </div>\n </div>\n </Fragment>\n );\n })}\n </div>\n );\n};\n\nHierarchicalCheckboxGroup.displayName = 'HierarchicalCheckboxGroup';\n\nexport default HierarchicalCheckboxGroup;\n","import { useState, useMemo, useCallback, useEffect, useRef } from 'react';\nimport { MedalIcon } from '@phosphor-icons/react/dist/csr/Medal';\nimport { StarIcon } from '@phosphor-icons/react/dist/csr/Star';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport { WarningCircleIcon } from '@phosphor-icons/react/dist/csr/WarningCircle';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport { EyeIcon } from '@phosphor-icons/react/dist/csr/Eye';\nimport Text from '../Text/Text';\nimport { TruncatedText } from '../TruncatedText/TruncatedText';\nimport Button from '../Button/Button';\nimport Badge from '../Badge/Badge';\nimport Modal from '../Modal/Modal';\nimport { ActivityCardQuestionPreview } from '../ActivityCardQuestionPreview/ActivityCardQuestionPreview';\nimport EmptyState from '../EmptyState/EmptyState';\nimport {\n SkeletonText,\n SkeletonRounded,\n SkeletonTable,\n} from '../Skeleton/Skeleton';\nimport { TableProvider } from '../TableProvider/TableProvider';\nimport CorrectActivityModal from '../CorrectActivityModal/CorrectActivityModal';\nimport { getSubjectInfo, type SubjectData } from '../SubjectInfo/SubjectInfo';\nimport { useMobile } from '../../hooks/useMobile';\nimport { cn } from '../../utils/utils';\nimport { SubjectEnum } from '../../enums/SubjectEnum';\nimport type { ColumnConfig, TableParams } from '../TableProvider/TableProvider';\nimport type {\n StudentActivityCorrectionData,\n SaveQuestionCorrectionPayload,\n} from '../../utils/studentActivityCorrection';\nimport { convertApiResponseToCorrectionData } from '../../utils/studentActivityCorrection';\nimport {\n STUDENT_ACTIVITY_STATUS,\n type ActivityDetailsData,\n type ActivityStudentTableItem,\n type StudentActivityStatus,\n} from '../../types/activityDetails';\nimport { ActivitySubtype } from '../SendActivityModal/types';\nimport {\n getStatusBadgeConfig,\n formatTimeSpent,\n formatQuestionNumbers,\n formatDateToBrazilian,\n formatActivityDateToBrazilian,\n} from '../../utils/activityDetailsUtils';\nimport type { BaseApiClient } from '../../types/api';\nimport { useActivityDetails } from '../../hooks/useActivityDetails';\nimport {\n useQuestionsPdfPrint,\n QuestionsPdfContent,\n} from '../QuestionsPdfGenerator';\nimport type { PreviewQuestion } from '../ActivityPreview/ActivityPreview';\nimport { convertQuestionToPreview } from '../ActivityCreate/ActivityCreate.utils';\nimport type { Question } from '../../types/questions';\nimport { createUseQuestionsList } from '../../hooks/useQuestionsList';\nimport useToastStore from '../Toast/utils/ToastStore';\n\n/**\n * Props for the ActivityDetails component\n */\nexport interface ActivityDetailsProps {\n /** Activity ID to display details for */\n activityId: string;\n /** API client instance for making requests */\n apiClient: BaseApiClient;\n /** Callback when back button is clicked */\n onBack?: () => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /**\n * Callback for downloading the answer sheet for a student.\n * Presencial mode is determined by `activity.isDigital === false && activity.subtype === ActivitySubtype.PROVA`,\n * not by the presence of this callback. When the activity is presencial:\n * - Hides the \"Duração\" column\n * - Renames \"Respondido em\" → \"Gabarito recebido em\"\n * - Shows a \"Gabarito\" column (before \"Resultado\"); button is disabled when callback is absent\n */\n onDownloadAnswerSheet?: (studentId: string) => void;\n}\n\n/**\n * Create table columns configuration\n * @param onCorrectActivity - Callback for correction action\n * @param isPresencial - Whether the activity is in presencial mode (drives column visibility)\n * @param onDownloadAnswerSheet - Optional callback for answer sheet download button in presencial mode\n * @returns Column configuration array\n */\nconst createTableColumns = (\n onCorrectActivity: (studentId: string) => void,\n isPresencial: boolean,\n onDownloadAnswerSheet?: (studentId: string) => void\n): ColumnConfig<ActivityStudentTableItem>[] => {\n const columns: ColumnConfig<ActivityStudentTableItem>[] = [\n {\n key: 'studentName',\n label: 'Aluno',\n sortable: true,\n className: 'max-w-[220px]',\n render: (value: unknown) => {\n const name = typeof value === 'string' ? value : '';\n return (\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center shrink-0\">\n <Text className=\"text-xs font-semibold text-primary-700\">\n {name.charAt(0).toUpperCase()}\n </Text>\n </div>\n <TruncatedText size=\"sm\" color=\"text-text-950\">\n {name}\n </TruncatedText>\n </div>\n );\n },\n },\n {\n key: 'status',\n label: 'Status',\n sortable: false,\n render: (value: unknown) => {\n const status = value as StudentActivityStatus;\n const config = getStatusBadgeConfig(status);\n return (\n <Badge\n className={`${config.bgColor} ${config.textColor} text-xs px-2 py-1`}\n >\n {config.label}\n </Badge>\n );\n },\n },\n {\n key: 'answeredAt',\n label: isPresencial ? 'Gabarito recebido em' : 'Respondido em',\n sortable: true,\n render: (value: unknown) => {\n if (!value || typeof value !== 'string') {\n return <Text className=\"text-sm text-text-400\">-</Text>;\n }\n return (\n <Text className=\"text-sm text-text-700\">\n {formatDateToBrazilian(value)}\n </Text>\n );\n },\n },\n ];\n\n // \"Duração\" column is hidden in presencial mode\n if (!isPresencial) {\n columns.push({\n key: 'timeSpent',\n label: 'Duração',\n sortable: false,\n render: (value: unknown) =>\n Number(value) > 0 ? (\n <Text className=\"text-sm text-text-700\">\n {formatTimeSpent(Number(value))}\n </Text>\n ) : (\n <Text className=\"text-sm text-text-400\">-</Text>\n ),\n });\n }\n\n columns.push({\n key: 'score',\n label: 'Nota',\n sortable: true,\n render: (value: unknown) =>\n value === null ? (\n <Text className=\"text-sm text-text-400\">-</Text>\n ) : (\n <Text className=\"text-sm font-semibold text-text-950\">\n {Number(value).toFixed(1)}\n </Text>\n ),\n });\n\n // \"Gabarito\" column is always shown in presencial mode; button disabled when callback is absent\n if (isPresencial) {\n columns.push({\n key: 'answerSheet',\n label: 'Gabarito',\n sortable: false,\n render: (_value: unknown, row: ActivityStudentTableItem) => (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() => onDownloadAnswerSheet?.(row.studentId)}\n disabled={!onDownloadAnswerSheet}\n className=\"text-xs\"\n >\n Baixar gabarito\n </Button>\n ),\n });\n }\n\n columns.push({\n key: 'actions',\n label: 'Resultado',\n sortable: false,\n render: (_value: unknown, row: ActivityStudentTableItem) => {\n // Presencial mode: show \"Ver respostas\" — disabled until answer sheet is received\n if (isPresencial) {\n const hasResponse =\n row.status === STUDENT_ACTIVITY_STATUS.ANSWER_SHEET_RECEIVED ||\n row.status === STUDENT_ACTIVITY_STATUS.AGUARDANDO_CORRECAO ||\n row.status === STUDENT_ACTIVITY_STATUS.CONCLUIDO;\n return (\n <Button\n variant=\"link\"\n size=\"small\"\n onClick={\n hasResponse ? () => onCorrectActivity(row.studentId) : undefined\n }\n disabled={!hasResponse}\n className=\"text-xs\"\n >\n Ver respostas\n </Button>\n );\n }\n\n // Normal mode\n if (row.status === STUDENT_ACTIVITY_STATUS.AGUARDANDO_CORRECAO) {\n return (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() => onCorrectActivity(row.studentId)}\n className=\"text-xs\"\n >\n Corrigir atividade\n </Button>\n );\n }\n\n if (\n row.status === STUDENT_ACTIVITY_STATUS.CONCLUIDO ||\n row.status === STUDENT_ACTIVITY_STATUS.NAO_ENTREGUE\n ) {\n return (\n <Button\n variant=\"link\"\n size=\"small\"\n onClick={() => onCorrectActivity(row.studentId)}\n className=\"text-xs\"\n >\n Ver detalhes\n </Button>\n );\n }\n\n return null;\n },\n });\n\n return columns;\n};\n\n/**\n * Normalize questions with positions\n */\nconst normalizeWithPositions = (items: PreviewQuestion[]) =>\n items.map((item, index) => ({\n ...item,\n position: index + 1,\n }));\n\n/**\n * Extract the question ids from a quiz/activity endpoint response, supporting\n * both a `questions` array and a `questionIds` array.\n * @param response - the raw endpoint response (may be undefined when it failed)\n * @returns the list of question ids (empty when none are present)\n */\nconst extractQuestionIds = (response?: {\n data?: { data?: { questions?: Question[]; questionIds?: string[] } };\n}): string[] =>\n response?.data?.data?.questions?.map((q) => q.id) ??\n response?.data?.data?.questionIds ??\n [];\n\n/**\n * Resolve an error message from an unknown thrown value, falling back to a\n * provided default when it is not an `Error` instance.\n */\nconst toErrorMessage = (err: unknown, fallback: string): string =>\n err instanceof Error ? err.message : fallback;\n\n/**\n * Body of the \"Ver atividade\" modal: renders the read-only question cards\n * (with answer key + resolution) or an empty-state message. Kept as a separate\n * component to keep the parent's cognitive complexity low.\n */\nconst ViewQuestionsModalBody = ({\n questions,\n}: {\n questions: PreviewQuestion[];\n}) => {\n if (questions.length === 0) {\n return (\n <Text className=\"text-text-600 text-sm\">\n Nenhuma questão encontrada para esta atividade.\n </Text>\n );\n }\n\n return (\n <>\n {questions.map((q, index) => (\n <ActivityCardQuestionPreview\n key={q.id}\n subjectName={q.subjectName}\n subjectColor={q.subjectColor}\n iconName={q.iconName}\n bank={q.bank}\n year={q.year}\n questionType={q.questionType}\n questionTypeLabel={q.questionTypeLabel}\n statement={q.statement}\n question={q.question}\n solutionExplanation={q.solutionExplanation}\n position={q.position ?? index + 1}\n defaultExpanded\n value={q.id}\n />\n ))}\n </>\n );\n};\n\n/** Result shape returned by the quiz/activity question endpoints helpers */\ntype QuestionsEndpointResult = {\n response?: {\n data?: { data?: { questions?: Question[]; questionIds?: string[] } };\n };\n error?: Error;\n};\n\n/**\n * Resolve the activity's questions (with answer key + resolution) for the\n * \"Ver atividade\" modal: tries the quiz endpoint, falls back to the activity\n * endpoint, then loads the full questions via `/questions/by-ids`.\n * Kept at module scope to keep the component's cognitive complexity low.\n *\n * @returns the converted preview questions (empty when none are found)\n * @throws when both endpoints fail outright\n */\nconst loadViewQuestions = async (\n tryQuiz: () => Promise<QuestionsEndpointResult>,\n tryActivity: () => Promise<QuestionsEndpointResult>,\n fetchByIds: (ids: string[]) => Promise<Question[]>\n): Promise<PreviewQuestion[]> => {\n const { response: quizResponse, error: quizError } = await tryQuiz();\n let ids = extractQuestionIds(quizResponse);\n\n if (ids.length === 0) {\n const { response: activityResponse, error: activityError } =\n await tryActivity();\n\n // If both endpoints failed outright, surface the error instead of silently\n // showing an empty modal.\n if (!quizResponse && !activityResponse) {\n throw (\n quizError ??\n activityError ??\n new Error('Erro ao buscar questões da atividade. Tente novamente.')\n );\n }\n\n ids = extractQuestionIds(activityResponse);\n }\n\n if (ids.length === 0) {\n return [];\n }\n\n const fullQuestions = await fetchByIds(ids);\n return fullQuestions.map((q) => convertQuestionToPreview(q));\n};\n\n/**\n * Return the average-score card label depending on how many students were assigned to the activity.\n * @param totalStudents - total students attributed to the activity\n * @returns singular label when only one student is attributed, plural label otherwise\n */\nconst getAverageScoreLabel = (totalStudents: number): string =>\n totalStudents === 1 ? 'Nota do Aluno' : 'Média da Turma';\n\n/**\n * ActivityDetails component\n * Displays detailed information about an activity including statistics and student progress\n */\nexport const ActivityDetails = ({\n activityId,\n apiClient,\n onBack,\n emptyStateImage,\n mapSubjectNameToEnum,\n onDownloadAnswerSheet,\n}: ActivityDetailsProps) => {\n const { isMobile } = useMobile();\n const statsGridColsClass = isMobile ? 'grid-cols-2' : 'grid-cols-5';\n\n // Pagination and sorting state\n const [page, setPage] = useState(1);\n const [limit, setLimit] = useState(10);\n const [sortBy, setSortBy] = useState<\n 'name' | 'score' | 'answeredAt' | undefined\n >(undefined);\n const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | undefined>(\n undefined\n );\n\n // Data state\n const [data, setData] = useState<ActivityDetailsData | null>(null);\n const [correctionData, setCorrectionData] =\n useState<StudentActivityCorrectionData | null>(null);\n\n // Loading/Error state\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n // Modal state\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [isViewOnlyModal, setIsViewOnlyModal] = useState(false);\n const [correctionError, setCorrectionError] = useState<string | null>(null);\n\n // PDF download state\n const [activityQuestions, setActivityQuestions] = useState<PreviewQuestion[]>(\n []\n );\n const [isLoadingQuestions, setIsLoadingQuestions] = useState(false);\n const [shouldPrint, setShouldPrint] = useState(false);\n // \"Ver atividade\" modal state\n const [isViewModalOpen, setIsViewModalOpen] = useState(false);\n // Questions WITH answer key + resolution, used only by the \"Ver atividade\" modal\n // (kept separate from `activityQuestions`, which feeds the student-facing PDF download)\n const [viewQuestions, setViewQuestions] = useState<PreviewQuestion[]>([]);\n const [activityQuestionsError, setActivityQuestionsError] = useState<\n string | null\n >(null);\n\n // Toast store for notifications\n const addToast = useToastStore((state) => state.addToast);\n\n // Use activity details hook\n const {\n fetchActivityDetails,\n fetchStudentCorrection,\n safeFetchStudentFeedback,\n submitObservation,\n submitQuestionCorrection,\n } = useActivityDetails(apiClient);\n\n // Use questions list hook for fetching questions by IDs\n // Store hook factory in ref to preserve identity across renders\n const hookFactoryRef = useRef<ReturnType<\n typeof createUseQuestionsList\n > | null>(null);\n const apiClientRef = useRef<BaseApiClient | null>(null);\n\n // Create hook factory only when apiClient changes\n if (apiClientRef.current !== apiClient || !hookFactoryRef.current) {\n hookFactoryRef.current = createUseQuestionsList(apiClient);\n apiClientRef.current = apiClient;\n }\n\n const { fetchQuestionsByIds } = hookFactoryRef.current();\n\n /**\n * Reset PDF/question state when activityId changes\n * Prevents printing stale data when navigating between activities\n */\n useEffect(() => {\n setActivityQuestions([]);\n setViewQuestions([]);\n setIsLoadingQuestions(false);\n setShouldPrint(false);\n setActivityQuestionsError(null);\n }, [activityId]);\n\n /**\n * Fetch activity details when params change\n */\n useEffect(() => {\n const loadData = async () => {\n if (!activityId) return;\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchActivityDetails(activityId, {\n page,\n limit,\n sortBy,\n sortOrder,\n });\n setData(result);\n } catch (err) {\n setError(toErrorMessage(err, 'Erro ao carregar detalhes'));\n } finally {\n setLoading(false);\n }\n };\n\n loadData();\n }, [activityId, page, limit, sortBy, sortOrder, fetchActivityDetails]);\n\n /**\n * Handle correct activity button click\n */\n const handleCorrectActivity = useCallback(\n async (studentId: string) => {\n const student = data?.students.find((s) => s.studentId === studentId);\n if (!student || !activityId) return;\n\n const isViewOnly =\n student.status !== STUDENT_ACTIVITY_STATUS.AGUARDANDO_CORRECAO;\n setIsViewOnlyModal(isViewOnly);\n\n setCorrectionError(null);\n try {\n const [apiResponse, feedbackResponse] = await Promise.all([\n fetchStudentCorrection(activityId, studentId),\n safeFetchStudentFeedback(activityId, studentId),\n ]);\n\n // Convert API response to StudentActivityCorrectionData format\n const correction = convertApiResponseToCorrectionData(\n apiResponse,\n studentId,\n student.studentName || 'Aluno',\n feedbackResponse?.teacherFeedback ?? undefined,\n feedbackResponse?.attachment ?? undefined\n );\n setCorrectionData(correction);\n setIsModalOpen(true);\n } catch (err) {\n console.error('Failed to fetch student correction:', err);\n setCorrectionError(\n err instanceof Error\n ? err.message\n : 'Erro ao carregar dados de correção'\n );\n }\n },\n [\n data?.students,\n activityId,\n fetchStudentCorrection,\n safeFetchStudentFeedback,\n ]\n );\n\n /**\n * Handle modal close\n */\n const handleCloseModal = useCallback(() => {\n setIsModalOpen(false);\n }, []);\n\n /**\n * Handle observation submit\n * @param studentId - Student ID from modal (passed explicitly to avoid stale closure)\n * @param observation - Observation text\n * @param files - Attached files (only first file is used)\n */\n const handleObservationSubmit = useCallback(\n async (\n studentId: string,\n observation: string,\n files: File[],\n existingAttachment: string | null\n ) => {\n if (!activityId || !studentId) return;\n try {\n const file = files.length > 0 ? files[0] : null;\n await submitObservation(\n activityId,\n studentId,\n observation,\n file,\n existingAttachment\n );\n const feedbackResponse = await safeFetchStudentFeedback(\n activityId,\n studentId\n );\n\n setCorrectionData((prev) =>\n prev\n ? {\n ...prev,\n observation:\n feedbackResponse === null\n ? prev.observation\n : (feedbackResponse.teacherFeedback ?? undefined),\n attachment:\n feedbackResponse === null\n ? prev.attachment\n : (feedbackResponse.attachment ?? undefined),\n }\n : prev\n );\n } catch (err) {\n console.error('Failed to submit observation:', err);\n }\n },\n [activityId, submitObservation, safeFetchStudentFeedback]\n );\n\n /**\n * Handle question correction submit\n * @param studentId - Student ID from modal\n * @param payload - Question correction payload\n */\n const handleQuestionCorrectionSubmit = useCallback(\n async (studentId: string, payload: SaveQuestionCorrectionPayload) => {\n if (!activityId || !studentId) return;\n try {\n await submitQuestionCorrection(activityId, studentId, payload);\n } catch (err) {\n console.error('Failed to submit question correction:', err);\n throw err;\n }\n },\n [activityId, submitQuestionCorrection]\n );\n\n const isPresencial =\n data?.activity?.isDigital === false &&\n data?.activity?.subtype === ActivitySubtype.PROVA;\n\n /**\n * Convert student data to table format.\n * Presencial status remapping happens here so all consumers (modal logic,\n * view-only check, column render) see a consistent value.\n */\n const tableData: ActivityStudentTableItem[] = useMemo(() => {\n if (!data?.students) return [];\n\n return data.students.map((student) => {\n let status = student.status;\n if (isPresencial) {\n if (status === STUDENT_ACTIVITY_STATUS.AGUARDANDO_RESPOSTA) {\n status = STUDENT_ACTIVITY_STATUS.AWAITING_ANSWER_SHEET;\n } else if (status === STUDENT_ACTIVITY_STATUS.AGUARDANDO_CORRECAO) {\n status = STUDENT_ACTIVITY_STATUS.ANSWER_SHEET_RECEIVED;\n }\n }\n return {\n id: student.studentId,\n studentId: student.studentId,\n studentName: student.studentName,\n status,\n answeredAt: student.answeredAt,\n timeSpent: student.timeSpent,\n score: student.score,\n };\n });\n }, [data?.students, isPresencial]);\n\n /**\n * Table columns configuration\n */\n const columns = useMemo(\n () =>\n createTableColumns(\n handleCorrectActivity,\n isPresencial,\n onDownloadAnswerSheet\n ),\n [handleCorrectActivity, isPresencial, onDownloadAnswerSheet]\n );\n\n /**\n * Handle table parameters change\n */\n const handleTableParamsChange = (params: TableParams) => {\n if (params.page) setPage(params.page);\n if (params.limit) setLimit(params.limit);\n if (params.sortBy !== undefined) {\n // Map table column keys to API parameter names\n const sortByMap: Record<string, 'name' | 'score' | 'answeredAt'> = {\n studentName: 'name',\n answeredAt: 'answeredAt',\n score: 'score',\n };\n setSortBy(params.sortBy ? sortByMap[params.sortBy] : undefined);\n }\n if (params.sortOrder !== undefined) {\n setSortOrder(params.sortOrder);\n }\n };\n\n const orderedQuestions = useMemo(\n () => normalizeWithPositions(activityQuestions),\n [activityQuestions]\n );\n\n /**\n * Use PDF print hook\n */\n const { contentRef, handlePrint } = useQuestionsPdfPrint(orderedQuestions);\n\n /**\n * Extract questions from API response\n * Handles both direct questions array and questionIds array\n */\n const extractQuestionsFromResponse = useCallback(\n async (\n response:\n | Awaited<\n ReturnType<\n typeof apiClient.get<{\n data: { questions?: Question[]; questionIds?: string[] };\n }>\n >\n >\n | undefined\n ): Promise<Question[]> => {\n if (!response?.data?.data) {\n return [];\n }\n\n if (response.data.data.questions) {\n return response.data.data.questions;\n }\n\n if (response.data.data.questionIds) {\n return await fetchQuestionsByIds(response.data.data.questionIds);\n }\n\n return [];\n },\n [fetchQuestionsByIds]\n );\n\n /**\n * Try to fetch questions from quiz endpoint\n */\n const tryFetchQuizResponse = useCallback(async () => {\n try {\n const response = await apiClient.get<{\n data: { questions?: Question[]; questionIds?: string[] };\n }>(`/activities/${activityId}/quiz`);\n return { response, error: undefined };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return { response: undefined, error };\n }\n }, [activityId, apiClient]);\n\n /**\n * Try to fetch questions from activity endpoint\n */\n const tryFetchActivityResponse = useCallback(async () => {\n try {\n const response = await apiClient.get<{\n data: { questions?: Question[]; questionIds?: string[] };\n }>(`/activities/${activityId}`);\n return { response, error: undefined };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return { response: undefined, error };\n }\n }, [activityId, apiClient]);\n\n /**\n * Fetch questions from both endpoints (quiz and activity)\n * Returns questions array or throws error if both fail\n */\n const fetchQuestionsFromEndpoints = useCallback(async (): Promise<\n Question[]\n > => {\n // Try quiz endpoint first\n const { response: quizResponse, error: quizError } =\n await tryFetchQuizResponse();\n let questions = await extractQuestionsFromResponse(quizResponse);\n\n // If quiz endpoint didn't return questions, try activity endpoint\n if (questions.length === 0) {\n const { response: activityResponse, error: activityError } =\n await tryFetchActivityResponse();\n\n // If both endpoints failed, throw error\n if (!quizResponse && !activityResponse) {\n const errorMessage =\n quizError?.message ||\n activityError?.message ||\n 'Erro ao buscar questões da atividade. Tente novamente.';\n throw new Error(errorMessage);\n }\n\n questions = await extractQuestionsFromResponse(activityResponse);\n }\n\n return questions;\n }, [\n tryFetchQuizResponse,\n tryFetchActivityResponse,\n extractQuestionsFromResponse,\n ]);\n\n /**\n * Handle fetch error and show toast notification\n */\n const handleQuestionsFetchError = useCallback(\n (errorMessage: string) => {\n console.error('Erro ao buscar questões da atividade:', errorMessage);\n setActivityQuestions([]);\n setActivityQuestionsError(errorMessage);\n addToast({\n title: 'Erro ao carregar questões',\n description: errorMessage,\n variant: 'solid',\n action: 'warning',\n position: 'top-right',\n });\n },\n [addToast]\n );\n\n /**\n * Fetch activity questions for PDF download\n * @returns Promise that resolves to true if questions were successfully loaded (non-empty), false otherwise\n */\n const fetchActivityQuestions = useCallback(async (): Promise<boolean> => {\n if (!activityId) return false;\n\n setIsLoadingQuestions(true);\n setActivityQuestionsError(null);\n try {\n const questions = await fetchQuestionsFromEndpoints();\n\n // Convert questions to PreviewQuestion format\n const previewQuestions = questions.map((q) =>\n convertQuestionToPreview(q)\n );\n setActivityQuestions(previewQuestions);\n setActivityQuestionsError(null);\n\n // Notify user if no questions were found\n if (previewQuestions.length === 0) {\n addToast({\n title: 'Nenhuma questão encontrada',\n description: 'Esta atividade não possui questões para download.',\n variant: 'solid',\n action: 'info',\n position: 'top-right',\n });\n }\n\n return previewQuestions.length > 0;\n } catch (err) {\n const errorMessage =\n err instanceof Error\n ? err.message\n : 'Erro ao buscar questões da atividade. Tente novamente.';\n handleQuestionsFetchError(errorMessage);\n return false;\n } finally {\n setIsLoadingQuestions(false);\n }\n }, [fetchQuestionsFromEndpoints, handleQuestionsFetchError, addToast]);\n\n /**\n * Handle download PDF button click\n */\n const handleDownloadPdf = useCallback(async () => {\n try {\n // If questions are not loaded yet, fetch them first\n if (activityQuestions.length === 0) {\n const success = await fetchActivityQuestions();\n // Only set print flag if fetch succeeded and returned questions\n setShouldPrint(success);\n } else if (activityQuestions.length > 0) {\n setShouldPrint(true);\n } else {\n setShouldPrint(false);\n }\n } catch {\n // Error already handled in fetchActivityQuestions, ensure print flag is false\n setShouldPrint(false);\n }\n }, [activityQuestions.length, fetchActivityQuestions]);\n\n /**\n * Fetch the activity questions WITH the answer key + resolution for the\n * \"Ver atividade\" modal. Unlike the PDF download (student-facing quiz), this\n * resolves the question ids and loads them via `/questions/by-ids`, which\n * returns `options.isCorrect` and `solutionExplanation`. On no questions the\n * `viewQuestions` list is cleared so the modal shows an empty state; on a real\n * fetch failure a feedback toast is shown via `handleQuestionsFetchError`.\n */\n const fetchViewQuestions = useCallback(async (): Promise<void> => {\n if (!activityId) return;\n\n setIsLoadingQuestions(true);\n setActivityQuestionsError(null);\n try {\n const questions = await loadViewQuestions(\n tryFetchQuizResponse,\n tryFetchActivityResponse,\n fetchQuestionsByIds\n );\n setViewQuestions(questions);\n } catch (err) {\n handleQuestionsFetchError(\n toErrorMessage(\n err,\n 'Erro ao buscar questões da atividade. Tente novamente.'\n )\n );\n } finally {\n setIsLoadingQuestions(false);\n }\n }, [\n activityId,\n tryFetchQuizResponse,\n tryFetchActivityResponse,\n fetchQuestionsByIds,\n handleQuestionsFetchError,\n ]);\n\n /**\n * Handle \"Ver atividade\" button click: ensure questions (with answer key) are\n * loaded, then open the read-only questions modal. The modal always opens — if\n * no questions are found it shows an empty-state message.\n */\n const handleViewActivity = useCallback(async () => {\n if (viewQuestions.length === 0) {\n await fetchViewQuestions();\n }\n setIsViewModalOpen(true);\n }, [viewQuestions.length, fetchViewQuestions]);\n\n /**\n * Effect to handle PDF printing when shouldPrint flag is set\n * Waits for contentRef to be ready and questions to be loaded\n */\n useEffect(() => {\n if (!shouldPrint) {\n return;\n }\n\n // Guard against empty activityQuestions - reset flag if empty\n if (activityQuestions.length === 0) {\n setShouldPrint(false);\n return;\n }\n\n // Check if all conditions are met for printing\n if (\n contentRef.current &&\n handlePrint &&\n typeof handlePrint === 'function'\n ) {\n // Defer handlePrint out of React's commit phase. handlePrint calls\n // window.open() synchronously; running it inside this passive effect\n // re-enters the scheduler (Sentry Replay also patches window.open) and\n // throws \"Error: Should not already be working\" (FRONTEND-ALUNO-WEB-EF).\n // A macrotask lets the commit finish before the print window opens.\n setShouldPrint(false);\n const printFn = handlePrint;\n setTimeout(() => printFn(), 0);\n return;\n }\n\n // If conditions aren't met but shouldPrint is true, reset it to prevent getting stuck\n // This handles cases where contentRef or handlePrint aren't ready yet\n setShouldPrint(false);\n }, [shouldPrint, activityQuestions.length, contentRef, handlePrint]);\n\n /**\n * Handle back navigation\n */\n const handleBack = () => {\n if (onBack) {\n onBack();\n }\n };\n\n /**\n * Get subject info for icon display\n */\n const subjectEnum =\n data?.activity?.subjectName && mapSubjectNameToEnum\n ? mapSubjectNameToEnum(data.activity.subjectName)\n : null;\n const subjectInfo: SubjectData | null = subjectEnum\n ? getSubjectInfo(subjectEnum)\n : null;\n\n // Loading state\n if (loading && !data) {\n return (\n <div className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\">\n <div className=\"flex flex-col w-full h-full max-w-[1150px] mx-auto z-10 lg:px-0 px-4 pt-4 gap-4\">\n {/* Breadcrumb Skeleton */}\n <div className=\"flex items-center gap-2 py-4\">\n <SkeletonText width={100} height={14} />\n </div>\n\n {/* Header Card Skeleton */}\n <SkeletonRounded className=\"w-full h-[120px]\" />\n\n {/* Statistics Cards Skeleton */}\n <div className={cn('grid gap-5', statsGridColsClass)}>\n {[\n 'total-students',\n 'completed',\n 'pending',\n 'avg-score',\n 'avg-time',\n ].map((id) => (\n <SkeletonRounded key={id} className=\"w-full h-[150px]\" />\n ))}\n </div>\n\n {/* Table Skeleton */}\n <div className=\"w-full bg-background rounded-xl p-6\">\n <SkeletonTable rows={5} columns={6} showHeader />\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !data) {\n return (\n <div className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5\">\n <div className=\"flex flex-col w-full h-full max-w-[1150px] mx-auto z-10 lg:px-0 px-4 pt-4\">\n <EmptyState\n image={emptyStateImage}\n title=\"Erro ao carregar detalhes\"\n description={\n error || 'Não foi possível carregar os detalhes da atividade'\n }\n />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\">\n {/* Main container */}\n <div className=\"flex flex-col w-full h-full max-w-[1150px] mx-auto z-10 lg:px-0 px-4 pt-4 gap-4\">\n {/* Breadcrumb */}\n <div className=\"flex items-center gap-2 py-4\">\n <button\n onClick={handleBack}\n className=\"text-text-500 hover:text-text-700 text-sm font-bold underline\"\n >\n Atividades\n </button>\n <CaretRightIcon size={16} className=\"text-text-500\" />\n <Text className=\"text-text-950 text-sm font-bold\">\n {data.activity?.title || 'Atividade'}\n </Text>\n </div>\n\n {/* Activity header card */}\n {data.activity && (\n <div className=\"bg-background rounded-xl p-4 flex flex-col gap-2\">\n <div className=\"flex justify-between items-start\">\n <div className=\"flex flex-col gap-2\">\n <Text className=\"text-2xl font-bold text-text-950\">\n {data.activity.title}\n </Text>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <Text className=\"text-sm text-text-500\">\n Início{' '}\n {data.activity.startDate\n ? formatActivityDateToBrazilian(data.activity.startDate)\n : '00/00/0000'}\n </Text>\n <span className=\"w-1 h-1 rounded-full bg-text-500\" />\n <Text className=\"text-sm text-text-500\">\n Prazo final{' '}\n {data.activity.finalDate\n ? formatActivityDateToBrazilian(data.activity.finalDate)\n : '00/00/0000'}\n </Text>\n <span className=\"w-1 h-1 rounded-full bg-text-500\" />\n <Text className=\"text-sm text-text-500\">\n {data.activity.schoolName}\n </Text>\n <span className=\"w-1 h-1 rounded-full bg-text-500\" />\n <Text className=\"text-sm text-text-500\">\n {data.activity.year}\n </Text>\n <span className=\"w-1 h-1 rounded-full bg-text-500\" />\n {subjectInfo ? (\n <div className=\"flex items-center gap-1\">\n <span\n className={cn(\n 'w-[21px] h-[21px] flex items-center justify-center rounded-sm text-text-950 shrink-0',\n subjectInfo.colorClass\n )}\n >\n {subjectInfo.icon}\n </span>\n <Text className=\"text-sm text-text-500\">\n {data.activity.subjectName}\n </Text>\n </div>\n ) : (\n <Text className=\"text-sm text-text-500\">\n {data.activity.subjectName}\n </Text>\n )}\n <span className=\"w-1 h-1 rounded-full bg-text-500\" />\n <Text className=\"text-sm text-text-500\">\n {data.activity.className}\n </Text>\n </div>\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex items-center gap-2\">\n <Button\n size=\"small\"\n variant=\"outline\"\n action=\"primary\"\n onClick={handleViewActivity}\n disabled={isLoadingQuestions}\n iconLeft={<EyeIcon size={16} />}\n className=\"gap-2\"\n >\n Ver Atividade\n </Button>\n <Button\n size=\"small\"\n onClick={handleDownloadPdf}\n disabled={isLoadingQuestions}\n iconLeft={<DownloadSimpleIcon size={16} />}\n className=\"bg-primary-950 text-text gap-2\"\n >\n {isLoadingQuestions ? 'Carregando...' : 'Baixar Atividade'}\n </Button>\n </div>\n {activityQuestionsError && (\n <div className=\"flex items-center gap-2 max-w-[300px]\">\n <WarningCircleIcon\n size={16}\n className=\"text-error-600 shrink-0\"\n weight=\"fill\"\n />\n <Text className=\"text-error-700 text-xs\">\n {activityQuestionsError}\n </Text>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Statistics cards */}\n <div className={cn('grid gap-5', statsGridColsClass)}>\n {/* Completion percentage */}\n <div className=\"border border-border-50 rounded-xl py-4 px-0 flex flex-col items-center justify-center gap-2 bg-primary-50\">\n <div className=\"relative w-[90px] h-[90px]\">\n <svg className=\"w-full h-full transform -rotate-90\">\n <circle\n cx=\"45\"\n cy=\"45\"\n r=\"40\"\n stroke=\"var(--color-primary-100)\"\n strokeWidth=\"8\"\n fill=\"none\"\n />\n <circle\n cx=\"45\"\n cy=\"45\"\n r=\"40\"\n stroke=\"var(--color-primary-700)\"\n strokeWidth=\"8\"\n fill=\"none\"\n strokeDasharray={`${(data.generalStats.completionPercentage / 100) * 251.2} 251.2`}\n strokeLinecap=\"round\"\n />\n </svg>\n <div className=\"absolute inset-0 flex flex-col items-center justify-center\">\n <Text className=\"text-xl font-medium text-primary-600\">\n {Math.round(data.generalStats.completionPercentage)}%\n </Text>\n <Text className=\"text-2xs font-bold text-text-600 uppercase\">\n Concluído\n </Text>\n </div>\n </div>\n </div>\n\n {/* Average score */}\n <div className=\"border border-border-50 rounded-xl py-4 px-3 flex flex-col items-center justify-center gap-1 bg-warning-background\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-warning-300\">\n <StarIcon size={16} className=\"text-white\" weight=\"regular\" />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-warning-600\">\n {getAverageScoreLabel(data.pagination.total)}\n </Text>\n <Text className=\"text-xl font-bold text-warning-600\">\n {data.generalStats.averageScore.toFixed(1)}\n </Text>\n </div>\n\n {/* Most correct questions */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-success-200\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-indicator-positive\">\n <MedalIcon size={16} className=\"text-text-950\" weight=\"regular\" />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-success-700\">\n Questões com mais acertos\n </Text>\n <Text className=\"text-xl font-bold text-success-700\">\n {formatQuestionNumbers(data.questionStats.mostCorrect)}\n </Text>\n </div>\n\n {/* Most incorrect questions */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-error-100\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-indicator-negative\">\n <WarningCircleIcon\n size={16}\n className=\"text-white\"\n weight=\"regular\"\n />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-error-700\">\n Questões com mais erros\n </Text>\n <Text className=\"text-xl font-bold text-error-700\">\n {formatQuestionNumbers(data.questionStats.mostIncorrect)}\n </Text>\n </div>\n\n {/* Not answered questions */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-info-background\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-info-500\">\n <WarningCircleIcon\n size={16}\n className=\"text-white\"\n weight=\"regular\"\n />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-info-700\">\n Questões não respondidas\n </Text>\n <Text className=\"text-xl font-bold text-info-700\">\n {formatQuestionNumbers(data.questionStats.notAnswered)}\n </Text>\n </div>\n </div>\n\n {/* Correction error message */}\n {correctionError && (\n <div className=\"w-full bg-error-50 border border-error-200 rounded-xl p-4 flex items-center gap-3\">\n <WarningCircleIcon\n size={20}\n className=\"text-error-600\"\n weight=\"fill\"\n />\n <Text className=\"text-error-700 text-sm\">{correctionError}</Text>\n </div>\n )}\n\n {/* Students table */}\n <div className=\"w-full bg-background rounded-xl p-6 space-y-4\">\n <TableProvider<ActivityStudentTableItem>\n data={tableData}\n headers={columns}\n loading={false}\n variant=\"borderless\"\n enableTableSort\n enablePagination\n paginationConfig={{\n itemLabel: 'alunos',\n itemsPerPageOptions: [10, 20, 50],\n defaultItemsPerPage: 10,\n totalItems: data.pagination.total,\n totalPages: data.pagination.totalPages,\n }}\n emptyState={{\n component: (\n <EmptyState\n image={emptyStateImage}\n title=\"Nenhum aluno encontrado\"\n description=\"Não há alunos matriculados nesta atividade\"\n />\n ),\n }}\n onParamsChange={handleTableParamsChange}\n >\n {({ table, pagination }) => (\n <>\n {table}\n {pagination}\n </>\n )}\n </TableProvider>\n </div>\n </div>\n\n {/* Correct Activity Modal */}\n <CorrectActivityModal\n isOpen={isModalOpen}\n onClose={handleCloseModal}\n data={correctionData}\n isViewOnly={isViewOnlyModal}\n onObservationSubmit={handleObservationSubmit}\n onQuestionCorrectionSubmit={handleQuestionCorrectionSubmit}\n />\n\n {/* Ver atividade (read-only questions with answer key + resolution) */}\n <Modal\n isOpen={isViewModalOpen}\n onClose={() => setIsViewModalOpen(false)}\n title=\"Ver atividade\"\n size=\"xl\"\n >\n <div className=\"flex flex-col gap-3 max-h-[70vh] overflow-y-auto pr-1\">\n <ViewQuestionsModalBody questions={viewQuestions} />\n </div>\n </Modal>\n\n {/* Hidden PDF content for printing */}\n <div style={{ display: 'none' }}>\n <QuestionsPdfContent ref={contentRef} questions={orderedQuestions} />\n </div>\n </div>\n );\n};\n\nexport default ActivityDetails;\n","import { useCallback } from 'react';\nimport type { BaseApiClient } from '../types/api';\nimport type {\n ActivityDetailsData,\n ActivityDetailsQueryParams,\n ActivityDetailsApiResponse,\n QuizResponse,\n PresignedUrlResponse,\n} from '../types/activityDetails';\nimport type {\n QuestionsAnswersByStudentResponse,\n SaveQuestionCorrectionPayload,\n} from '../utils/studentActivityCorrection';\n\n/**\n * Hook return type for activity details\n */\nexport interface StudentFeedbackResponse {\n teacherFeedback: string | null;\n attachment: string | null;\n}\n\nexport interface UseActivityDetailsReturn {\n /**\n * Fetch activity details from API\n * @param id - Activity ID\n * @param params - Query parameters for pagination\n * @returns Activity details data\n */\n fetchActivityDetails: (\n id: string,\n params?: ActivityDetailsQueryParams\n ) => Promise<ActivityDetailsData>;\n /**\n * Fetch student correction data from API\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @returns Student answers response data\n */\n fetchStudentCorrection: (\n activityId: string,\n studentId: string\n ) => Promise<QuestionsAnswersByStudentResponse>;\n /**\n * Fetch teacher feedback for a student activity\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @returns Teacher feedback and attachment\n */\n fetchStudentFeedback: (\n activityId: string,\n studentId: string\n ) => Promise<StudentFeedbackResponse>;\n /**\n * Fetch teacher feedback for a student activity, returning null on error\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @returns Teacher feedback and attachment, or null if fetch fails\n */\n safeFetchStudentFeedback: (\n activityId: string,\n studentId: string\n ) => Promise<StudentFeedbackResponse | null>;\n /**\n * Submit observation for student activity\n * @param actId - Activity ID\n * @param studentId - Student ID\n * @param observation - Observation text\n * @param file - Attached file (optional)\n * @returns The attachment URL if a file was uploaded, null otherwise\n */\n submitObservation: (\n actId: string,\n studentId: string,\n observation: string,\n file: File | null,\n existingAttachment: string | null\n ) => Promise<string | null>;\n /**\n * Submit question correction for student activity\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @param payload - Question correction payload\n */\n submitQuestionCorrection: (\n activityId: string,\n studentId: string,\n payload: SaveQuestionCorrectionPayload\n ) => Promise<void>;\n}\n\n/**\n * Build query parameters for API request\n */\nconst buildQueryParams = (\n params?: ActivityDetailsQueryParams\n): Record<string, unknown> => {\n const paramsObj: Record<string, unknown> = {};\n if (params?.page) paramsObj.page = params.page;\n if (params?.limit) paramsObj.limit = params.limit;\n if (params?.sortBy) paramsObj.sortBy = params.sortBy;\n if (params?.sortOrder) paramsObj.sortOrder = params.sortOrder;\n if (params?.status) paramsObj.status = params.status;\n return paramsObj;\n};\n\n/**\n * Hook for managing activity details API calls\n * Provides functions to fetch activity details, student corrections, and submit observations/corrections\n *\n * @param apiClient - API client instance for making requests\n * @returns Hook return object with API functions\n *\n * @example\n * ```tsx\n * import { useActivityDetails } from 'analytica-frontend-lib';\n * import { useApi } from './services/apiService';\n *\n * function ActivityDetailsPage() {\n * const api = useApi();\n * const { fetchActivityDetails, fetchStudentCorrection } = useActivityDetails(api);\n *\n * // Use functions...\n * }\n * ```\n */\nexport const useActivityDetails = (\n apiClient: BaseApiClient\n): UseActivityDetailsReturn => {\n /**\n * Fetch activity details from API\n * @param id - Activity ID\n * @param params - Query parameters for pagination\n * @returns Activity details data\n */\n const fetchActivityDetails = useCallback(\n async (\n id: string,\n params?: ActivityDetailsQueryParams\n ): Promise<ActivityDetailsData> => {\n const queryParams = buildQueryParams(params);\n\n const [detailsResponse, quizResponse] = await Promise.all([\n apiClient.get<ActivityDetailsApiResponse>(`/activities/${id}/details`, {\n params: queryParams,\n }),\n apiClient.get<QuizResponse>(`/activities/${id}/quiz`).catch(() => null),\n ]);\n\n return {\n ...detailsResponse.data.data,\n activity: quizResponse?.data?.data,\n };\n },\n [apiClient]\n );\n\n /**\n * Fetch student correction data from API\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @returns Student answers response data\n */\n const fetchStudentCorrection = useCallback(\n async (\n activityId: string,\n studentId: string\n ): Promise<QuestionsAnswersByStudentResponse> => {\n const response = await apiClient.get<QuestionsAnswersByStudentResponse>(\n `/questions/activity/${activityId}/user/${studentId}/answers`\n );\n return response.data;\n },\n [apiClient]\n );\n\n /**\n * Fetch teacher feedback for a student activity\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @returns Teacher feedback and attachment\n */\n const fetchStudentFeedback = useCallback(\n async (\n activityId: string,\n studentId: string\n ): Promise<StudentFeedbackResponse> => {\n const response = await apiClient.get<{\n data: {\n feedback: {\n teacherFeedback: string | null;\n attachment: string | null;\n };\n };\n }>(`/activities/${activityId}/students/${studentId}/feedback`);\n return response.data.data.feedback;\n },\n [apiClient]\n );\n\n const safeFetchStudentFeedback = useCallback(\n async (\n activityId: string,\n studentId: string\n ): Promise<StudentFeedbackResponse | null> => {\n try {\n return await fetchStudentFeedback(activityId, studentId);\n } catch (err) {\n console.warn('Failed to fetch student feedback:', err);\n return null;\n }\n },\n [fetchStudentFeedback]\n );\n\n /**\n * Submit observation for student activity\n * @param actId - Activity ID\n * @param studentId - Student ID\n * @param observation - Observation text\n * @param file - Attached file (optional)\n * @returns The attachment URL if a file was uploaded, null otherwise\n */\n const submitObservation = useCallback(\n async (\n actId: string,\n studentId: string,\n observation: string,\n file: File | null,\n existingAttachment: string | null\n ): Promise<string | null> => {\n let attachmentUrl: string | null = existingAttachment;\n\n if (file) {\n const presignedRes = await apiClient.post<PresignedUrlResponse>(\n '/user/get-pre-signed-url',\n {\n fileName: file.name,\n mimeType: file.type,\n fileSize: file.size,\n }\n );\n\n const { signedUrl, publicUrl } = presignedRes.data.data;\n\n await fetch(signedUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type },\n }).then((response) => {\n if (!response.ok) {\n throw new Error('Falha ao fazer upload do arquivo');\n }\n });\n\n attachmentUrl = publicUrl;\n }\n\n await apiClient.patch(\n `/activities/${actId}/students/${studentId}/feedback`,\n {\n teacherFeedback: observation,\n attachment: attachmentUrl,\n }\n );\n\n return attachmentUrl;\n },\n [apiClient]\n );\n\n /**\n * Submit question correction for student activity\n * @param activityId - Activity ID\n * @param studentId - Student ID\n * @param payload - Question correction payload\n */\n const submitQuestionCorrection = useCallback(\n async (\n activityId: string,\n studentId: string,\n payload: SaveQuestionCorrectionPayload\n ): Promise<void> => {\n await apiClient.post(\n `/activities/${activityId}/students/${studentId}/questions/correction`,\n payload\n );\n },\n [apiClient]\n );\n\n return {\n fetchActivityDetails,\n fetchStudentCorrection,\n fetchStudentFeedback,\n safeFetchStudentFeedback,\n submitObservation,\n submitQuestionCorrection,\n };\n};\n","import { useCallback, useEffect, useRef, ChangeEvent } from 'react';\nimport Modal from '../Modal/Modal';\nimport Stepper from '../Stepper/Stepper';\nimport Chips from '../Chips/Chips';\nimport Input from '../Input/Input';\nimport TextArea from '../TextArea/TextArea';\nimport Text from '../Text/Text';\nimport { RadioGroup, RadioGroupItem } from '../Radio/Radio';\nimport DateTimeInput from '../DateTimeInput/DateTimeInput';\nimport { useSendActivityModalStore } from './hooks/useSendActivityModal';\nimport {\n SendActivityModalProps,\n SendActivityModalInitialData,\n ActivitySubtype,\n ActivityMode,\n SendActivityFormData,\n ACTIVITY_TYPE_OPTIONS,\n ACTIVITY_MODE_OPTIONS,\n CategoryConfig,\n} from './types';\nimport {\n RecipientStep,\n DeadlineStep,\n SendModalFooter,\n SendModalError,\n useDateTimeHandlers,\n useCategoryInitialization,\n useCategorySync,\n} from '../shared/SendModalBase';\n\n/**\n * Stepper steps configuration for activity mode\n */\nconst ACTIVITY_STEPPER_STEPS = [\n { id: 'activity', label: 'Atividade', state: 'pending' as const },\n { id: 'recipient', label: 'Destinatário', state: 'pending' as const },\n { id: 'deadline', label: 'Prazo', state: 'pending' as const },\n];\n\n/**\n * Stepper steps configuration for exam mode\n */\nconst EXAM_STEPPER_STEPS = [\n { id: 'exam', label: 'Prova', state: 'pending' as const },\n { id: 'recipient', label: 'Destinatário', state: 'pending' as const },\n { id: 'deadline', label: 'Prazo', state: 'pending' as const },\n];\n\n/**\n * Modal configuration constants\n */\nconst MAX_STEPS = 3;\n\n/**\n * SendActivityModal component for sending activities to students\n *\n * A multi-step modal with 3 steps:\n * 1. Activity - Select type, title, and optional notification message\n * 2. Recipient - Select students from hierarchical structure using CheckboxGroup\n * 3. Deadline - Set start/end dates and retry option\n */\nconst SendActivityModal = ({\n isOpen,\n onClose,\n onSubmit,\n categories: initialCategories,\n onCategoriesChange,\n isLoading = false,\n onError,\n initialData,\n enableExamMode = false,\n isInPersonExam = false,\n}: SendActivityModalProps) => {\n const store = useSendActivityModalStore();\n const reset = useSendActivityModalStore((state) => state.reset);\n const setFormData = useSendActivityModalStore((state) => state.setFormData);\n const setCategories = useSendActivityModalStore(\n (state) => state.setCategories\n );\n const storeCategories = useSendActivityModalStore(\n (state) => state.categories\n );\n\n /**\n * Dynamic configuration based on exam mode\n */\n const isExamMode = enableExamMode || isInPersonExam;\n const stepperSteps = isExamMode ? EXAM_STEPPER_STEPS : ACTIVITY_STEPPER_STEPS;\n const modalTitle = isExamMode ? 'Enviar prova' : 'Enviar atividade';\n const entityName = isExamMode ? 'prova' : 'atividade';\n const entityNameWithArticle = isExamMode ? 'a prova' : 'a atividade';\n\n /**\n * Track the previous initialData reference to detect changes\n */\n const prevInitialDataRef = useRef<SendActivityModalInitialData | undefined>(\n undefined\n );\n\n /**\n * Initialize categories with auto-selection when modal opens\n */\n useCategoryInitialization({\n isOpen,\n initialCategories,\n setCategories,\n onCategoriesChange,\n });\n\n /**\n * Get categoriesInitialized ref from hook\n */\n const { categoriesInitializedRef } = useCategoryInitialization({\n isOpen,\n initialCategories,\n setCategories,\n onCategoriesChange,\n });\n\n /**\n * Sync categories from parent when they change (e.g., after fetching students)\n */\n useCategorySync({\n isOpen,\n initialCategories,\n storeCategories,\n setCategories,\n categoriesInitializedRef,\n });\n\n /**\n * Apply initial data when modal opens with new data\n */\n useEffect(() => {\n if (isOpen && initialData && prevInitialDataRef.current !== initialData) {\n setFormData({\n title: initialData.title ?? '',\n subtype: initialData.subtype ?? ActivitySubtype.TAREFA,\n notification: initialData.notification ?? '',\n });\n prevInitialDataRef.current = initialData;\n }\n }, [isOpen, initialData, setFormData]);\n\n /**\n * Reset store when modal closes\n */\n useEffect(() => {\n if (!isOpen) {\n reset();\n prevInitialDataRef.current = undefined;\n }\n }, [isOpen, reset]);\n\n /**\n * Auto-set subtype for exam mode (enableExamMode without isInPersonExam)\n */\n useEffect(() => {\n if (isOpen && enableExamMode && !isInPersonExam) {\n setFormData({\n subtype: ActivitySubtype.PROVA,\n });\n }\n }, [isOpen, enableExamMode, isInPersonExam, setFormData]);\n\n /**\n * Auto-set subtype and mode for in-person exam\n */\n useEffect(() => {\n if (isOpen && isInPersonExam) {\n setFormData({\n subtype: ActivitySubtype.PROVA,\n mode: ActivityMode.PRESENCIAL,\n canRetry: false,\n });\n }\n }, [isOpen, isInPersonExam, setFormData]);\n\n /**\n * Date/time change handlers from shared hook\n */\n const {\n handleStartDateChange,\n handleStartTimeChange,\n handleFinalDateChange,\n handleFinalTimeChange,\n } = useDateTimeHandlers({ setFormData });\n\n /**\n * Handle categories change from CheckboxGroup\n */\n const handleCategoriesChange = useCallback(\n (updatedCategories: CategoryConfig[]) => {\n setCategories(updatedCategories);\n onCategoriesChange?.(updatedCategories);\n },\n [setCategories, onCategoriesChange]\n );\n\n /**\n * Handle activity type selection\n * Clears mode when switching away from PROVA\n */\n const handleActivityTypeSelect = useCallback(\n (subtype: ActivitySubtype) => {\n const update: Partial<SendActivityFormData> =\n subtype !== ActivitySubtype.PROVA\n ? { subtype, mode: undefined }\n : { subtype };\n setFormData(update);\n },\n [setFormData]\n );\n\n /**\n * Handle prova mode selection (Online / Presencial)\n */\n const handleModeSelect = useCallback(\n (mode: ActivityMode) => {\n const update: Partial<SendActivityFormData> =\n mode === ActivityMode.PRESENCIAL ? { mode, canRetry: false } : { mode };\n setFormData(update);\n },\n [setFormData]\n );\n\n /**\n * Handle title change\n */\n const handleTitleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n setFormData({ title: e.target.value });\n },\n [setFormData]\n );\n\n /**\n * Handle notification message change\n */\n const handleNotificationChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => {\n setFormData({ notification: e.target.value });\n },\n [setFormData]\n );\n\n /**\n * Handle retry option change\n */\n const handleRetryChange = useCallback(\n (value: string) => {\n setFormData({ canRetry: value === 'yes' });\n },\n [setFormData]\n );\n\n /**\n * Handle form submission\n */\n const handleSubmit = useCallback(async () => {\n // For in-person exams, always validate as exam mode since mode is auto-set\n const isValid = store.validateAllSteps(enableExamMode || isInPersonExam);\n if (!isValid) return;\n\n try {\n const formData = store.formData as SendActivityFormData;\n await onSubmit(formData);\n } catch (error) {\n if (onError) {\n onError(error);\n } else {\n console.error('Falha ao enviar atividade:', error);\n }\n }\n }, [store, onSubmit, onError, enableExamMode, isInPersonExam]);\n\n /**\n * Handle cancel button click\n */\n const handleCancel = useCallback(() => {\n onClose();\n }, [onClose]);\n\n /**\n * Render Step 1 - Activity/Exam (unique to SendActivityModal)\n * In exam mode: only shows title input\n * In activity mode: shows type selection, title, and notification\n */\n const renderActivityStep = () => {\n // Exam mode: simplified step with only title\n if (isExamMode) {\n return (\n <div className=\"flex flex-col gap-6\">\n <Input\n label=\"Título\"\n placeholder=\"Digite o título da prova\"\n value={store.formData.title || ''}\n onChange={handleTitleChange}\n variant=\"rounded\"\n required\n errorMessage={store.errors.title}\n />\n </div>\n );\n }\n\n // Activity mode: full form\n return (\n <div className=\"flex flex-col gap-6\">\n {/* Activity Type Selection - hidden for in-person exams (auto-set to PROVA) */}\n {!isInPersonExam && (\n <div>\n <Text\n size=\"sm\"\n weight=\"medium\"\n color=\"text-text-700\"\n className=\"mb-3\"\n >\n Tipo de atividade*\n </Text>\n <div className=\"flex flex-wrap gap-2\">\n {ACTIVITY_TYPE_OPTIONS.map((type) => (\n <Chips\n key={type.value}\n selected={store.formData.subtype === type.value}\n onClick={() => handleActivityTypeSelect(type.value)}\n >\n {type.label}\n </Chips>\n ))}\n </div>\n <SendModalError error={store.errors.subtype} />\n </div>\n )}\n\n {/* In-person exam indicator */}\n {isInPersonExam && (\n <div>\n <Text\n size=\"sm\"\n weight=\"medium\"\n color=\"text-text-700\"\n className=\"mb-3\"\n >\n Tipo de atividade\n </Text>\n <Chips selected disabled>\n Prova Presencial\n </Chips>\n </div>\n )}\n\n {/* Prova Mode Selection — only visible when enableExamMode and subtype is PROVA and NOT in-person exam */}\n {enableExamMode &&\n !isInPersonExam &&\n store.formData.subtype === ActivitySubtype.PROVA && (\n <div>\n <Text\n size=\"sm\"\n weight=\"medium\"\n color=\"text-text-700\"\n className=\"mb-3\"\n >\n Modo de prova*\n </Text>\n <div className=\"flex flex-wrap gap-2\">\n {ACTIVITY_MODE_OPTIONS.map((modeOption) => (\n <Chips\n key={modeOption.value}\n selected={store.formData.mode === modeOption.value}\n onClick={() => handleModeSelect(modeOption.value)}\n >\n {modeOption.label}\n </Chips>\n ))}\n </div>\n <SendModalError error={store.errors.mode} />\n </div>\n )}\n\n {/* Title Input */}\n <Input\n label=\"Título\"\n placeholder=\"Digite o título da atividade\"\n value={store.formData.title || ''}\n onChange={handleTitleChange}\n variant=\"rounded\"\n required\n errorMessage={store.errors.title}\n />\n\n {/* Notification Message */}\n <TextArea\n label=\"Mensagem da notificação\"\n placeholder=\"Digite uma mensagem para a notificação (opcional)\"\n value={store.formData.notification || ''}\n onChange={handleNotificationChange}\n />\n </div>\n );\n };\n\n /**\n * Render retry option for deadline step\n * Hidden for in-person exams since retry is always disabled\n */\n const renderRetryOption = () => {\n if (\n isInPersonExam ||\n (enableExamMode && store.formData.mode === ActivityMode.PRESENCIAL)\n ) {\n return null;\n }\n\n return (\n <div>\n <Text size=\"sm\" weight=\"medium\" color=\"text-text-700\" className=\"mb-3\">\n Permitir refazer?\n </Text>\n <RadioGroup\n value={store.formData.canRetry ? 'yes' : 'no'}\n onValueChange={handleRetryChange}\n className=\"flex flex-row gap-6\"\n >\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"yes\" id=\"radio-item-yes\" />\n <Text\n as=\"label\"\n size=\"sm\"\n color=\"text-text-700\"\n className=\"cursor-pointer\"\n htmlFor=\"radio-item-yes\"\n >\n Sim\n </Text>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"no\" id=\"radio-item-no\" />\n <Text\n as=\"label\"\n size=\"sm\"\n color=\"text-text-700\"\n className=\"cursor-pointer\"\n htmlFor=\"radio-item-no\"\n >\n Não\n </Text>\n </div>\n </RadioGroup>\n </div>\n );\n };\n\n /**\n * Render simplified deadline step for exam mode\n * Only shows \"Data da prova\" field\n */\n const renderExamDeadlineStep = () => (\n <div className=\"flex flex-col gap-4 sm:gap-6 pt-6\">\n <DateTimeInput\n label=\"Data da prova*\"\n date={store.formData.startDate || ''}\n time={store.formData.startTime || ''}\n onDateChange={handleStartDateChange}\n onTimeChange={handleStartTimeChange}\n errorMessage={store.errors.startDate}\n defaultTime=\"08:00\"\n testId=\"exam-datetime\"\n className=\"w-full max-w-xs\"\n />\n </div>\n );\n\n /**\n * Render current step content\n */\n const renderStepContent = () => {\n // Use store categories if available, otherwise use initial categories\n const categoriesToRender =\n storeCategories.length > 0 ? storeCategories : initialCategories;\n\n switch (store.currentStep) {\n case 1:\n return renderActivityStep();\n case 2:\n return (\n <RecipientStep\n categories={categoriesToRender}\n onCategoriesChange={handleCategoriesChange}\n entityNameWithArticle={entityNameWithArticle}\n studentsError={store.errors.students}\n />\n );\n case 3:\n // Exam mode: simplified deadline with only exam date\n if (isExamMode) {\n return renderExamDeadlineStep();\n }\n // Activity mode: full deadline with start/end dates and retry option\n return (\n <DeadlineStep\n startDate={store.formData.startDate || ''}\n startTime={store.formData.startTime || ''}\n finalDate={store.formData.finalDate || ''}\n finalTime={store.formData.finalTime || ''}\n onStartDateChange={handleStartDateChange}\n onStartTimeChange={handleStartTimeChange}\n onFinalDateChange={handleFinalDateChange}\n onFinalTimeChange={handleFinalTimeChange}\n errors={{\n startDate: store.errors.startDate,\n finalDate: store.errors.finalDate,\n }}\n >\n {renderRetryOption()}\n </DeadlineStep>\n );\n default:\n return null;\n }\n };\n\n /**\n * Render footer with navigation buttons\n */\n const renderFooter = () => (\n <SendModalFooter\n currentStep={store.currentStep}\n maxSteps={MAX_STEPS}\n isLoading={isLoading}\n onCancel={handleCancel}\n onPreviousStep={store.previousStep}\n onNextStep={() => store.nextStep(isExamMode)}\n onSubmit={handleSubmit}\n entityName={entityName}\n />\n );\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={modalTitle}\n size=\"md\"\n footer={renderFooter()}\n contentClassName=\"flex flex-col gap-8 sm:gap-10 max-h-[70vh] overflow-y-auto\"\n >\n {/* Stepper */}\n <Stepper\n steps={stepperSteps}\n currentStep={store.currentStep - 1}\n size=\"small\"\n />\n\n {/* Step Content */}\n {renderStepContent()}\n </Modal>\n );\n};\n\nexport default SendActivityModal;\n","import { ChangeEvent, useCallback, useEffect, useState } from 'react';\nimport Modal from '../Modal/Modal';\nimport Input from '../Input/Input';\nimport Button from '../Button/Button';\n\nconst TITLE_MAX_LENGTH = 255;\n\n/**\n * SaveActivityModelModal component props\n */\nexport interface SaveActivityModelModalProps {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Callback to close the modal without saving */\n onClose: () => void;\n /** Callback called with the validated title when the user confirms */\n onConfirm: (title: string) => void;\n /** Disables actions and shows loading state on confirm button */\n isLoading?: boolean;\n /** Optional initial title value (used when editing an existing draft) */\n initialTitle?: string;\n}\n\n/**\n * Modal that prompts the user for a model title before saving as a reusable\n * template. Generic — used both by ActivityCreate (ActivityType.MODELO) and\n * RecommendedLessonCreate (RecommendedClassDraftType.MODELO) flows.\n *\n * Validates the title locally with the same constraints as the backend schema\n * (1–255 characters after trimming).\n */\nexport const SaveActivityModelModal = ({\n isOpen,\n onClose,\n onConfirm,\n isLoading = false,\n initialTitle = '',\n}: SaveActivityModelModalProps) => {\n const [title, setTitle] = useState(initialTitle);\n const [error, setError] = useState<string | undefined>(undefined);\n\n // Reset internal state every time the modal opens\n useEffect(() => {\n if (isOpen) {\n setTitle(initialTitle);\n setError(undefined);\n }\n }, [isOpen, initialTitle]);\n\n const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {\n setTitle(event.target.value);\n setError(undefined);\n }, []);\n\n const handleConfirm = useCallback(() => {\n const trimmed = title.trim();\n\n if (trimmed.length === 0) {\n setError('Informe um título para o modelo');\n return;\n }\n\n if (trimmed.length > TITLE_MAX_LENGTH) {\n setError(`O título deve ter no máximo ${TITLE_MAX_LENGTH} caracteres`);\n return;\n }\n\n onConfirm(trimmed);\n }, [title, onConfirm]);\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Salvar modelo\"\n size=\"sm\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={isLoading}>\n Cancelar\n </Button>\n <Button variant=\"solid\" onClick={handleConfirm} disabled={isLoading}>\n Salvar modelo\n </Button>\n </>\n }\n >\n <div className=\"flex flex-col gap-4\">\n <Input\n label=\"Título\"\n placeholder=\"Digite o título do modelo\"\n value={title}\n onChange={handleChange}\n variant=\"rounded\"\n required\n maxLength={TITLE_MAX_LENGTH}\n errorMessage={error}\n autoFocus\n />\n </div>\n </Modal>\n );\n};\n\nexport default SaveActivityModelModal;\n","import { useCallback, useEffect, ChangeEvent } from 'react';\nimport Modal from '../Modal/Modal';\nimport Stepper from '../Stepper/Stepper';\nimport Input from '../Input/Input';\nimport TextArea from '../TextArea/TextArea';\nimport Text from '../Text/Text';\nimport { RadioGroup, RadioGroupItem } from '../Radio/Radio';\nimport { useSendLessonModalStore } from './hooks/useSendLessonModal';\nimport {\n SendLessonModalProps,\n SendLessonFormData,\n CategoryConfig,\n} from './types';\nimport {\n RecipientStep,\n DeadlineStep,\n SendModalFooter,\n useDateTimeHandlers,\n useCategoryInitialization,\n useCategorySync,\n} from '../shared/SendModalBase';\n\n/**\n * Stepper steps configuration\n */\nconst STEPPER_STEPS = [\n { id: 'lesson', label: 'Aula', state: 'pending' as const },\n { id: 'recipient', label: 'Destinatário', state: 'pending' as const },\n { id: 'deadline', label: 'Prazo', state: 'pending' as const },\n];\n\n/**\n * Modal configuration constants\n */\nconst MAX_STEPS = 3;\nconst ENTITY_NAME = 'aula';\nconst ENTITY_NAME_WITH_ARTICLE = 'a aula';\n\n/**\n * SendLessonModal component for sending lessons to students\n *\n * A multi-step modal with 3 steps:\n * 1. Lesson - Enter title and optional notification message\n * 2. Recipient - Select students from hierarchical structure using CheckboxGroup\n * 3. Deadline - Set start/end dates\n */\nconst SendLessonModal = ({\n isOpen,\n onClose,\n onSubmit,\n categories: initialCategories,\n onCategoriesChange,\n isLoading = false,\n onError,\n modalTitle: modalTitleProp,\n hasAttachedActivities = false,\n}: SendLessonModalProps) => {\n const store = useSendLessonModalStore();\n const reset = useSendLessonModalStore((state) => state.reset);\n const setCategories = useSendLessonModalStore((state) => state.setCategories);\n const storeCategories = useSendLessonModalStore((state) => state.categories);\n\n /**\n * Initialize categories with auto-selection when modal opens\n */\n const { categoriesInitializedRef } = useCategoryInitialization({\n isOpen,\n initialCategories,\n setCategories,\n onCategoriesChange,\n });\n\n /**\n * Sync categories from parent when they change (e.g., after fetching students)\n */\n useCategorySync({\n isOpen,\n initialCategories,\n storeCategories,\n setCategories,\n categoriesInitializedRef,\n });\n\n /**\n * Reset store when modal closes\n */\n useEffect(() => {\n if (!isOpen) {\n reset();\n }\n }, [isOpen, reset]);\n\n /**\n * Date/time change handlers from shared hook\n */\n const {\n handleStartDateChange,\n handleStartTimeChange,\n handleFinalDateChange,\n handleFinalTimeChange,\n } = useDateTimeHandlers({ setFormData: store.setFormData });\n\n /**\n * Handle categories change from CheckboxGroup\n */\n const handleCategoriesChange = useCallback(\n (updatedCategories: CategoryConfig[]) => {\n setCategories(updatedCategories);\n onCategoriesChange?.(updatedCategories);\n },\n [setCategories, onCategoriesChange]\n );\n\n /**\n * Handle title change\n */\n const handleTitleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n store.setFormData({ title: e.target.value });\n },\n [store]\n );\n\n /**\n * Handle notification message change\n */\n const handleNotificationChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => {\n store.setFormData({ notification: e.target.value });\n },\n [store]\n );\n\n /**\n * Handle retry option change (only relevant when activities are attached)\n */\n const handleRetryChange = useCallback(\n (value: string) => {\n store.setFormData({ canRetry: value === 'yes' });\n },\n [store]\n );\n\n /**\n * Render retry option for the deadline step.\n * Only visible when the selected model has activity drafts attached —\n * otherwise the radio would have no effect on backend state.\n */\n const renderRetryOption = () => {\n if (!hasAttachedActivities) return null;\n\n return (\n <div>\n <Text size=\"sm\" weight=\"medium\" color=\"text-text-700\" className=\"mb-3\">\n Permitir refazer?\n </Text>\n <RadioGroup\n value={store.formData.canRetry ? 'yes' : 'no'}\n onValueChange={handleRetryChange}\n className=\"flex flex-row gap-6\"\n >\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"yes\" id=\"send-lesson-retry-yes\" />\n <Text\n as=\"label\"\n size=\"sm\"\n color=\"text-text-700\"\n className=\"cursor-pointer\"\n htmlFor=\"send-lesson-retry-yes\"\n >\n Sim\n </Text>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"no\" id=\"send-lesson-retry-no\" />\n <Text\n as=\"label\"\n size=\"sm\"\n color=\"text-text-700\"\n className=\"cursor-pointer\"\n htmlFor=\"send-lesson-retry-no\"\n >\n Não\n </Text>\n </div>\n </RadioGroup>\n </div>\n );\n };\n\n /**\n * Handle form submission\n */\n const handleSubmit = useCallback(async () => {\n const isValid = store.validateAllSteps();\n if (!isValid) return;\n\n try {\n const formData = store.formData as SendLessonFormData;\n await onSubmit(formData);\n } catch (error) {\n if (onError) {\n onError(error);\n } else {\n console.error('Falha ao enviar aula:', error);\n }\n }\n }, [store, onSubmit, onError]);\n\n /**\n * Handle cancel button click\n */\n const handleCancel = useCallback(() => {\n onClose();\n }, [onClose]);\n\n /**\n * Render Step 1 - Lesson (unique to SendLessonModal)\n */\n const renderLessonStep = () => (\n <div className=\"flex flex-col gap-6\">\n {/* Title Input */}\n <Input\n label=\"Título\"\n placeholder=\"Digite o título da aula\"\n value={store.formData.title || ''}\n onChange={handleTitleChange}\n variant=\"rounded\"\n required\n errorMessage={store.errors.title}\n />\n\n {/* Notification Message */}\n <TextArea\n label=\"Mensagem da notificação\"\n placeholder=\"Digite uma mensagem para a notificação (opcional)\"\n value={store.formData.notification || ''}\n onChange={handleNotificationChange}\n />\n </div>\n );\n\n /**\n * Render current step content\n */\n const renderStepContent = () => {\n // Use store categories if available, otherwise use initial categories\n const categoriesToRender =\n storeCategories.length > 0 ? storeCategories : initialCategories;\n\n switch (store.currentStep) {\n case 1:\n return renderLessonStep();\n case 2:\n return (\n <RecipientStep\n categories={categoriesToRender}\n onCategoriesChange={handleCategoriesChange}\n entityNameWithArticle={ENTITY_NAME_WITH_ARTICLE}\n studentsError={store.errors.students}\n />\n );\n case 3:\n return (\n <div className=\"flex flex-col gap-6\">\n <DeadlineStep\n startDate={store.formData.startDate || ''}\n startTime={store.formData.startTime || ''}\n finalDate={store.formData.finalDate || ''}\n finalTime={store.formData.finalTime || ''}\n onStartDateChange={handleStartDateChange}\n onStartTimeChange={handleStartTimeChange}\n onFinalDateChange={handleFinalDateChange}\n onFinalTimeChange={handleFinalTimeChange}\n errors={{\n startDate: store.errors.startDate,\n finalDate: store.errors.finalDate,\n }}\n />\n {renderRetryOption()}\n </div>\n );\n default:\n return null;\n }\n };\n\n /**\n * Render footer with navigation buttons\n */\n const renderFooter = () => (\n <SendModalFooter\n currentStep={store.currentStep}\n maxSteps={MAX_STEPS}\n isLoading={isLoading}\n onCancel={handleCancel}\n onPreviousStep={store.previousStep}\n onNextStep={() => store.nextStep()}\n onSubmit={handleSubmit}\n entityName={ENTITY_NAME}\n />\n );\n\n const modalTitle = modalTitleProp\n ? `Enviar aula: ${modalTitleProp}`\n : 'Enviar aula recomendada';\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={modalTitle}\n size=\"md\"\n footer={renderFooter()}\n contentClassName=\"flex flex-col gap-8 sm:gap-10 max-h-[70vh] overflow-y-auto\"\n >\n {/* Stepper */}\n <Stepper\n steps={STEPPER_STEPS}\n currentStep={store.currentStep - 1}\n size=\"small\"\n />\n\n {/* Step Content */}\n {renderStepContent()}\n </Modal>\n );\n};\n\nexport default SendLessonModal;\n","import { create } from 'zustand';\nimport { SendLessonFormData, StepErrors, CategoryConfig } from '../types';\nimport { validateStep } from '../validation';\nimport { extractStudentsFromCategories } from '../../../utils/extractStudentsFromCategories';\n\n/**\n * Store interface for SendLessonModal\n */\nexport interface SendLessonModalStore {\n /** Form data */\n formData: Partial<SendLessonFormData>;\n /** Set form data */\n setFormData: (data: Partial<SendLessonFormData>) => void;\n\n /** Current step (1, 2, or 3) */\n currentStep: number;\n /** Completed steps */\n completedSteps: number[];\n /** Go to a specific step */\n goToStep: (step: number) => void;\n /** Go to next step (validates current step first) */\n nextStep: () => boolean;\n /** Go to previous step */\n previousStep: () => void;\n\n /** Validation errors */\n errors: StepErrors;\n /** Set errors */\n setErrors: (errors: StepErrors) => void;\n /** Validate current step */\n validateCurrentStep: () => boolean;\n /** Validate all steps */\n validateAllSteps: () => boolean;\n\n /** Categories state managed by CheckboxGroup */\n categories: CategoryConfig[];\n /** Update categories (called by CheckboxGroup) */\n setCategories: (categories: CategoryConfig[]) => void;\n\n /** Reset store to initial state */\n reset: () => void;\n}\n\nconst initialState = {\n formData: {\n startTime: '00:00',\n finalTime: '23:59',\n canRetry: false,\n } as Partial<SendLessonFormData>,\n currentStep: 1,\n completedSteps: [] as number[],\n errors: {} as StepErrors,\n categories: [] as CategoryConfig[],\n};\n\n/**\n * Creates the SendLessonModal store\n */\nexport const useSendLessonModalStore = create<SendLessonModalStore>(\n (set, get) => ({\n ...initialState,\n\n setFormData: (data) => {\n set((state) => ({\n formData: { ...state.formData, ...data },\n }));\n },\n\n goToStep: (step) => {\n if (step >= 1 && step <= 3) {\n set({ currentStep: step, errors: {} });\n }\n },\n\n nextStep: () => {\n const state = get();\n const isValid = state.validateCurrentStep();\n\n if (isValid && state.currentStep < 3) {\n set((prev) => ({\n currentStep: prev.currentStep + 1,\n completedSteps: prev.completedSteps.includes(prev.currentStep)\n ? prev.completedSteps\n : [...prev.completedSteps, prev.currentStep],\n errors: {},\n }));\n return true;\n }\n\n return isValid;\n },\n\n previousStep: () => {\n const state = get();\n if (state.currentStep > 1) {\n set({ currentStep: state.currentStep - 1, errors: {} });\n }\n },\n\n setErrors: (errors) => {\n set({ errors });\n },\n\n validateCurrentStep: () => {\n const state = get();\n // For step 2, extract students from categories\n let formDataToValidate = state.formData;\n let updatedFormData = state.formData;\n if (state.currentStep === 2 && state.categories.length > 0) {\n const students = extractStudentsFromCategories(state.categories);\n formDataToValidate = { ...state.formData, students };\n updatedFormData = formDataToValidate;\n }\n const errors = validateStep(state.currentStep, formDataToValidate);\n set({ formData: updatedFormData, errors });\n return Object.keys(errors).length === 0;\n },\n\n validateAllSteps: () => {\n const state = get();\n // Extract students from categories for step 2 validation\n let formDataForStep2 = state.formData;\n if (state.categories.length > 0) {\n const students = extractStudentsFromCategories(state.categories);\n formDataForStep2 = { ...state.formData, students };\n }\n const errors1 = validateStep(1, state.formData);\n const errors2 = validateStep(2, formDataForStep2);\n const errors3 = validateStep(3, state.formData);\n const allErrors = { ...errors1, ...errors2, ...errors3 };\n set({ errors: allErrors });\n return Object.keys(allErrors).length === 0;\n },\n\n setCategories: (categories) => {\n // Extract students from categories and update formData\n const students = extractStudentsFromCategories(categories);\n set((state) => ({\n categories,\n formData: { ...state.formData, students },\n }));\n },\n\n reset: () => {\n set({\n ...initialState,\n categories: [],\n });\n },\n })\n);\n\n/**\n * Hook to use the SendLessonModal store\n */\nexport function useSendLessonModal() {\n return useSendLessonModalStore();\n}\n","import { SendLessonFormData, StepErrors } from './types';\n\n/**\n * Error messages for validation\n */\nexport const ERROR_MESSAGES = {\n TITLE_REQUIRED:\n 'Campo obrigatório! Por favor, preencha este campo para continuar.',\n STUDENTS_REQUIRED:\n 'Campo obrigatório! Por favor, selecione pelo menos um aluno para continuar.',\n START_DATE_REQUIRED:\n 'Campo obrigatório! Por favor, preencha este campo para continuar.',\n FINAL_DATE_REQUIRED:\n 'Campo obrigatório! Por favor, preencha este campo para continuar.',\n FINAL_DATE_INVALID: 'A data final deve ser maior ou igual à data inicial.',\n} as const;\n\n/**\n * Validates Step 1 - Lesson (title and notification)\n */\nfunction validateLessonStep(formData: Partial<SendLessonFormData>): StepErrors {\n const errors: StepErrors = {};\n\n if (!formData.title || formData.title.trim().length === 0) {\n errors.title = ERROR_MESSAGES.TITLE_REQUIRED;\n }\n\n return errors;\n}\n\n/**\n * Validates Step 2 - Recipient (students selection)\n */\nfunction validateRecipientStep(\n formData: Partial<SendLessonFormData>\n): StepErrors {\n const errors: StepErrors = {};\n\n if (!formData.students || formData.students.length === 0) {\n errors.students = ERROR_MESSAGES.STUDENTS_REQUIRED;\n }\n\n return errors;\n}\n\n/**\n * Validates Step 3 - Deadline (dates)\n */\nfunction validateDeadlineStep(\n formData: Partial<SendLessonFormData>\n): StepErrors {\n const errors: StepErrors = {};\n\n if (!formData.startDate) {\n errors.startDate = ERROR_MESSAGES.START_DATE_REQUIRED;\n }\n\n if (!formData.finalDate) {\n errors.finalDate = ERROR_MESSAGES.FINAL_DATE_REQUIRED;\n }\n\n // Validate date range\n if (formData.startDate && formData.finalDate) {\n const startDateTime = new Date(\n `${formData.startDate}T${formData.startTime || '00:00'}`\n );\n const finalDateTime = new Date(\n `${formData.finalDate}T${formData.finalTime || '23:59'}`\n );\n\n if (finalDateTime <= startDateTime) {\n errors.finalDate = ERROR_MESSAGES.FINAL_DATE_INVALID;\n }\n }\n\n return errors;\n}\n\n/**\n * Validates a specific step\n * @param step - Step number (1, 2, or 3)\n * @param formData - Current form data\n * @returns Validation errors for the step\n */\nexport function validateStep(\n step: number,\n formData: Partial<SendLessonFormData>\n): StepErrors {\n switch (step) {\n case 1:\n return validateLessonStep(formData);\n case 2:\n return validateRecipientStep(formData);\n case 3:\n return validateDeadlineStep(formData);\n default:\n return {};\n }\n}\n","import type { MouseEvent, ReactNode } from 'react';\nimport { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { PencilSimpleIcon } from '@phosphor-icons/react/dist/csr/PencilSimple';\nimport Text from '../Text/Text';\nimport { TruncatedText } from '../TruncatedText/TruncatedText';\nimport Button from '../Button/Button';\nimport IconButton from '../IconButton/IconButton';\nimport Badge from '../Badge/Badge';\nimport EmptyState from '../EmptyState/EmptyState';\nimport { Menu, MenuItem, MenuContent } from '../Menu/Menu';\nimport { TableProvider } from '../TableProvider/TableProvider';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { getSubjectInfo } from '../SubjectInfo/SubjectInfo';\nimport { AlertDialog } from '../AlertDialog/AlertDialog';\nimport useToastStore from '../Toast/utils/ToastStore';\nimport { EditRecommendedLessonModal } from './EditRecommendedLessonModal';\nimport { cn } from '../../utils/utils';\nimport { SubjectEnum } from '../../enums/SubjectEnum';\nimport type { ColumnConfig, TableParams } from '../TableProvider/TableProvider';\nimport type { FilterConfig } from '../Filter';\nimport {\n RecommendedClassDisplayStatus,\n RecommendedClassApiStatus,\n getRecommendedClassStatusBadgeAction,\n RECOMMENDED_CLASS_FILTER_STATUS_OPTIONS,\n type RecommendedClassTableItem,\n type RecommendedClassHistoryFilters,\n type RecommendedClassHistoryApiResponse,\n type RecommendedClassUserFilterData,\n type RecommendedClassFilterOption,\n type RecommendedClassModelFilters,\n type RecommendedClassModelsApiResponse,\n type RecommendedClassModelTableItem,\n type RecommendedClassData,\n type UpdateRecommendedClassData,\n} from '../../types/recommendedLessons';\nimport {\n createUseRecommendedLessonsHistory,\n type UseRecommendedLessonsHistoryReturn,\n} from '../../hooks/useRecommendedLessons';\nimport { RecommendedClassModelsTab } from './tabs/ModelsTab';\nimport { RecommendedClassDraftsTab } from './tabs/DraftsTab';\n\n/**\n * Enum for page tabs - exported for external routing control\n */\nexport enum RecommendedClassPageTab {\n HISTORY = 'history',\n DRAFTS = 'drafts',\n MODELS = 'models',\n}\n\n/**\n * Props for the RecommendedLessonsHistory component\n */\nexport interface RecommendedLessonsHistoryProps {\n /** Function to fetch recommendedClass history from API. Must return RecommendedClassHistoryApiResponse. */\n fetchRecommendedClassHistory: (\n filters?: RecommendedClassHistoryFilters\n ) => Promise<RecommendedClassHistoryApiResponse>;\n /** Callback when create lesson button is clicked */\n onCreateLesson: () => void;\n /** Callback when a row is clicked */\n onRowClick: (row: RecommendedClassTableItem) => void;\n /** Delete a recommended class (history item). Enables the row delete action. */\n deleteRecommendedClass?: (id: string) => Promise<void>;\n /** Update a recommended class title/dates. Enables the row edit action (with fetchRecommendedClassById). */\n updateRecommendedClass?: (\n id: string,\n data: UpdateRecommendedClassData\n ) => Promise<void>;\n /** Load a single recommended class to pre-fill the edit modal. */\n fetchRecommendedClassById?: (id: string) => Promise<RecommendedClassData>;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: RecommendedClassUserFilterData;\n /** Page title */\n title?: string;\n /** Create button text */\n createButtonText?: string;\n /** Search placeholder */\n searchPlaceholder?: string;\n /** Function to fetch recommendedClass models from API (optional - for Models tab) */\n fetchRecommendedClassModels?: (\n filters?: RecommendedClassModelFilters\n ) => Promise<RecommendedClassModelsApiResponse>;\n /** Function to delete a recommendedClass model (optional - for Models tab) */\n deleteRecommendedClassModel?: (id: string) => Promise<void>;\n /** Callback when create model button is clicked (optional - for Models tab) */\n onCreateModel?: () => void;\n /** Callback when send lesson button is clicked on a model (optional - for Models tab) */\n onSendLesson?: (model: RecommendedClassModelTableItem) => void;\n /** Callback when edit model button is clicked (optional - for Models tab) */\n onEditModel?: (model: RecommendedClassModelTableItem) => void;\n /**\n * Map of subject IDs to names for models display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches.\n */\n subjectsMap?: Map<string, string>;\n /** Function to fetch recommendedClass drafts from API (optional - for Drafts tab) */\n fetchRecommendedClassDrafts?: (\n filters?: RecommendedClassModelFilters\n ) => Promise<RecommendedClassModelsApiResponse>;\n /** Function to delete a recommendedClass draft (optional - for Drafts tab) */\n deleteRecommendedClassDraft?: (id: string) => Promise<void>;\n /** Callback when send draft button is clicked (optional - for Drafts tab) */\n onSendDraft?: (draft: RecommendedClassModelTableItem) => void;\n /** Callback when edit draft button is clicked (optional - for Drafts tab) */\n onEditDraft?: (draft: RecommendedClassModelTableItem) => void;\n /**\n * Default tab to display. When provided with onTabChange, enables controlled mode\n * for URL routing.\n */\n defaultTab?: RecommendedClassPageTab;\n /**\n * Callback when tab changes. When provided, enables controlled mode for URL routing.\n * Use this to navigate to different routes when tabs change.\n */\n onTabChange?: (tab: RecommendedClassPageTab) => void;\n /**\n * Extra filter categories to inject (e.g., creatorType for gestors).\n * Merged with default filters when provided.\n */\n extraFilterCategories?: FilterConfig[];\n /**\n * Id of the current user. When provided, the edit/delete actions are shown\n * only for rows the current user created (i.e. rows whose `creatorId`\n * matches this id). When undefined, the actions behave as before and are\n * shown for every row (subject to the delete/edit capabilities), keeping\n * other consumers of this shared component unaffected.\n */\n currentUserId?: string;\n}\n\n/**\n * Extract filter value from params for single/multiple selection\n */\nconst extractFilterValue = (\n param: unknown\n): { single?: string; multiple?: string[] } => {\n if (!Array.isArray(param) || param.length === 0) return {};\n return param.length === 1 ? { single: param[0] } : { multiple: param };\n};\n\n/**\n * Build recommendedClass history filters from table params\n */\nconst buildFiltersFromParams = (\n params: TableParams\n): RecommendedClassHistoryFilters => {\n const filters: RecommendedClassHistoryFilters = {\n page: params.page,\n limit: params.limit,\n };\n\n if (params.search) {\n filters.search = params.search;\n }\n\n // Status filter (single selection)\n if (Array.isArray(params.status) && params.status.length > 0) {\n filters.status = params.status[0] as RecommendedClassApiStatus;\n }\n\n // School filter\n const schoolFilter = extractFilterValue(params.school);\n if (schoolFilter.single) filters.schoolId = schoolFilter.single;\n if (schoolFilter.multiple) filters.schoolIds = schoolFilter.multiple;\n\n // School year filter\n const schoolYearFilter = extractFilterValue(params.schoolYear);\n if (schoolYearFilter.single) filters.schoolYearId = schoolYearFilter.single;\n if (schoolYearFilter.multiple)\n filters.schoolYearIds = schoolYearFilter.multiple;\n\n // Class filter\n const classFilter = extractFilterValue(params.class);\n if (classFilter.single) filters.classId = classFilter.single;\n if (classFilter.multiple) filters.classIds = classFilter.multiple;\n\n // Subject filter (single selection)\n if (Array.isArray(params.subject) && params.subject.length > 0) {\n filters.subjectId = params.subject[0];\n }\n\n // Start date filter\n if (params.startDate && typeof params.startDate === 'string') {\n filters.startDate = params.startDate;\n }\n\n // Creator type filter (single selection)\n if (Array.isArray(params.creatorType) && params.creatorType.length > 0) {\n filters.creatorType = params.creatorType[0];\n }\n\n return filters;\n};\n\n/**\n * Get school options from user data\n */\nconst getSchoolOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.schools) return [];\n return data.schools.map((school) => ({\n id: school.id,\n name: school.name,\n }));\n};\n\n/**\n * Get school year options from user data\n */\nconst getSchoolYearOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.schoolYears) return [];\n return data.schoolYears.map((year) => ({\n id: year.id,\n name: year.name,\n }));\n};\n\n/**\n * Get class options from user data\n */\nconst getClassOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.classes) return [];\n return data.classes.map((cls) => ({\n id: cls.id,\n name: cls.name,\n }));\n};\n\n/**\n * Get subject options from user data\n */\nconst getSubjectOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.subjects) return [];\n return data.subjects.map((subject) => ({\n id: subject.id,\n name: subject.name,\n }));\n};\n\n/**\n * Create filter configuration for recommendedClass\n */\nconst createRecommendedClassFiltersConfig = (\n userData: RecommendedClassUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'academic',\n label: 'DADOS ACADÊMICOS',\n categories: [\n {\n key: 'school',\n label: 'Escola',\n selectedIds: [],\n itens: getSchoolOptions(userData),\n },\n {\n key: 'schoolYear',\n label: 'Série',\n selectedIds: [],\n itens: getSchoolYearOptions(userData),\n },\n {\n key: 'class',\n label: 'Turma',\n selectedIds: [],\n itens: getClassOptions(userData),\n },\n ],\n },\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n ],\n },\n {\n key: 'lesson',\n label: 'AULA',\n categories: [\n {\n key: 'status',\n label: 'Status',\n selectedIds: [],\n itens: RECOMMENDED_CLASS_FILTER_STATUS_OPTIONS,\n },\n ],\n },\n];\n\n/**\n * Create table columns configuration\n */\nconst createTableColumns = (\n mapSubjectNameToEnum: ((name: string) => SubjectEnum | null) | undefined,\n onDelete: ((row: RecommendedClassTableItem) => void) | undefined,\n onEdit: ((row: RecommendedClassTableItem) => void) | undefined,\n currentUserId?: string\n): ColumnConfig<RecommendedClassTableItem>[] => [\n {\n key: 'startDate',\n label: 'Início',\n sortable: true,\n },\n {\n key: 'deadline',\n label: 'Prazo',\n sortable: true,\n },\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[200px]',\n render: (value: unknown) => {\n const title = typeof value === 'string' ? value : '';\n return <TruncatedText size=\"sm\">{title}</TruncatedText>;\n },\n },\n {\n key: 'school',\n label: 'Escola',\n sortable: true,\n className: 'max-w-[150px]',\n render: (value: unknown) => {\n const school = typeof value === 'string' ? value : '';\n return <TruncatedText size=\"sm\">{school}</TruncatedText>;\n },\n },\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[140px]',\n render: (value: unknown) => {\n const subjectName = typeof value === 'string' ? value : '';\n const subjectEnum = mapSubjectNameToEnum?.(subjectName);\n\n if (!subjectEnum) {\n return <TruncatedText size=\"sm\">{subjectName}</TruncatedText>;\n }\n\n const subjectInfo = getSubjectInfo(subjectEnum);\n\n return (\n <div className=\"flex items-center gap-2 min-w-0\">\n <span\n className={cn(\n 'w-[21px] h-[21px] flex items-center justify-center rounded-sm text-text-950 shrink-0',\n subjectInfo.colorClass\n )}\n >\n {subjectInfo.icon}\n </span>\n <TruncatedText size=\"sm\">{subjectName}</TruncatedText>\n </div>\n );\n },\n },\n {\n key: 'class',\n label: 'Turma',\n sortable: true,\n className: 'max-w-[120px]',\n render: (value: unknown) => {\n const className = typeof value === 'string' ? value : '';\n return <TruncatedText size=\"sm\">{className}</TruncatedText>;\n },\n },\n {\n key: 'status',\n label: 'Status',\n sortable: true,\n render: (value: unknown) => {\n const status = typeof value === 'string' ? value : '';\n if (!status) {\n return (\n <Text size=\"sm\" color=\"text-text-500\">\n -\n </Text>\n );\n }\n return (\n <Badge\n variant=\"solid\"\n action={getRecommendedClassStatusBadgeAction(\n status as RecommendedClassDisplayStatus\n )}\n size=\"small\"\n >\n {status}\n </Badge>\n );\n },\n },\n {\n key: 'completionPercentage',\n label: 'Conclusão',\n sortable: true,\n render: (value: unknown) => (\n <ProgressBar\n value={Number(value)}\n variant=\"blue\"\n size=\"medium\"\n layout=\"compact\"\n showPercentage={true}\n compactWidth=\"w-[100px]\"\n />\n ),\n },\n {\n key: 'actions',\n label: '',\n sortable: false,\n className: 'w-20',\n render: (_value: unknown, row: RecommendedClassTableItem) => {\n if (!onDelete && !onEdit) {\n return null;\n }\n\n // Opt-in ownership gate: when a current user id is provided, only the\n // row's creator may see the edit/delete actions. Rows created by others\n // (or with an unknown creator) hide the actions.\n if (currentUserId && row.creatorId !== currentUserId) {\n return null;\n }\n\n const handleDelete = (e: MouseEvent) => {\n e.stopPropagation();\n onDelete?.(row);\n };\n\n const handleEdit = (e: MouseEvent) => {\n e.stopPropagation();\n onEdit?.(row);\n };\n\n return (\n <div className=\"flex justify-center gap-2\">\n {onDelete && (\n <IconButton\n icon={<TrashIcon size={20} />}\n size=\"sm\"\n title=\"Excluir\"\n aria-label=\"Excluir\"\n onClick={handleDelete}\n />\n )}\n {onEdit && (\n <IconButton\n icon={<PencilSimpleIcon size={20} />}\n size=\"sm\"\n title=\"Editar\"\n aria-label=\"Editar\"\n onClick={handleEdit}\n />\n )}\n </div>\n );\n },\n },\n {\n key: 'navigation',\n label: '',\n sortable: false,\n className: 'w-12',\n render: () => (\n <div className=\"flex justify-center\">\n <CaretRightIcon size={20} className=\"text-text-600\" />\n </div>\n ),\n },\n];\n\n/**\n * RecommendedLessonsHistory component\n * Displays recommendedClass/recommended lessons history with tabs, filters, and table\n */\nexport const RecommendedLessonsHistory = ({\n fetchRecommendedClassHistory,\n onCreateLesson,\n onRowClick,\n deleteRecommendedClass,\n updateRecommendedClass,\n fetchRecommendedClassById,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n title = 'Histórico de aulas recomendadas',\n createButtonText = 'Criar aula',\n searchPlaceholder = 'Buscar aula',\n fetchRecommendedClassModels,\n deleteRecommendedClassModel,\n onCreateModel,\n onSendLesson,\n onEditModel,\n subjectsMap,\n fetchRecommendedClassDrafts,\n deleteRecommendedClassDraft,\n onSendDraft,\n onEditDraft,\n defaultTab,\n onTabChange,\n extraFilterCategories,\n currentUserId,\n}: RecommendedLessonsHistoryProps) => {\n const [activeTab, setActiveTab] = useState<RecommendedClassPageTab>(\n defaultTab ?? RecommendedClassPageTab.HISTORY\n );\n\n // Sync with external defaultTab prop when it changes (for URL routing)\n useEffect(() => {\n if (defaultTab !== undefined) {\n setActiveTab(defaultTab);\n }\n }, [defaultTab]);\n\n /**\n * Handle tab change - calls onTabChange callback if provided for URL routing\n */\n const handleTabChange = useCallback(\n (value: string) => {\n const newTab = value as RecommendedClassPageTab;\n setActiveTab(newTab);\n onTabChange?.(newTab);\n },\n [onTabChange]\n );\n\n // Use ref to keep stable reference of fetchRecommendedClassHistory\n // This prevents hook recreation if parent doesn't memoize the function\n const fetchRecommendedClassHistoryRef = useRef(fetchRecommendedClassHistory);\n fetchRecommendedClassHistoryRef.current = fetchRecommendedClassHistory;\n\n // Create hook instance with stable fetch function wrapper\n const useRecommendedClassHistory = useMemo(\n () =>\n createUseRecommendedLessonsHistory((filters) =>\n fetchRecommendedClassHistoryRef.current(filters)\n ),\n []\n );\n\n // Use the hook\n const {\n recommendedClass,\n loading,\n error,\n pagination,\n fetchRecommendedClass,\n }: UseRecommendedLessonsHistoryReturn = useRecommendedClassHistory();\n\n const addToast = useToastStore((state) => state.addToast);\n\n // Whether the per-row edit/delete actions are available\n const deleteEnabled = Boolean(deleteRecommendedClass);\n const editEnabled = Boolean(\n updateRecommendedClass && fetchRecommendedClassById\n );\n\n // Recommended class pending deletion confirmation (drives the AlertDialog)\n const [recommendedClassToDelete, setRecommendedClassToDelete] = useState<{\n id: string;\n title: string;\n } | null>(null);\n\n // Recommended class currently being edited (drives the EditRecommendedLessonModal)\n const [recommendedClassToEdit, setRecommendedClassToEdit] = useState<{\n id: string;\n } | null>(null);\n\n // Last applied filters, used to reload the list after a mutation\n const lastFiltersRef = useRef<RecommendedClassHistoryFilters | undefined>(\n undefined\n );\n\n // In-flight guard so a double-click on \"Excluir\" can't fire multiple DELETEs\n const deletingRef = useRef(false);\n\n const handleOpenDelete = useCallback((row: RecommendedClassTableItem) => {\n setRecommendedClassToDelete({ id: row.id, title: row.title });\n }, []);\n\n const handleOpenEdit = useCallback((row: RecommendedClassTableItem) => {\n setRecommendedClassToEdit({ id: row.id });\n }, []);\n\n /**\n * Reload the history list with the last applied filters (after a mutation)\n */\n const reloadHistory = useCallback(() => {\n fetchRecommendedClass(lastFiltersRef.current);\n }, [fetchRecommendedClass]);\n\n /**\n * Confirm deletion of the selected recommended class, then reload the list\n */\n const handleConfirmDelete = useCallback(async () => {\n if (\n !recommendedClassToDelete ||\n !deleteRecommendedClass ||\n deletingRef.current\n ) {\n return;\n }\n deletingRef.current = true;\n try {\n await deleteRecommendedClass(recommendedClassToDelete.id);\n setRecommendedClassToDelete(null);\n reloadHistory();\n addToast({\n title: 'Aula recomendada excluída com sucesso',\n action: 'success',\n position: 'top-right',\n });\n } catch {\n setRecommendedClassToDelete(null);\n addToast({\n title: 'Erro ao excluir aula recomendada',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n deletingRef.current = false;\n }\n }, [\n recommendedClassToDelete,\n deleteRecommendedClass,\n reloadHistory,\n addToast,\n ]);\n\n // Create filter and column configurations, merging extra filters if provided\n const initialFilterConfigs = useMemo(\n () => [\n ...createRecommendedClassFiltersConfig(userFilterData),\n ...(Array.isArray(extraFilterCategories) &&\n extraFilterCategories.length > 0\n ? extraFilterCategories\n : []),\n ],\n [userFilterData, extraFilterCategories]\n );\n\n const tableColumns = useMemo(\n () =>\n createTableColumns(\n mapSubjectNameToEnum,\n deleteEnabled ? handleOpenDelete : undefined,\n editEnabled ? handleOpenEdit : undefined,\n currentUserId\n ),\n [\n mapSubjectNameToEnum,\n deleteEnabled,\n editEnabled,\n handleOpenDelete,\n handleOpenEdit,\n currentUserId,\n ]\n );\n\n /**\n * Handle table params change\n * Note: TableProvider calls this on mount with initial params,\n * so no separate useEffect for initial fetch is needed\n */\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n const filters = buildFiltersFromParams(params);\n lastFiltersRef.current = filters;\n fetchRecommendedClass(filters);\n },\n [fetchRecommendedClass]\n );\n\n return (\n <div\n data-testid=\"recommended-class-history\"\n className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\"\n >\n {/* Delete confirmation dialog */}\n {deleteEnabled && (\n <AlertDialog\n isOpen={!!recommendedClassToDelete}\n onChangeOpen={(open) => {\n if (!open) {\n setRecommendedClassToDelete(null);\n }\n }}\n title=\"Excluir aula recomendada\"\n description={`Tem certeza que deseja excluir a aula \"${\n recommendedClassToDelete?.title ?? ''\n }\"? Esta ação não pode ser desfeita.`}\n submitButtonLabel=\"Excluir\"\n cancelButtonLabel=\"Cancelar\"\n submitAction=\"negative\"\n onSubmit={handleConfirmDelete}\n onCancel={() => setRecommendedClassToDelete(null)}\n />\n )}\n\n {/* Edit modal (title + dates) — keyed by id so it remounts per row,\n avoiding a stale-form flash when switching between lessons */}\n {editEnabled &&\n fetchRecommendedClassById &&\n updateRecommendedClass &&\n recommendedClassToEdit && (\n <EditRecommendedLessonModal\n key={recommendedClassToEdit.id}\n isOpen\n recommendedClassId={recommendedClassToEdit.id}\n fetchById={fetchRecommendedClassById}\n onUpdate={updateRecommendedClass}\n onClose={() => setRecommendedClassToEdit(null)}\n onSaved={() => {\n setRecommendedClassToEdit(null);\n reloadHistory();\n }}\n />\n )}\n\n {/* Background decoration */}\n <span className=\"absolute top-0 left-0 h-[150px] w-full z-0\" />\n\n {/* Main container */}\n <div className=\"flex flex-col w-full h-full max-w-[1350px] mx-auto z-10 lg:px-0 px-4 pt-4 sm:pt-0\">\n {/* Header Section */}\n <div className=\"flex flex-col sm:flex-row w-full mb-6 items-start sm:items-center sm:justify-between gap-0 sm:gap-4\">\n {/* Page Title */}\n <Text\n as=\"h1\"\n weight=\"bold\"\n className=\"leading-[28px] tracking-[0.2px] text-xl lg:text-2xl\"\n >\n {title}\n </Text>\n\n {/* Tabs Menu */}\n <div className=\"flex-shrink-0 lg:w-auto self-center sm:self-auto\">\n <Menu\n defaultValue={RecommendedClassPageTab.HISTORY}\n value={activeTab}\n onValueChange={handleTabChange}\n variant=\"menu2\"\n className=\"bg-transparent shadow-none px-0\"\n >\n <MenuContent\n variant=\"menu2\"\n className=\"w-full lg:w-auto max-w-full min-w-0\"\n >\n <MenuItem\n variant=\"menu2\"\n value={RecommendedClassPageTab.HISTORY}\n data-testid=\"menu-item-history\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Histórico\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={RecommendedClassPageTab.DRAFTS}\n data-testid=\"menu-item-drafts\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Rascunhos\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={RecommendedClassPageTab.MODELS}\n data-testid=\"menu-item-models\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Modelos\n </MenuItem>\n </MenuContent>\n </Menu>\n </div>\n </div>\n\n {/* Content Area */}\n <div className=\"flex flex-col items-center w-full min-h-0 flex-1\">\n {activeTab === RecommendedClassPageTab.HISTORY && (\n <>\n {/* Error State */}\n {error ? (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-error-500\">\n {error}\n </Text>\n </div>\n ) : (\n <div className=\"w-full\">\n <TableProvider\n data={recommendedClass}\n headers={tableColumns}\n loading={loading}\n variant=\"borderless\"\n enableSearch\n enableFilters\n enableTableSort\n enablePagination\n enableRowClick\n initialFilters={initialFilterConfigs}\n paginationConfig={{\n itemLabel: 'aulas',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: 10,\n totalItems: pagination.total,\n totalPages: pagination.totalPages,\n }}\n searchPlaceholder={searchPlaceholder}\n noSearchResultState={{\n image: noSearchImage,\n }}\n emptyState={{\n component: (\n <EmptyState\n image={emptyStateImage}\n title=\"Crie uma nova aula\"\n description=\"Selecione um conjunto de aulas organizadas por tema e ajude seus alunos a estudarem de forma estruturada e eficiente!\"\n buttonText={createButtonText}\n buttonIcon={<PlusIcon size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={onCreateLesson}\n />\n ),\n }}\n onParamsChange={handleParamsChange}\n onRowClick={onRowClick}\n >\n {(renderProps: unknown) => {\n const {\n controls,\n table,\n pagination: paginationComponent,\n } = renderProps as {\n controls: ReactNode;\n table: ReactNode;\n pagination: ReactNode;\n };\n return (\n <div className=\"space-y-4\">\n {/* Header row: Button on left, Controls on right */}\n <div className=\"flex items-center justify-between gap-4\">\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={onCreateLesson}\n iconLeft={<PlusIcon size={18} weight=\"bold\" />}\n >\n {createButtonText}\n </Button>\n {controls}\n </div>\n {/* Table and pagination */}\n <div className=\"bg-background rounded-xl p-6 space-y-4\">\n {table}\n {paginationComponent}\n </div>\n </div>\n );\n }}\n </TableProvider>\n </div>\n )}\n </>\n )}\n\n {activeTab === RecommendedClassPageTab.DRAFTS &&\n fetchRecommendedClassDrafts &&\n deleteRecommendedClassDraft &&\n onCreateLesson && (\n <RecommendedClassDraftsTab\n fetchRecommendedClassDrafts={fetchRecommendedClassDrafts}\n deleteRecommendedClassDraft={deleteRecommendedClassDraft}\n onCreateDraft={onCreateLesson}\n onSendDraft={onSendDraft}\n onEditDraft={onEditDraft}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n />\n )}\n\n {activeTab === RecommendedClassPageTab.MODELS &&\n fetchRecommendedClassModels &&\n deleteRecommendedClassModel &&\n onCreateModel && (\n <RecommendedClassModelsTab\n fetchRecommendedClassModels={fetchRecommendedClassModels}\n deleteRecommendedClassModel={deleteRecommendedClassModel}\n onCreateModel={onCreateModel}\n onSendLesson={onSendLesson}\n onEditModel={onEditModel}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n />\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport default RecommendedLessonsHistory;\n","import type { ChangeEvent, FC } from 'react';\nimport { useCallback } from 'react';\nimport { FloppyDiskIcon } from '@phosphor-icons/react/dist/csr/FloppyDisk';\nimport Modal from '../Modal/Modal';\nimport Input from '../Input/Input';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport DeadlineStep from '../shared/SendModalBase/components/DeadlineStep';\nimport { useEditModalForm } from '../shared/SendModalBase/hooks/useEditModalForm';\nimport { splitDateTime } from '../shared/SendModalBase/utils/splitDateTime';\nimport { validateDeadlineStep } from '../SendActivityModal/validation';\nimport type {\n SendActivityFormData,\n StepErrors,\n} from '../SendActivityModal/types';\nimport useToastStore from '../Toast/utils/ToastStore';\nimport { buildISODateTime } from '../ActivityCreate/ActivityCreate.utils';\nimport type {\n RecommendedClassData,\n UpdateRecommendedClassData,\n} from '../../types/recommendedLessons';\n\n/** Fields edited by the quick-edit modal */\ntype EditFormData = Partial<\n Pick<\n SendActivityFormData,\n 'title' | 'startDate' | 'startTime' | 'finalDate' | 'finalTime'\n >\n>;\n\n/**\n * Props for the \"Editar aula recomendada\" modal.\n */\nexport interface EditRecommendedLessonModalProps {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Id of the recommended class being edited */\n recommendedClassId?: string;\n /** Loads the current recommended class data to pre-fill the form */\n fetchById: (id: string) => Promise<RecommendedClassData>;\n /** Persists the edited title and dates */\n onUpdate: (id: string, data: UpdateRecommendedClassData) => Promise<void>;\n /** Close the modal without saving */\n onClose: () => void;\n /** Fired after a successful save (consumer reloads the list + closes) */\n onSaved: () => void;\n}\n\nconst TITLE_REQUIRED =\n 'Campo obrigatório! Por favor, preencha este campo para continuar.';\n\n/**\n * Quick-edit modal for a recommended class, limited to the editable fields the\n * product allows: title and the start/deadline dates. Loads via the injected\n * `fetchById` and saves via the injected `onUpdate` (PATCH /recommended-class/:id).\n * @returns the edit modal element\n */\nexport const EditRecommendedLessonModal: FC<\n EditRecommendedLessonModalProps\n> = ({ isOpen, recommendedClassId, fetchById, onUpdate, onClose, onSaved }) => {\n const addToast = useToastStore((state) => state.addToast);\n\n /** Fetch the recommended class and map it to the editable form fields */\n const load = useCallback(async (): Promise<EditFormData> => {\n if (!recommendedClassId) return {};\n const data = await fetchById(recommendedClassId);\n const start = splitDateTime(data.startDate);\n const final = splitDateTime(data.finalDate);\n return {\n title: data.title ?? '',\n startDate: start.date,\n startTime: start.time || '00:00',\n finalDate: final.date,\n finalTime: final.time || '23:59',\n };\n }, [fetchById, recommendedClassId]);\n\n const onLoadError = useCallback(() => {\n addToast({\n title: 'Erro ao carregar aula recomendada',\n action: 'warning',\n position: 'top-right',\n });\n onClose();\n }, [addToast, onClose]);\n\n const {\n formData,\n errors,\n setErrors,\n loading,\n saving,\n setSaving,\n updateFormData,\n dateHandlers,\n } = useEditModalForm<EditFormData>({\n isOpen,\n enabled: !!recommendedClassId,\n load,\n onLoadError,\n });\n\n const handleTitleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) =>\n updateFormData({ title: e.target.value }),\n [updateFormData]\n );\n\n const handleSubmit = useCallback(async () => {\n if (!recommendedClassId) return;\n\n const validationErrors: StepErrors = validateDeadlineStep(formData);\n if (!formData.title || formData.title.trim().length === 0) {\n validationErrors.title = TITLE_REQUIRED;\n }\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n return;\n }\n\n setSaving(true);\n try {\n await onUpdate(recommendedClassId, {\n title: formData.title?.trim(),\n startDate: buildISODateTime(\n formData.startDate ?? '',\n formData.startTime || '00:00'\n ),\n finalDate: buildISODateTime(\n formData.finalDate ?? '',\n formData.finalTime || '23:59'\n ),\n });\n addToast({\n title: 'Aula recomendada atualizada com sucesso',\n action: 'success',\n position: 'top-right',\n });\n onSaved();\n } catch {\n addToast({\n title: 'Erro ao atualizar aula recomendada',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setSaving(false);\n }\n }, [recommendedClassId, formData, onUpdate, addToast, onSaved]);\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Editar aula recomendada\"\n size=\"md\"\n contentClassName=\"flex flex-col gap-8 sm:gap-10 max-h-[70vh] overflow-y-auto\"\n footer={\n <div className=\"flex flex-col-reverse sm:flex-row items-center justify-between gap-3 w-full\">\n <Button\n variant=\"link\"\n action=\"primary\"\n onClick={onClose}\n className=\"w-full sm:w-auto\"\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={handleSubmit}\n disabled={saving || loading}\n iconLeft={<FloppyDiskIcon size={16} />}\n className=\"w-full sm:w-auto\"\n >\n {saving ? 'Salvando...' : 'Salvar alterações'}\n </Button>\n </div>\n }\n >\n {loading ? (\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando...\n </Text>\n ) : (\n <div className=\"flex flex-col gap-6\">\n <Input\n label=\"Título\"\n placeholder=\"Digite o título da aula\"\n value={formData.title || ''}\n onChange={handleTitleChange}\n variant=\"rounded\"\n required\n errorMessage={errors.title}\n />\n\n <DeadlineStep\n startDate={formData.startDate ?? ''}\n startTime={formData.startTime ?? ''}\n finalDate={formData.finalDate ?? ''}\n finalTime={formData.finalTime ?? ''}\n onStartDateChange={dateHandlers.handleStartDateChange}\n onStartTimeChange={dateHandlers.handleStartTimeChange}\n onFinalDateChange={dateHandlers.handleFinalDateChange}\n onFinalTimeChange={dateHandlers.handleFinalTimeChange}\n errors={{\n startDate: errors.startDate,\n startTime: errors.startTime,\n finalDate: errors.finalDate,\n finalTime: errors.finalTime,\n }}\n />\n </div>\n )}\n </Modal>\n );\n};\n\nexport default EditRecommendedLessonModal;\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { Dispatch, SetStateAction } from 'react';\nimport { useDateTimeHandlers } from './useDateTimeHandlers';\nimport type { UseDateTimeHandlersReturn } from './useDateTimeHandlers';\nimport type { StepErrors } from '../../../SendActivityModal/types';\n\n/** Minimal shape required so date/time handlers can update the form */\ninterface DateTimeFields {\n startDate?: string;\n startTime?: string;\n finalDate?: string;\n finalTime?: string;\n}\n\n/**\n * Params for {@link useEditModalForm}.\n */\nexport interface UseEditModalFormParams<T extends DateTimeFields> {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Extra precondition for loading (e.g. has id + api client). Defaults to true. */\n enabled?: boolean;\n /** Fetches the entity and maps it to the form shape. Should be stable (useCallback). */\n load: () => Promise<T>;\n /** Called when the load fails (e.g. toast + close). Should be stable (useCallback). */\n onLoadError: () => void;\n}\n\n/**\n * Return value of {@link useEditModalForm}.\n */\nexport interface UseEditModalFormReturn<T extends DateTimeFields> {\n formData: T;\n setFormData: Dispatch<SetStateAction<T>>;\n errors: StepErrors;\n setErrors: Dispatch<SetStateAction<StepErrors>>;\n loading: boolean;\n saving: boolean;\n setSaving: Dispatch<SetStateAction<boolean>>;\n updateFormData: (data: Partial<T>) => void;\n dateHandlers: UseDateTimeHandlersReturn;\n}\n\n/**\n * Shared plumbing for \"quick edit\" modals (title/dates): form state, validation\n * errors, loading/saving flags, a merge helper that clears errors, date/time\n * handlers, and a generic load-on-open effect with cancellation.\n *\n * The modal-specific fetch + mapping lives in `load`; the error feedback\n * (toast/close) lives in `onLoadError`.\n *\n * @param params - {@link UseEditModalFormParams}\n * @returns form state and helpers ({@link UseEditModalFormReturn})\n */\nexport function useEditModalForm<T extends DateTimeFields>({\n isOpen,\n enabled = true,\n load,\n onLoadError,\n}: UseEditModalFormParams<T>): UseEditModalFormReturn<T> {\n const [formData, setFormData] = useState<T>({} as T);\n const [errors, setErrors] = useState<StepErrors>({});\n const [loading, setLoading] = useState(false);\n const [saving, setSaving] = useState(false);\n\n /** Merge a partial into the form and clear any standing validation errors */\n const updateFormData = useCallback((data: Partial<T>) => {\n setFormData((prev) => ({ ...prev, ...data }));\n setErrors({});\n }, []);\n\n const dateHandlers = useDateTimeHandlers<T>({ setFormData: updateFormData });\n\n // Keep the latest callbacks in refs so the load effect depends only on\n // [isOpen, enabled] and never loops when callers pass inline functions.\n const loadRef = useRef(load);\n loadRef.current = load;\n const onLoadErrorRef = useRef(onLoadError);\n onLoadErrorRef.current = onLoadError;\n\n useEffect(() => {\n if (!isOpen || !enabled) return;\n\n let active = true;\n const run = async () => {\n setLoading(true);\n setErrors({});\n try {\n const data = await loadRef.current();\n if (active) setFormData(data);\n } catch {\n if (active) onLoadErrorRef.current();\n } finally {\n if (active) setLoading(false);\n }\n };\n\n run();\n return () => {\n active = false;\n };\n }, [isOpen, enabled]);\n\n return {\n formData,\n setFormData,\n errors,\n setErrors,\n loading,\n saving,\n setSaving,\n updateFormData,\n dateHandlers,\n };\n}\n","/** Zero-pad a number to two digits */\nconst pad = (value: number): string => String(value).padStart(2, '0');\n\n/**\n * Split an ISO datetime into the local `date` (YYYY-MM-DD) + `time` (HH:MM)\n * pair expected by `DateTimeInput`. Mirrors the local interpretation used by\n * `buildISODateTime` so the round-trip is stable.\n *\n * @param iso - ISO datetime string (or null/undefined)\n * @returns object with local `date` and `time` (empty strings when absent/invalid)\n */\nexport const splitDateTime = (\n iso?: string | null\n): { date: string; time: string } => {\n if (!iso) return { date: '', time: '' };\n const parsed = new Date(iso);\n if (Number.isNaN(parsed.getTime())) return { date: '', time: '' };\n return {\n date: `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())}`,\n time: `${pad(parsed.getHours())}:${pad(parsed.getMinutes())}`,\n };\n};\n","import type { ReactNode } from 'react';\nimport { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport Button from '../../Button/Button';\nimport EmptyState from '../../EmptyState/EmptyState';\nimport { TableProvider } from '../../TableProvider/TableProvider';\nimport { AlertDialog } from '../../AlertDialog/AlertDialog';\nimport Toaster, { useToast } from '../../Toast/utils/Toaster';\nimport { ErrorDisplay } from '../../ActivitiesHistory/components/ErrorDisplay';\nimport type {\n TableParams,\n ColumnConfig,\n} from '../../TableProvider/TableProvider';\nimport type { FilterConfig } from '../../Filter';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\nimport type { BaseModelItem } from './createModelsTableColumnsBase';\nimport type { BaseApiClient } from '../../../types/api';\n\n/**\n * Configuration for entity-specific text and labels\n */\nexport interface ModelsTabConfig {\n /** Entity name for messages (e.g., \"aula\", \"atividade\") */\n entityName: string;\n /** Plural entity name (e.g., \"aulas\", \"atividades\") */\n entityNamePlural: string;\n /** data-testid attribute */\n testId: string;\n /** Empty state title */\n emptyStateTitle: string;\n /** Empty state description */\n emptyStateDescription: string;\n /** Search placeholder */\n searchPlaceholder: string;\n}\n\n/**\n * Return type for the models hook\n */\nexport interface UseModelsReturn<T> {\n models: T[];\n loading: boolean;\n error: string | null;\n pagination: { total: number; totalPages: number };\n fetchModels: (\n filters: Record<string, unknown>,\n subjectsMap?: Map<string, string>\n ) => void;\n deleteModel: (id: string) => Promise<boolean>;\n}\n\n/**\n * Props for ModelsTabBase component\n */\nexport interface ModelsTabBaseProps<\n T extends BaseModelItem,\n TFilters,\n TResponse,\n TUserFilterData,\n> {\n /** Function to fetch models from API */\n fetchModels: (filters?: TFilters) => Promise<TResponse>;\n /** Function to delete a model */\n deleteModel: (id: string) => Promise<void>;\n /** Callback when create model button is clicked */\n onCreateModel: () => void;\n /** Callback when send button is clicked on a model */\n onSend?: (model: T) => void;\n /** Callback when edit model button is clicked */\n onEditModel?: (model: T) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: TUserFilterData;\n /**\n * Map of subject IDs to names for models display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches.\n */\n subjectsMap?: Map<string, string>;\n /** Configuration for entity-specific text */\n config: ModelsTabConfig;\n /** Function to create table columns */\n createTableColumns: (\n mapSubjectNameToEnum: ((name: string) => SubjectEnum | null) | undefined,\n onSend: ((model: T) => void) | undefined,\n onEdit: ((model: T) => void) | undefined,\n onDelete: (model: T) => void\n ) => ColumnConfig<T>[];\n /** Function to create filter configs */\n createFiltersConfig: (userFilterData?: TUserFilterData) => FilterConfig[];\n /** Function to build filters from table params */\n buildFiltersFromParams: (params: TableParams) => TFilters;\n /** Hook creator function that receives an API client */\n createUseModels: (apiClient: BaseApiClient) => () => UseModelsReturn<T>;\n}\n\n/**\n * Generic ModelsTab base component that can be used by both\n * RecommendedClassModels (Lessons) and ActivityModels with type-safe generics.\n */\nexport const ModelsTabBase = <\n T extends BaseModelItem,\n TFilters,\n TResponse,\n TUserFilterData,\n>({\n fetchModels: fetchModelsProp,\n deleteModel: deleteModelProp,\n onCreateModel,\n onSend,\n onEditModel,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n subjectsMap,\n config,\n createTableColumns,\n createFiltersConfig,\n buildFiltersFromParams,\n createUseModels,\n}: ModelsTabBaseProps<T, TFilters, TResponse, TUserFilterData>) => {\n const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);\n const [modelToDelete, setModelToDelete] = useState<T | null>(null);\n\n // Toast hook\n const { addToast } = useToast();\n\n // Use refs to keep stable references\n const fetchModelsRef = useRef(fetchModelsProp);\n fetchModelsRef.current = fetchModelsProp;\n\n const deleteModelRef = useRef(deleteModelProp);\n deleteModelRef.current = deleteModelProp;\n\n // Keep stable reference to subjectsMap to avoid unnecessary re-fetches\n const subjectsMapRef = useRef(subjectsMap);\n subjectsMapRef.current = subjectsMap;\n\n // Create an API client adapter that wraps the fetch/delete functions\n const apiClientAdapter = useMemo(\n () => ({\n get: async <R,>(\n _url: string,\n options?: { params?: Record<string, unknown> }\n ) => {\n const result = await fetchModelsRef.current(\n options?.params as TFilters\n );\n return { data: result as R };\n },\n delete: async <R,>(_url: string): Promise<{ data: R }> => {\n let cleanUrl = _url.split('?')[0];\n while (cleanUrl.endsWith('/')) {\n cleanUrl = cleanUrl.slice(0, -1);\n }\n const id = cleanUrl.split('/').pop() || '';\n\n if (!id) {\n throw new Error(`Cannot extract ID from URL: ${_url}`);\n }\n\n await deleteModelRef.current(id);\n return { data: {} as R };\n },\n post: async <R,>(): Promise<{ data: R }> => {\n throw new Error('post not implemented in ModelsTabBase adapter');\n },\n patch: async <R,>(): Promise<{ data: R }> => {\n throw new Error('patch not implemented in ModelsTabBase adapter');\n },\n }),\n []\n );\n\n // Create hook instance with the API client adapter\n const useModels = useMemo(\n () => createUseModels(apiClientAdapter),\n [createUseModels, apiClientAdapter]\n );\n\n // Use the hook\n const {\n models,\n loading: modelsLoading,\n error: modelsError,\n pagination: modelsPagination,\n fetchModels,\n deleteModel,\n }: UseModelsReturn<T> = useModels();\n\n // Create filter configuration\n const modelsFilterConfigs = useMemo(\n () => createFiltersConfig(userFilterData),\n [createFiltersConfig, userFilterData]\n );\n\n const handleDeleteClick = useCallback((model: T) => {\n setModelToDelete(model);\n setDeleteDialogOpen(true);\n }, []);\n\n const modelsTableColumns = useMemo(\n () =>\n createTableColumns(\n mapSubjectNameToEnum,\n onSend,\n onEditModel,\n handleDeleteClick\n ),\n [\n createTableColumns,\n mapSubjectNameToEnum,\n onSend,\n onEditModel,\n handleDeleteClick,\n ]\n );\n\n /**\n * Handle table params change\n */\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n const filters = buildFiltersFromParams(params);\n fetchModels(filters as Record<string, unknown>, subjectsMapRef.current);\n },\n [buildFiltersFromParams, fetchModels]\n );\n\n /**\n * Fetch models on mount\n */\n useEffect(() => {\n fetchModels({ page: 1, limit: 10 }, subjectsMapRef.current);\n }, [fetchModels]);\n\n /**\n * Handle confirm delete\n */\n const handleConfirmDelete = useCallback(async () => {\n if (modelToDelete) {\n const success = await deleteModel(modelToDelete.id);\n if (success) {\n addToast({ title: 'Modelo deletado com sucesso', action: 'success' });\n fetchModels({ page: 1, limit: 10 }, subjectsMapRef.current);\n } else {\n addToast({ title: 'Erro ao deletar modelo', action: 'warning' });\n }\n }\n setDeleteDialogOpen(false);\n setModelToDelete(null);\n }, [modelToDelete, deleteModel, fetchModels, addToast]);\n\n /**\n * Handle cancel delete\n */\n const handleCancelDelete = useCallback(() => {\n setDeleteDialogOpen(false);\n setModelToDelete(null);\n }, []);\n\n return (\n <>\n <Toaster />\n {modelsError ? (\n <ErrorDisplay error={modelsError} />\n ) : (\n <div className=\"w-full\" data-testid={config.testId}>\n <TableProvider\n data={models}\n headers={modelsTableColumns}\n loading={modelsLoading}\n variant=\"borderless\"\n enableSearch\n enableFilters\n enableTableSort\n enablePagination\n initialFilters={modelsFilterConfigs}\n paginationConfig={{\n itemLabel: 'modelos',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: 10,\n totalItems: modelsPagination.total,\n totalPages: modelsPagination.totalPages,\n }}\n searchPlaceholder={config.searchPlaceholder}\n noSearchResultState={{\n image: noSearchImage,\n }}\n emptyState={{\n component: (\n <EmptyState\n image={emptyStateImage}\n title={config.emptyStateTitle}\n description={config.emptyStateDescription}\n buttonText=\"Criar modelo\"\n buttonIcon={<PlusIcon size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={onCreateModel}\n />\n ),\n }}\n onParamsChange={handleParamsChange}\n >\n {(renderProps: unknown) => {\n const {\n controls,\n table,\n pagination: paginationComponent,\n } = renderProps as {\n controls: ReactNode;\n table: ReactNode;\n pagination: ReactNode;\n };\n return (\n <div className=\"space-y-4\">\n {/* Header row: Button on left, Controls on right */}\n <div className=\"flex items-center justify-between gap-4\">\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={onCreateModel}\n iconLeft={<PlusIcon size={18} weight=\"bold\" />}\n >\n Criar modelo\n </Button>\n {controls}\n </div>\n {/* Table and pagination */}\n <div className=\"bg-background rounded-xl p-6 space-y-4\">\n {table}\n {paginationComponent}\n </div>\n </div>\n );\n }}\n </TableProvider>\n </div>\n )}\n\n <AlertDialog\n isOpen={deleteDialogOpen}\n onChangeOpen={setDeleteDialogOpen}\n title=\"Deletar modelo\"\n description={`Tem certeza que deseja deletar o modelo \"${modelToDelete?.title}\"? Esta ação não pode ser desfeita.`}\n submitButtonLabel=\"Deletar\"\n cancelButtonLabel=\"Cancelar\"\n onSubmit={handleConfirmDelete}\n onCancel={handleCancelDelete}\n />\n </>\n );\n};\n\nexport default ModelsTabBase;\n","import Text from '../../Text/Text';\n\n/**\n * Props for the ErrorDisplay component\n */\ninterface ErrorDisplayProps {\n /** Error message to display */\n error: string;\n}\n\n/**\n * Component to display error messages in the activities history\n * @param props - ErrorDisplay props\n * @returns Error display component\n */\nexport const ErrorDisplay = ({ error }: ErrorDisplayProps) => (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-error-500\">\n {error}\n </Text>\n </div>\n);\n","import type { FilterConfig } from '../../Filter';\nimport type {\n RecommendedClassUserFilterData,\n RecommendedClassFilterOption,\n} from '../../../types/recommendedLessons';\n\n/**\n * Get subject options from user data\n * @param data - User filter data containing subjects\n * @returns Array of subject options for filter dropdown\n */\nconst getSubjectOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.subjects) return [];\n return data.subjects.map((subject) => ({\n id: subject.id,\n name: subject.name,\n }));\n};\n\n/**\n * Create filter configuration for recommendedClass models\n * Simplified filter with only subject selection\n * @param userData - User data for populating filter options\n * @returns Array of filter configurations\n */\nexport const createRecommendedClassModelsFiltersConfig = (\n userData: RecommendedClassUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n ],\n },\n];\n","import type { TableParams } from '../../TableProvider/TableProvider';\nimport type { RecommendedClassModelFilters } from '../../../types/recommendedLessons';\n\n/**\n * Build recommendedClass models filters from table params\n * Converts TableProvider parameters to API filter format\n * @param params - Table parameters from TableProvider\n * @returns RecommendedClass model filters for API request\n */\nexport const buildRecommendedClassModelsFiltersFromParams = (\n params: TableParams\n): RecommendedClassModelFilters => {\n const filters: RecommendedClassModelFilters = {\n page: params.page,\n limit: params.limit,\n };\n\n if (params.search) {\n filters.search = params.search;\n }\n\n // Subject filter (single selection)\n if (Array.isArray(params.subject) && params.subject.length > 0) {\n filters.subjectId = params.subject[0];\n }\n\n return filters;\n};\n","import {\n ModelsTabBase,\n createModelsTableColumnsBase,\n type ModelsTabConfig,\n type ModelsColumnsConfig,\n} from '../../shared/ModelsTabBase';\nimport { createRecommendedClassModelsFiltersConfig } from '../config/modelsFiltersConfig';\nimport { buildRecommendedClassModelsFiltersFromParams } from '../utils/filterBuilders';\nimport { createUseRecommendedClassModels } from '../../../hooks/useRecommendedClassModels';\nimport type {\n RecommendedClassModelTableItem,\n RecommendedClassModelFilters,\n RecommendedClassModelsApiResponse,\n RecommendedClassUserFilterData,\n} from '../../../types/recommendedLessons';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Configuration for recommendedClass models tab\n */\nconst RECOMMENDED_CLASS_MODELS_CONFIG: ModelsTabConfig = {\n entityName: 'aula',\n entityNamePlural: 'aulas',\n testId: 'recommendedClass-models-tab',\n emptyStateTitle: 'Crie modelos para agilizar suas aulas',\n emptyStateDescription:\n 'Salve modelos de aulas recomendadas para reutilizar e enviar rapidamente para suas turmas!',\n searchPlaceholder: 'Buscar modelo',\n};\n\n/**\n * Configuration for recommendedClass models table columns\n */\nconst RECOMMENDED_CLASS_COLUMNS_CONFIG: ModelsColumnsConfig = {\n sendButtonLabel: 'Enviar aula',\n sendButtonAriaLabel: 'Enviar aula',\n deleteButtonAriaLabel: 'Deletar modelo',\n editButtonAriaLabel: 'Editar modelo',\n};\n\n/**\n * Props for the RecommendedClassModelsTab component\n */\nexport interface RecommendedClassModelsTabProps {\n /** Function to fetch recommendedClass models from API */\n fetchRecommendedClassModels: (\n filters?: RecommendedClassModelFilters\n ) => Promise<RecommendedClassModelsApiResponse>;\n /** Function to delete a recommendedClass model */\n deleteRecommendedClassModel: (id: string) => Promise<void>;\n /** Callback when create model button is clicked */\n onCreateModel: () => void;\n /** Callback when send lesson button is clicked on a model */\n onSendLesson?: (model: RecommendedClassModelTableItem) => void;\n /** Callback when edit model button is clicked */\n onEditModel?: (model: RecommendedClassModelTableItem) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: RecommendedClassUserFilterData;\n /**\n * Map of subject IDs to names for models display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches.\n */\n subjectsMap?: Map<string, string>;\n}\n\n/**\n * RecommendedClassModelsTab component\n * Displays recommendedClass models with filters, pagination, and CRUD actions.\n * Uses the shared ModelsTabBase component for common functionality.\n */\nexport const RecommendedClassModelsTab = ({\n fetchRecommendedClassModels,\n deleteRecommendedClassModel,\n onCreateModel,\n onSendLesson,\n onEditModel,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n subjectsMap,\n}: RecommendedClassModelsTabProps) => (\n <ModelsTabBase<\n RecommendedClassModelTableItem,\n RecommendedClassModelFilters,\n RecommendedClassModelsApiResponse,\n RecommendedClassUserFilterData\n >\n fetchModels={fetchRecommendedClassModels}\n deleteModel={deleteRecommendedClassModel}\n onCreateModel={onCreateModel}\n onSend={onSendLesson}\n onEditModel={onEditModel}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n config={RECOMMENDED_CLASS_MODELS_CONFIG}\n createTableColumns={(mapSubject, send, edit, del) =>\n createModelsTableColumnsBase(\n mapSubject,\n send,\n edit,\n del,\n RECOMMENDED_CLASS_COLUMNS_CONFIG\n )\n }\n createFiltersConfig={createRecommendedClassModelsFiltersConfig}\n buildFiltersFromParams={buildRecommendedClassModelsFiltersFromParams}\n createUseModels={createUseRecommendedClassModels}\n />\n);\n\nexport default RecommendedClassModelsTab;\n","import type { FilterConfig } from '../../Filter';\nimport type {\n RecommendedClassUserFilterData,\n RecommendedClassFilterOption,\n} from '../../../types/recommendedLessons';\n\n/**\n * Get subject options from user data\n * @param data - User filter data containing subjects\n * @returns Array of subject options for filter dropdown\n */\nconst getSubjectOptions = (\n data: RecommendedClassUserFilterData | undefined\n): RecommendedClassFilterOption[] => {\n if (!data?.subjects) return [];\n return data.subjects.map((subject) => ({\n id: subject.id,\n name: subject.name,\n }));\n};\n\n/**\n * Create filter configuration for recommendedClass drafts\n * Simplified filter with only subject selection\n * @param userData - User data for populating filter options\n * @returns Array of filter configurations\n */\nexport const createRecommendedClassDraftsFiltersConfig = (\n userData: RecommendedClassUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n ],\n },\n];\n","import {\n ModelsTabBase,\n createModelsTableColumnsBase,\n type ModelsTabConfig,\n type ModelsColumnsConfig,\n} from '../../shared/ModelsTabBase';\nimport { createRecommendedClassDraftsFiltersConfig } from '../config/draftsFiltersConfig';\nimport { buildRecommendedClassModelsFiltersFromParams } from '../utils/filterBuilders';\nimport { createUseRecommendedClassDrafts } from '../../../hooks/useRecommendedClassDrafts';\nimport type {\n RecommendedClassModelTableItem,\n RecommendedClassModelFilters,\n RecommendedClassModelsApiResponse,\n RecommendedClassUserFilterData,\n} from '../../../types/recommendedLessons';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Configuration for recommendedClass drafts tab\n */\nconst RECOMMENDED_CLASS_DRAFTS_CONFIG: ModelsTabConfig = {\n entityName: 'rascunho',\n entityNamePlural: 'rascunhos',\n testId: 'recommendedClass-drafts-tab',\n emptyStateTitle: 'Você não tem aulas recomendadas em rascunho',\n emptyStateDescription:\n 'As aulas recomendadas que você começar a criar, aparecerão aqui automaticamente como rascunhos. Tudo é salvo enquanto você cria, continue de onde parou quando quiser!',\n searchPlaceholder: 'Buscar rascunho',\n};\n\n/**\n * Configuration for recommendedClass drafts table columns\n */\nconst RECOMMENDED_CLASS_DRAFTS_COLUMNS_CONFIG: ModelsColumnsConfig = {\n sendButtonLabel: 'Enviar aula',\n sendButtonAriaLabel: 'Enviar rascunho',\n deleteButtonAriaLabel: 'Deletar rascunho',\n editButtonAriaLabel: 'Editar rascunho',\n};\n\n/**\n * Props for the RecommendedClassDraftsTab component\n */\nexport interface RecommendedClassDraftsTabProps {\n /** Function to fetch recommendedClass drafts from API */\n fetchRecommendedClassDrafts: (\n filters?: RecommendedClassModelFilters\n ) => Promise<RecommendedClassModelsApiResponse>;\n /** Function to delete a recommendedClass draft */\n deleteRecommendedClassDraft: (id: string) => Promise<void>;\n /** Callback when create draft button is clicked */\n onCreateDraft: () => void;\n /** Callback when send lesson button is clicked on a draft */\n onSendDraft?: (draft: RecommendedClassModelTableItem) => void;\n /** Callback when edit draft button is clicked */\n onEditDraft?: (draft: RecommendedClassModelTableItem) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: RecommendedClassUserFilterData;\n /**\n * Map of subject IDs to names for drafts display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches.\n */\n subjectsMap?: Map<string, string>;\n}\n\n/**\n * RecommendedClassDraftsTab component\n * Displays recommendedClass drafts with filters, pagination, and CRUD actions.\n * Uses the shared ModelsTabBase component for common functionality.\n */\nexport const RecommendedClassDraftsTab = ({\n fetchRecommendedClassDrafts,\n deleteRecommendedClassDraft,\n onCreateDraft,\n onSendDraft,\n onEditDraft,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n subjectsMap,\n}: RecommendedClassDraftsTabProps) => (\n <ModelsTabBase<\n RecommendedClassModelTableItem,\n RecommendedClassModelFilters,\n RecommendedClassModelsApiResponse,\n RecommendedClassUserFilterData\n >\n fetchModels={fetchRecommendedClassDrafts}\n deleteModel={deleteRecommendedClassDraft}\n onCreateModel={onCreateDraft}\n onSend={onSendDraft}\n onEditModel={onEditDraft}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n config={RECOMMENDED_CLASS_DRAFTS_CONFIG}\n createTableColumns={(mapSubject, send, edit, del) =>\n createModelsTableColumnsBase(\n mapSubject,\n send,\n edit,\n del,\n RECOMMENDED_CLASS_DRAFTS_COLUMNS_CONFIG\n )\n }\n createFiltersConfig={createRecommendedClassDraftsFiltersConfig}\n buildFiltersFromParams={buildRecommendedClassModelsFiltersFromParams}\n createUseModels={createUseRecommendedClassDrafts}\n />\n);\n\nexport default RecommendedClassDraftsTab;\n","import { useState, useCallback, useEffect } from 'react';\nimport { z } from 'zod';\nimport type {\n LessonDetailsData,\n RecommendedClassMetadata,\n RecommendedClassDetailsData,\n RecommendedClassBreakdown,\n RecommendedClassApiResponse,\n RecommendedClassDetailsApiResponse,\n RecommendedClassHistoryApiResponse,\n} from '../types/recommendedLessons';\n\n// ============================================\n// Zod Schemas for API Response Validation\n// ============================================\n\n/**\n * Schema for subject in lesson\n */\nconst recommendedClassLessonSubjectSchema = z.object({\n id: z.string(),\n name: z.string(),\n color: z.string(),\n icon: z.string(),\n});\n\n/**\n * Schema for lesson content/subtopic/topic\n */\nconst lessonContentSchema = z.object({\n id: z.string(),\n name: z.string(),\n});\n\n/**\n * Schema for lesson in recommendedClass\n */\nconst recommendedClassLessonSchema = z.object({\n id: z.string(),\n content: lessonContentSchema,\n subtopic: lessonContentSchema,\n topic: lessonContentSchema,\n subject: recommendedClassLessonSubjectSchema,\n});\n\n/**\n * Schema for lesson progress\n */\nconst recommendedClassLessonProgressSchema = z.object({\n id: z.string(),\n userId: z.string(),\n lessonId: z.string(),\n progress: z.number(),\n lesson: recommendedClassLessonSchema,\n});\n\n/**\n * Schema for lesson recommendedClass item\n */\nconst recommendedClassLessonsItemSchema = z.object({\n recommendedClassId: z.string(),\n supLessonsProgressId: z.string(),\n supLessonsProgress: recommendedClassLessonProgressSchema,\n});\n\n/**\n * Schema for recommendedClass metadata from /recommendedClass/{id}\n */\nconst recommendedClassMetadataSchema = z.object({\n id: z.string(),\n title: z.string(),\n startDate: z.string(),\n finalDate: z.string(),\n progress: z.number(),\n lessons: z.array(recommendedClassLessonsItemSchema),\n});\n\n/**\n * Schema for /recommendedClass/{id} API response\n */\nexport const recommendedClassApiResponseSchema = z.object({\n message: z.string(),\n data: recommendedClassMetadataSchema,\n});\n\n/**\n * Schema for student in details\n */\nconst recommendedClassDetailStudentSchema = z.object({\n userInstitutionId: z.string(),\n userId: z.string(),\n name: z.string(),\n progress: z.number(),\n completedAt: z.string().nullable(),\n avgScore: z.number().nullable(),\n daysToComplete: z.number().nullable(),\n});\n\n/**\n * Schema for aggregated stats\n */\nconst recommendedClassDetailAggregatedSchema = z.object({\n completionPercentage: z.number(),\n avgScore: z.number().nullable(),\n});\n\n/**\n * Schema for content performance item\n */\nconst recommendedClassDetailContentPerformanceItemSchema = z\n .object({\n contentId: z.string(),\n contentName: z.string(),\n rate: z.number(),\n })\n .nullable();\n\n/**\n * Schema for content performance\n */\nconst recommendedClassDetailContentPerformanceSchema = z.object({\n best: recommendedClassDetailContentPerformanceItemSchema,\n worst: recommendedClassDetailContentPerformanceItemSchema,\n});\n\n/**\n * Schema for details data from /recommendedClass/{id}/details\n */\nconst recommendedClassDetailsDataSchema = z.object({\n students: z.array(recommendedClassDetailStudentSchema),\n aggregated: recommendedClassDetailAggregatedSchema,\n contentPerformance: recommendedClassDetailContentPerformanceSchema,\n});\n\n/**\n * Schema for /recommendedClass/{id}/details API response\n */\nexport const recommendedClassDetailsApiResponseSchema = z.object({\n message: z.string(),\n data: recommendedClassDetailsDataSchema,\n});\n\n/**\n * Schema for breakdown item from history\n */\nconst recommendedClassBreakdownSchema = z.object({\n classId: z.string().uuid(),\n className: z.string(),\n schoolId: z.string(),\n schoolName: z.string(),\n studentCount: z.number(),\n completedCount: z.number(),\n});\n\n/**\n * Schema for history recommendedClass item (partial, only what we need)\n */\nconst historyRecommendedClassItemSchema = z.object({\n recommendedClass: z.object({ id: z.string().uuid() }),\n breakdown: z.array(recommendedClassBreakdownSchema),\n});\n\n/**\n * Schema for history API response (partial)\n */\nexport const historyApiResponseSchema = z.object({\n message: z.string(),\n data: z.object({\n recommendedClass: z.array(historyRecommendedClassItemSchema),\n total: z.number(),\n }),\n});\n\n// ============================================\n// Hook Types\n// ============================================\n\n/**\n * Hook state interface\n */\nexport interface UseRecommendedLessonDetailsState {\n data: LessonDetailsData | null;\n loading: boolean;\n error: string | null;\n}\n\n/**\n * Hook return type\n */\nexport interface UseRecommendedLessonDetailsReturn extends UseRecommendedLessonDetailsState {\n refetch: () => Promise<void>;\n}\n\n/**\n * API client interface for fetching lesson details\n */\nexport interface LessonDetailsApiClient {\n /** Fetch recommendedClass metadata from /recommendedClass/{id} */\n fetchRecommendedClass: (id: string) => Promise<RecommendedClassApiResponse>;\n /** Fetch recommendedClass details from /recommendedClass/{id}/details */\n fetchRecommendedClassDetails: (\n id: string\n ) => Promise<RecommendedClassDetailsApiResponse>;\n /** Optional: Fetch breakdown from /recommended-class/history */\n fetchBreakdown?: (id: string) => Promise<RecommendedClassHistoryApiResponse>;\n}\n\n// ============================================\n// Error Handling\n// ============================================\n\n/**\n * Handle errors during lesson details fetch\n * @param error - Error object\n * @returns Error message for UI display\n */\nexport const handleLessonDetailsFetchError = (error: unknown): string => {\n if (error instanceof z.ZodError) {\n console.error('Erro ao validar dados dos detalhes da aula:', error);\n return 'Erro ao validar dados dos detalhes da aula';\n }\n\n console.error('Erro ao carregar detalhes da aula:', error);\n return 'Erro ao carregar detalhes da aula';\n};\n\n// ============================================\n// Hook Factory\n// ============================================\n\n/**\n * Factory function to create useRecommendedLessonDetails hook\n *\n * @param apiClient - Object containing API fetch functions\n * @returns Hook for managing recommended lesson details\n *\n * @example\n * ```tsx\n * // In your app setup\n * const apiClient = {\n * fetchRecommendedClass: async (id) => {\n * const response = await api.get(`/recommendedClass/${id}`);\n * return response.data;\n * },\n * fetchRecommendedClassDetails: async (id) => {\n * const response = await api.get(`/recommendedClass/${id}/details`);\n * return response.data;\n * },\n * fetchBreakdown: async (id) => {\n * const response = await api.get(`/recommended-class/history?search=${id}&limit=1`);\n * return response.data;\n * },\n * };\n *\n * const useRecommendedClassDetails = createUseRecommendedLessonDetails(apiClient);\n *\n * // In your component\n * const { data, loading, error, refetch } = useRecommendedClassDetails('recommendedClass-id-123');\n * ```\n */\nexport const createUseRecommendedLessonDetails = (\n apiClient: LessonDetailsApiClient\n) => {\n return (\n lessonId: string | undefined,\n userId?: string\n ): UseRecommendedLessonDetailsReturn => {\n const [state, setState] = useState<UseRecommendedLessonDetailsState>({\n data: null,\n loading: true,\n error: null,\n });\n\n /**\n * Fetch lesson details from multiple API endpoints\n */\n const fetchLessonDetails = useCallback(async () => {\n if (!lessonId) {\n setState({\n data: null,\n loading: false,\n error: 'ID da aula não encontrado',\n });\n return;\n }\n\n setState((prev) => ({ ...prev, loading: true, error: null }));\n\n try {\n // Fetch recommendedClass metadata and details in parallel\n // Breakdown is optional\n const promises: [\n Promise<RecommendedClassApiResponse>,\n Promise<RecommendedClassDetailsApiResponse>,\n Promise<RecommendedClassHistoryApiResponse | null>,\n ] = [\n apiClient.fetchRecommendedClass(lessonId),\n apiClient.fetchRecommendedClassDetails(lessonId),\n apiClient.fetchBreakdown\n ? apiClient.fetchBreakdown(lessonId)\n : Promise.resolve(null),\n ];\n\n const [recommendedClassResponse, detailsResponse, historyResponse] =\n await Promise.all(promises);\n\n // Validate responses with Zod\n const validatedRecommendedClass =\n recommendedClassApiResponseSchema.parse(recommendedClassResponse);\n const validatedDetails =\n recommendedClassDetailsApiResponseSchema.parse(detailsResponse);\n\n // Extract and validate breakdown if available\n let breakdown: RecommendedClassBreakdown | undefined;\n if (historyResponse) {\n const validatedHistory =\n historyApiResponseSchema.parse(historyResponse);\n const historyItem = validatedHistory.data.recommendedClass.find(\n (g) => g.recommendedClass.id === lessonId\n );\n breakdown = historyItem?.breakdown[0];\n }\n\n // Combine data\n const lessonData: LessonDetailsData = {\n recommendedClass:\n validatedRecommendedClass.data as RecommendedClassMetadata,\n details: validatedDetails.data as RecommendedClassDetailsData,\n breakdown,\n };\n\n setState({\n data: lessonData,\n loading: false,\n error: null,\n });\n } catch (error) {\n const errorMessage = handleLessonDetailsFetchError(error);\n setState({\n data: null,\n loading: false,\n error: errorMessage,\n });\n }\n }, [lessonId, userId]);\n\n // Fetch on mount and when lessonId changes\n useEffect(() => {\n fetchLessonDetails();\n }, [fetchLessonDetails]);\n\n return {\n ...state,\n refetch: fetchLessonDetails,\n };\n };\n};\n\n/**\n * Alias for createUseRecommendedLessonDetails\n */\nexport const createRecommendedLessonDetailsHook =\n createUseRecommendedLessonDetails;\n","import { useState } from 'react';\nimport Text from '../Text/Text';\nimport { Menu, MenuItem, MenuContent } from '../Menu/Menu';\nimport { HistoryTab } from './tabs/HistoryTab';\nimport { ModelsTab } from './tabs/ModelsTab';\nimport { DraftsTab } from './tabs/DraftsTab';\nimport type { SubjectEnum } from '../../enums/SubjectEnum';\nimport type {\n ActivityTableItem,\n ActivityHistoryFilters,\n ActivitiesHistoryApiResponse,\n ActivityUserFilterData,\n ActivityModelTableItem,\n ActivityModelFilters,\n ActivityModelsApiResponse,\n} from '../../types/activitiesHistory';\n\n/**\n * Enum for page tabs\n */\nenum PageTab {\n HISTORY = 'history',\n DRAFTS = 'drafts',\n MODELS = 'models',\n}\n\n/**\n * Page titles based on active tab\n */\nconst PAGE_TITLES: Record<PageTab, string> = {\n [PageTab.HISTORY]: 'Histórico de atividades',\n [PageTab.DRAFTS]: 'Rascunhos',\n [PageTab.MODELS]: 'Modelos de atividades',\n};\n\n/**\n * Props for the ActivitiesHistory component\n */\nexport interface ActivitiesHistoryProps {\n /** Function to fetch activities history from API */\n fetchActivitiesHistory: (\n filters?: ActivityHistoryFilters\n ) => Promise<ActivitiesHistoryApiResponse>;\n /** Function to fetch activity models from API */\n fetchActivityModels: (\n filters?: ActivityModelFilters\n ) => Promise<ActivityModelsApiResponse>;\n /** Function to delete an activity model */\n deleteActivityModel: (id: string) => Promise<void>;\n /** Callback when create activity button is clicked */\n onCreateActivity: () => void;\n /** Callback when create model button is clicked */\n onCreateModel: () => void;\n /** Callback when a history row is clicked */\n onRowClick: (row: ActivityTableItem) => void;\n /** Callback when send activity button is clicked on a model */\n onSendActivity?: (model: ActivityModelTableItem) => void;\n /** Callback when edit model button is clicked */\n onEditModel?: (model: ActivityModelTableItem) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: ActivityUserFilterData;\n /**\n * Map of subject IDs to names for models display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches when the MODELS tab is active.\n * @example\n * const subjectsMap = useMemo(() => {\n * const map = new Map();\n * subjects.forEach(s => map.set(s.id, s.name));\n * return map;\n * }, [subjects]);\n */\n subjectsMap?: Map<string, string>;\n}\n\n/**\n * ActivitiesHistory component\n * Displays activities history with tabs for history, drafts, and models.\n * Each tab is an independent component that manages its own state and data fetching.\n */\nexport const ActivitiesHistory = ({\n fetchActivitiesHistory,\n fetchActivityModels,\n deleteActivityModel,\n onCreateActivity,\n onCreateModel,\n onRowClick,\n onSendActivity,\n onEditModel,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n subjectsMap,\n}: ActivitiesHistoryProps) => {\n const [activeTab, setActiveTab] = useState<PageTab>(PageTab.HISTORY);\n\n return (\n <div\n data-testid=\"activities-history\"\n className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\"\n >\n {/* Background decoration */}\n <span className=\"absolute top-0 left-0 h-[150px] w-full z-0\" />\n\n {/* Main container */}\n <div className=\"flex flex-col w-full h-full max-w-[1350px] mx-auto z-10 lg:px-0 px-4 pt-4 sm:pt-0\">\n {/* Header Section */}\n <div className=\"flex flex-col sm:flex-row w-full mb-6 items-start sm:items-center sm:justify-between gap-0 sm:gap-4\">\n {/* Page Title */}\n <Text\n as=\"h1\"\n weight=\"bold\"\n className=\"leading-[28px] tracking-[0.2px] text-xl lg:text-2xl\"\n >\n {PAGE_TITLES[activeTab]}\n </Text>\n\n {/* Tabs Menu */}\n <div className=\"flex-shrink-0 lg:w-auto self-center sm:self-auto\">\n <Menu\n defaultValue={PageTab.HISTORY}\n value={activeTab}\n onValueChange={(value: string) => setActiveTab(value as PageTab)}\n variant=\"menu2\"\n className=\"bg-transparent shadow-none px-0\"\n >\n <MenuContent\n variant=\"menu2\"\n className=\"w-full lg:w-auto max-w-full min-w-0\"\n >\n <MenuItem\n variant=\"menu2\"\n value={PageTab.HISTORY}\n data-testid=\"menu-item-history\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Histórico\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={PageTab.DRAFTS}\n data-testid=\"menu-item-drafts\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Rascunhos\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={PageTab.MODELS}\n data-testid=\"menu-item-models\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Modelos\n </MenuItem>\n </MenuContent>\n </Menu>\n </div>\n </div>\n\n {/* Content Area */}\n <div className=\"flex flex-col items-center w-full min-h-0 flex-1\">\n {activeTab === PageTab.HISTORY && (\n <HistoryTab\n fetchActivitiesHistory={fetchActivitiesHistory}\n onCreateActivity={onCreateActivity}\n onRowClick={onRowClick}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n />\n )}\n\n {activeTab === PageTab.DRAFTS && <DraftsTab />}\n\n {activeTab === PageTab.MODELS && (\n <ModelsTab\n fetchActivityModels={fetchActivityModels}\n deleteActivityModel={deleteActivityModel}\n onCreateModel={onCreateModel}\n onSendActivity={onSendActivity}\n onEditModel={onEditModel}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n />\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport default ActivitiesHistory;\n","import type { ReactNode } from 'react';\nimport { useCallback, useMemo, useRef } from 'react';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport Button from '../../Button/Button';\nimport EmptyState from '../../EmptyState/EmptyState';\nimport { TableProvider } from '../../TableProvider/TableProvider';\nimport { ErrorDisplay } from '../components/ErrorDisplay';\nimport { createHistoryTableColumns } from '../config/historyTableColumns';\nimport { createHistoryFiltersConfig } from '../config/historyFiltersConfig';\nimport { buildHistoryFiltersFromParams } from '../utils/filterBuilders';\nimport {\n createUseActivitiesHistory,\n type UseActivitiesHistoryReturn,\n} from '../../../hooks/useActivitiesHistory';\nimport type { TableParams } from '../../TableProvider/TableProvider';\nimport type {\n ActivityTableItem,\n ActivityHistoryFilters,\n ActivitiesHistoryApiResponse,\n ActivityUserFilterData,\n} from '../../../types/activitiesHistory';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Props for the HistoryTab component\n */\nexport interface HistoryTabProps {\n /** Function to fetch activities history from API */\n fetchActivitiesHistory: (\n filters?: ActivityHistoryFilters\n ) => Promise<ActivitiesHistoryApiResponse>;\n /** Callback when create activity button is clicked */\n onCreateActivity: () => void;\n /** Callback when a history row is clicked */\n onRowClick: (row: ActivityTableItem) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: ActivityUserFilterData;\n}\n\n/**\n * HistoryTab component\n * Independent component for displaying activities history with filters and pagination\n */\nexport const HistoryTab = ({\n fetchActivitiesHistory,\n onCreateActivity,\n onRowClick,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n}: HistoryTabProps) => {\n // Use ref to keep stable reference to fetch function\n const fetchActivitiesHistoryRef = useRef(fetchActivitiesHistory);\n fetchActivitiesHistoryRef.current = fetchActivitiesHistory;\n\n // Create an API client adapter that wraps the fetch function\n const apiClientAdapter = useMemo(\n () => ({\n get: async <T,>(\n _url: string,\n options?: { params?: Record<string, unknown> }\n ) => {\n const result = await fetchActivitiesHistoryRef.current(options?.params);\n return { data: result as T };\n },\n // Methods not used by this component but required by BaseApiClient\n post: async <T,>(): Promise<{ data: T }> => {\n throw new Error('post not implemented in HistoryTab adapter');\n },\n patch: async <T,>(): Promise<{ data: T }> => {\n throw new Error('patch not implemented in HistoryTab adapter');\n },\n delete: async <T,>(): Promise<{ data: T }> => {\n throw new Error('delete not implemented in HistoryTab adapter');\n },\n }),\n []\n );\n\n // Create hook instance with the API client adapter\n const useActivitiesHistory = useMemo(\n () => createUseActivitiesHistory(apiClientAdapter),\n [apiClientAdapter]\n );\n\n // Use the hook\n const {\n activities,\n loading,\n error,\n pagination,\n fetchActivities,\n }: UseActivitiesHistoryReturn = useActivitiesHistory();\n\n // Create filter and column configurations\n const historyFilterConfigs = useMemo(\n () => createHistoryFiltersConfig(userFilterData),\n [userFilterData]\n );\n\n const historyTableColumns = useMemo(\n () => createHistoryTableColumns(mapSubjectNameToEnum),\n [mapSubjectNameToEnum]\n );\n\n /**\n * Handle table params change\n */\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n const filters = buildHistoryFiltersFromParams(params);\n fetchActivities(filters);\n },\n [fetchActivities]\n );\n\n // Error State\n if (error) {\n return <ErrorDisplay error={error} />;\n }\n\n return (\n <div className=\"w-full\">\n <TableProvider\n data={activities}\n headers={historyTableColumns}\n loading={loading}\n variant=\"borderless\"\n enableSearch\n enableFilters\n enableTableSort\n enablePagination\n enableRowClick\n initialFilters={historyFilterConfigs}\n paginationConfig={{\n itemLabel: 'atividades',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: 10,\n totalItems: pagination.total,\n totalPages: pagination.totalPages,\n }}\n searchPlaceholder=\"Buscar atividade\"\n noSearchResultState={{\n image: noSearchImage,\n }}\n emptyState={{\n component: (\n <EmptyState\n image={emptyStateImage}\n title=\"Incentive sua turma ao aprendizado\"\n description=\"Crie uma nova atividade e ajude seus alunos a colocarem o conteúdo em prática!\"\n buttonText=\"Criar atividade\"\n buttonIcon={<PlusIcon size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={onCreateActivity}\n />\n ),\n }}\n onParamsChange={handleParamsChange}\n onRowClick={onRowClick}\n >\n {(renderProps: unknown) => {\n const {\n controls,\n table,\n pagination: paginationComponent,\n } = renderProps as {\n controls: ReactNode;\n table: ReactNode;\n pagination: ReactNode;\n };\n return (\n <div className=\"space-y-4\">\n {/* Header row: Button on left, Controls on right */}\n <div className=\"flex items-center justify-between gap-4\">\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={onCreateActivity}\n iconLeft={<PlusIcon size={18} weight=\"bold\" />}\n >\n Criar atividade\n </Button>\n {controls}\n </div>\n {/* Table and pagination */}\n <div className=\"bg-background rounded-xl p-6 space-y-4\">\n {table}\n {paginationComponent}\n </div>\n </div>\n );\n }}\n </TableProvider>\n </div>\n );\n};\n\nexport default HistoryTab;\n","import { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport Text from '../../Text/Text';\nimport Badge from '../../Badge/Badge';\nimport ProgressBar from '../../ProgressBar/ProgressBar';\nimport { renderTruncatedText } from '../utils';\nimport { renderSubjectCell } from '../../../utils/renderSubjectCell';\nimport { getActivityStatusBadgeAction } from '../../../types/activitiesHistory';\nimport { GenericDisplayStatus } from '../../../types/common';\nimport type { ColumnConfig } from '../../TableProvider/TableProvider';\nimport type { ActivityTableItem } from '../../../types/activitiesHistory';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Create table columns configuration for activities history\n * @param mapSubjectNameToEnum - Optional function to map subject names to enum values\n * @returns Array of column configurations for the history table\n */\nexport const createHistoryTableColumns = (\n mapSubjectNameToEnum: ((name: string) => SubjectEnum | null) | undefined\n): ColumnConfig<ActivityTableItem>[] => [\n {\n key: 'startDate',\n label: 'Início',\n sortable: true,\n },\n {\n key: 'deadline',\n label: 'Prazo',\n sortable: true,\n },\n {\n key: 'creator',\n label: 'Autor',\n sortable: false,\n className: 'max-w-[150px] truncate',\n render: renderTruncatedText,\n },\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[200px] truncate',\n render: renderTruncatedText,\n },\n {\n key: 'school',\n label: 'Escola',\n sortable: true,\n className: 'max-w-[150px] truncate',\n render: renderTruncatedText,\n },\n {\n key: 'year',\n label: 'Ano',\n sortable: true,\n },\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[140px]',\n render: (value: unknown) => {\n const subjectName = typeof value === 'string' ? value : '';\n return renderSubjectCell(subjectName, mapSubjectNameToEnum, false);\n },\n },\n {\n key: 'class',\n label: 'Turma',\n sortable: true,\n },\n {\n key: 'status',\n label: 'Status',\n sortable: true,\n render: (value: unknown) => {\n const status = typeof value === 'string' ? value : '';\n if (!status) {\n return (\n <Text size=\"sm\" color=\"text-text-500\">\n -\n </Text>\n );\n }\n return (\n <Badge\n variant=\"solid\"\n action={getActivityStatusBadgeAction(status as GenericDisplayStatus)}\n size=\"small\"\n >\n {status}\n </Badge>\n );\n },\n },\n {\n key: 'completionPercentage',\n label: 'Conclusão',\n sortable: true,\n render: (value: unknown) => (\n <ProgressBar\n value={Number(value)}\n variant=\"blue\"\n size=\"medium\"\n layout=\"compact\"\n showPercentage={true}\n compactWidth=\"w-[100px]\"\n />\n ),\n },\n {\n key: 'navigation',\n label: '',\n sortable: false,\n className: 'w-12',\n render: () => (\n <div className=\"flex justify-center\">\n <CaretRightIcon size={20} className=\"text-text-600\" />\n </div>\n ),\n },\n];\n","import type { ReactNode } from 'react';\nimport Text from '../../Text/Text';\n\n/**\n * Render a truncated text cell for table columns\n * @param value - The value to render (will be converted to string)\n * @returns React node for the truncated text\n */\nexport const renderTruncatedText = (value: unknown): ReactNode => {\n const text = typeof value === 'string' ? value : '';\n return (\n <Text size=\"sm\" title={text}>\n {text}\n </Text>\n );\n};\n","import { GenericApiStatus } from '../../../types/common';\nimport type {\n ActivityHistoryFilters,\n ActivityModelFilters,\n} from '../../../types/activitiesHistory';\nimport type { TableParams } from '../../TableProvider/TableProvider';\n\n/**\n * Check if a value is a valid GenericApiStatus enum value\n * @param value - Value to validate\n * @returns True if value is a valid GenericApiStatus\n */\nexport const isValidApiStatus = (value: string): value is GenericApiStatus =>\n Object.values(GenericApiStatus).includes(value as GenericApiStatus);\n\n/**\n * Build activity history filters from table params\n * @param params - Table parameters from TableProvider\n * @returns Activity history filters for API call\n */\nexport const buildHistoryFiltersFromParams = (\n params: TableParams\n): ActivityHistoryFilters => {\n const filters: ActivityHistoryFilters = {\n page: params.page,\n limit: params.limit,\n };\n\n if (params.search) {\n filters.search = params.search;\n }\n\n // Status filter (single selection) - with runtime validation\n if (\n Array.isArray(params.status) &&\n params.status.length > 0 &&\n isValidApiStatus(params.status[0])\n ) {\n filters.status = params.status[0];\n }\n\n // School filter\n if (Array.isArray(params.school) && params.school.length > 0) {\n filters.schoolId = params.school[0];\n }\n\n // Subject filter\n if (Array.isArray(params.subject) && params.subject.length > 0) {\n filters.subjectId = params.subject[0];\n }\n\n return filters;\n};\n\n/**\n * Build activity models filters from table params\n * @param params - Table parameters from TableProvider\n * @returns Activity model filters for API call\n */\nexport const buildModelsFiltersFromParams = (\n params: TableParams\n): ActivityModelFilters => {\n const filters: ActivityModelFilters = {\n page: params.page,\n limit: params.limit,\n };\n\n if (params.search) {\n filters.search = params.search;\n }\n\n // Subject filter\n if (Array.isArray(params.subject) && params.subject.length > 0) {\n filters.subjectId = params.subject[0];\n }\n\n return filters;\n};\n","import type {\n ActivityFilterOption,\n ActivityUserFilterData,\n} from '../../../types/activitiesHistory';\n\n/**\n * Get school options from user data for filter dropdowns\n * @param data - User filter data containing schools\n * @returns Array of filter options for schools\n */\nexport const getSchoolOptions = (\n data: ActivityUserFilterData | undefined\n): ActivityFilterOption[] => {\n if (!data?.schools) return [];\n return data.schools.map((school) => ({\n id: school.id,\n name: school.name,\n }));\n};\n\n/**\n * Get subject options from user data for filter dropdowns\n * @param data - User filter data containing subjects\n * @returns Array of filter options for subjects\n */\nexport const getSubjectOptions = (\n data: ActivityUserFilterData | undefined\n): ActivityFilterOption[] => {\n if (!data?.subjects) return [];\n return data.subjects.map((subject) => ({\n id: subject.id,\n name: subject.name,\n }));\n};\n","import { getSchoolOptions, getSubjectOptions } from '../utils';\nimport { ACTIVITY_FILTER_STATUS_OPTIONS } from '../../../types/activitiesHistory';\nimport type { FilterConfig } from '../../Filter';\nimport type { ActivityUserFilterData } from '../../../types/activitiesHistory';\n\n/**\n * Create filter configuration for activities history tab\n * @param userData - User data containing schools and subjects for filter options\n * @returns Array of filter configurations for the history table\n */\nexport const createHistoryFiltersConfig = (\n userData: ActivityUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'status',\n label: 'STATUS',\n categories: [\n {\n key: 'status',\n label: 'Status da Atividade',\n selectedIds: [],\n itens: ACTIVITY_FILTER_STATUS_OPTIONS,\n },\n ],\n },\n {\n key: 'academic',\n label: 'DADOS ACADÊMICOS',\n categories: [\n {\n key: 'school',\n label: 'Escola',\n selectedIds: [],\n itens: getSchoolOptions(userData),\n },\n ],\n },\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n ],\n },\n];\n","import { getSubjectOptions } from '../utils';\nimport type { FilterConfig } from '../../Filter';\nimport type { ActivityUserFilterData } from '../../../types/activitiesHistory';\n\n/**\n * Create filter configuration for activity models tab\n * @param userData - User data containing subjects for filter options\n * @returns Array of filter configurations for the models table\n */\nexport const createModelsFiltersConfig = (\n userData: ActivityUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n ],\n },\n];\n","import {\n ModelsTabBase,\n createModelsTableColumnsBase,\n type ModelsTabConfig,\n type ModelsColumnsConfig,\n} from '../../shared/ModelsTabBase';\nimport { createModelsFiltersConfig } from '../config/modelsFiltersConfig';\nimport { buildModelsFiltersFromParams } from '../utils/filterBuilders';\nimport { createUseActivityModels } from '../../../hooks/useActivityModels';\nimport type {\n ActivityModelTableItem,\n ActivityModelFilters,\n ActivityModelsApiResponse,\n ActivityUserFilterData,\n} from '../../../types/activitiesHistory';\nimport type { SubjectEnum } from '../../../enums/SubjectEnum';\n\n/**\n * Configuration for activity models tab\n */\nconst ACTIVITY_MODELS_CONFIG: ModelsTabConfig = {\n entityName: 'atividade',\n entityNamePlural: 'atividades',\n testId: 'activity-models-tab',\n emptyStateTitle: 'Crie modelos para agilizar suas atividades',\n emptyStateDescription:\n 'Salve modelos de atividades para reutilizar e enviar rapidamente para suas turmas!',\n searchPlaceholder: 'Buscar modelo',\n};\n\n/**\n * Configuration for activity models table columns\n */\nconst ACTIVITY_COLUMNS_CONFIG: ModelsColumnsConfig = {\n sendButtonLabel: 'Enviar atividade',\n sendButtonAriaLabel: 'Enviar atividade',\n deleteButtonAriaLabel: 'Deletar modelo',\n editButtonAriaLabel: 'Editar modelo',\n};\n\n/**\n * Props for the ModelsTab component\n */\nexport interface ModelsTabProps {\n /** Function to fetch activity models from API */\n fetchActivityModels: (\n filters?: ActivityModelFilters\n ) => Promise<ActivityModelsApiResponse>;\n /** Function to delete an activity model */\n deleteActivityModel: (id: string) => Promise<void>;\n /** Callback when create model button is clicked */\n onCreateModel: () => void;\n /** Callback when send activity button is clicked on a model */\n onSendActivity?: (model: ActivityModelTableItem) => void;\n /** Callback when edit model button is clicked */\n onEditModel?: (model: ActivityModelTableItem) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: ActivityUserFilterData;\n /**\n * Map of subject IDs to names for models display.\n * IMPORTANT: This Map should be memoized with useMemo in the parent component\n * to avoid unnecessary re-fetches.\n */\n subjectsMap?: Map<string, string>;\n}\n\n/**\n * ModelsTab component\n * Displays activity models with filters, pagination, and CRUD actions.\n * Uses the shared ModelsTabBase component for common functionality.\n */\nexport const ModelsTab = ({\n fetchActivityModels,\n deleteActivityModel,\n onCreateModel,\n onSendActivity,\n onEditModel,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n subjectsMap,\n}: ModelsTabProps) => (\n <ModelsTabBase<\n ActivityModelTableItem,\n ActivityModelFilters,\n ActivityModelsApiResponse,\n ActivityUserFilterData\n >\n fetchModels={fetchActivityModels}\n deleteModel={deleteActivityModel}\n onCreateModel={onCreateModel}\n onSend={onSendActivity}\n onEditModel={onEditModel}\n emptyStateImage={emptyStateImage}\n noSearchImage={noSearchImage}\n mapSubjectNameToEnum={mapSubjectNameToEnum}\n userFilterData={userFilterData}\n subjectsMap={subjectsMap}\n config={ACTIVITY_MODELS_CONFIG}\n createTableColumns={(mapSubject, send, edit, del) =>\n createModelsTableColumnsBase(\n mapSubject,\n send,\n edit,\n del,\n ACTIVITY_COLUMNS_CONFIG\n )\n }\n createFiltersConfig={createModelsFiltersConfig}\n buildFiltersFromParams={buildModelsFiltersFromParams}\n createUseModels={createUseActivityModels}\n />\n);\n\nexport default ModelsTab;\n","import Text from '../../Text/Text';\n\n/**\n * DraftsTab component\n * Placeholder component for drafts functionality (to be implemented)\n */\nexport const DraftsTab = () => {\n return (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-text-600\">\n Rascunhos em desenvolvimento\n </Text>\n </div>\n );\n};\n\nexport default DraftsTab;\n","import { useCallback, useMemo } from 'react';\nimport { useMobile } from '../../hooks/useMobile';\nimport Text from '../Text/Text';\nimport { Menu, MenuContent, MenuItem } from '../Menu/Menu';\nimport Button from '../Button/Button';\nimport TableProvider from '../TableProvider/TableProvider';\nimport type { TableParams, ColumnConfig } from '../TableProvider/TableProvider';\nimport type { FilterConfig } from '../Filter/useTableFilter';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport type { ReactNode } from 'react';\n\n/**\n * Tab configuration for BasePageLayout\n */\nexport interface TabConfig {\n value: string;\n label: string;\n testId: string;\n}\n\n/**\n * Props for the BasePageLayout component\n */\nexport interface BasePageLayoutProps<T extends Record<string, unknown>> {\n /** Current active tab value */\n activeTab: string;\n /** Page title displayed in the header */\n pageTitle: string;\n /** Optional content rendered to the right of the page title (e.g., type selector) */\n headerRightContent?: ReactNode;\n /** Test ID for the page container */\n testId: string;\n /** Data to display in the table */\n data: T[];\n /** Table column headers configuration */\n headers: ColumnConfig<T>[];\n /** Whether data is loading */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Pagination configuration */\n pagination: {\n total: number;\n totalPages: number;\n };\n /** Initial filter configuration */\n initialFilters?: FilterConfig[];\n /** Label for pagination items (e.g., \"provas\", \"atividades\") */\n itemLabel: string;\n /** Search placeholder text */\n searchPlaceholder: string;\n /** Empty state component rendered inside the table */\n emptyState: ReactNode;\n /** Image shown when search returns no results */\n noSearchImage: string;\n /** Tab configuration */\n tabs: TabConfig[];\n /** Label for the create button */\n createButtonLabel: string;\n /** Callback when table params change (search, sort, pagination) */\n onParamsChange: (params: TableParams) => void;\n /** Callback when a row is clicked */\n onRowClick: (row: T) => void;\n /** Callback when a tab is changed */\n onTabChange: (tab: string) => void;\n /** Callback when the create button is clicked */\n onCreate: () => void;\n}\n\n/**\n * Shared base layout component for list pages (History, Drafts, Models).\n * Provides consistent structure with tabs navigation, search, filters, and table display.\n * Used by ExamPageLayout and ActivityPageLayout.\n * @returns JSX element representing the page layout\n */\nexport function BasePageLayout<T extends Record<string, unknown>>({\n activeTab,\n pageTitle,\n headerRightContent,\n testId,\n data,\n headers,\n loading,\n error,\n pagination,\n initialFilters,\n itemLabel,\n searchPlaceholder,\n emptyState,\n noSearchImage,\n tabs,\n createButtonLabel,\n onParamsChange,\n onRowClick,\n onTabChange,\n onCreate,\n}: Readonly<BasePageLayoutProps<T>>) {\n const { isMobile } = useMobile();\n\n /**\n * Compute a stable key for TableProvider based on filter options availability.\n * Forces remount when userData loads and filter options become available,\n * ensuring TableProvider initializes with correct filter data.\n */\n const tableKey = useMemo(() => {\n const totalOptions = Array.isArray(initialFilters)\n ? initialFilters\n .flatMap((group) => group.categories)\n .reduce(\n (sum, cat: FilterConfig['categories'][number]) =>\n sum + (cat.itens?.length ?? 0),\n 0\n )\n : 0;\n return `filters-${totalOptions}`;\n }, [initialFilters]);\n\n /**\n * Whether filters should be enabled — only when at least one filter has options.\n */\n const enableFilters = useMemo(\n () =>\n Array.isArray(initialFilters) &&\n initialFilters\n .flatMap((g) => g.categories)\n .some((c) => (c.itens?.length ?? 0) > 0),\n [initialFilters]\n );\n\n /**\n * Handles tab change by forwarding to the onTabChange callback.\n * @param value - Tab value selected by the user\n */\n const handleTabChange = useCallback(\n (value: string) => {\n onTabChange(value);\n },\n [onTabChange]\n );\n\n return (\n <div\n data-testid={testId}\n className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\"\n >\n {/* Background decoration */}\n <span className=\"absolute top-0 left-0 h-[150px] w-full z-0\" />\n\n {/* Main container */}\n <div className=\"flex flex-col w-full h-full max-w-[1350px] mx-auto z-10 lg:px-0 px-4 pt-4 sm:pt-0\">\n {/* Header Section */}\n <div className=\"flex flex-col sm:flex-row w-full mb-6 items-start sm:items-center sm:justify-between gap-0 sm:gap-4\">\n {/* Page Title + Optional Right Content */}\n <div\n className={`flex gap-3 ${\n isMobile ? 'flex-col items-start' : 'items-center'\n }`}\n >\n <Text\n as=\"h1\"\n className=\"font-bold leading-[28px] tracking-[0.2px] text-text-950 text-xl lg:text-2xl whitespace-nowrap\"\n >\n {pageTitle}\n </Text>\n {headerRightContent}\n </div>\n\n {/* Tabs Menu */}\n <div className=\"shrink-0 lg:w-auto self-center sm:self-auto\">\n <Menu\n defaultValue={activeTab}\n value={activeTab}\n onValueChange={handleTabChange}\n variant=\"menu2\"\n className=\"bg-transparent shadow-none px-0\"\n >\n <MenuContent\n variant=\"menu2\"\n className=\"w-full lg:w-auto max-w-full min-w-0\"\n >\n {tabs.map((tab) => (\n <MenuItem\n key={tab.value}\n variant=\"menu2\"\n value={tab.value}\n data-testid={tab.testId}\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n {tab.label}\n </MenuItem>\n ))}\n </MenuContent>\n </Menu>\n </div>\n </div>\n\n {/* Content Area */}\n <div className=\"flex flex-col items-center w-full min-h-0 flex-1\">\n {/* Error State */}\n {error ? (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-error-500\">\n {error}\n </Text>\n </div>\n ) : (\n <div className=\"w-full\">\n <TableProvider\n key={tableKey}\n data={data}\n headers={headers}\n loading={loading}\n variant=\"borderless\"\n enableSearch\n enableFilters={enableFilters}\n enableTableSort\n enablePagination\n enableRowClick\n initialFilters={initialFilters}\n paginationConfig={{\n itemLabel,\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: 10,\n totalItems: pagination.total,\n totalPages: pagination.totalPages,\n }}\n searchPlaceholder={searchPlaceholder}\n noSearchResultState={{\n image: noSearchImage,\n }}\n emptyState={{\n component: emptyState,\n }}\n onParamsChange={onParamsChange}\n onRowClick={onRowClick}\n headerContent={\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={onCreate}\n iconLeft={<PlusIcon size={18} weight=\"bold\" />}\n className=\"w-full lg:w-auto\"\n >\n {createButtonLabel}\n </Button>\n }\n containerClassName=\"bg-background rounded-xl p-6 space-y-4\"\n />\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { BasePageLayout } from '../BasePageLayout/BasePageLayout';\nimport type { TableParams, ColumnConfig } from '../TableProvider/TableProvider';\nimport type { FilterConfig } from '../Filter/useTableFilter';\n\n/**\n * Enum for activity page tabs\n */\nexport enum ActivityTab {\n HISTORY = 'historico',\n DRAFTS = 'rascunhos',\n MODELS = 'modelos',\n}\n\n/**\n * Tab configuration for activity pages\n */\nconst ACTIVITY_TABS = [\n {\n value: ActivityTab.HISTORY,\n label: 'Histórico',\n testId: 'menu-item-history',\n },\n { value: ActivityTab.DRAFTS, label: 'Rascunhos', testId: 'menu-item-drafts' },\n { value: ActivityTab.MODELS, label: 'Modelos', testId: 'menu-item-models' },\n];\n\n/**\n * Props for the ActivityPageLayout component\n */\nexport interface ActivityPageLayoutProps<T extends Record<string, unknown>> {\n /** Current active tab */\n activeTab: ActivityTab;\n /** Page title displayed in the header */\n pageTitle: string;\n /** Optional content rendered to the right of the page title (e.g., type selector) */\n headerRightContent?: ReactNode;\n /** Test ID for the page container */\n testId: string;\n /** Data to display in the table */\n data: T[];\n /** Table column headers configuration */\n headers: ColumnConfig<T>[];\n /** Whether data is loading */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Pagination configuration */\n pagination: {\n total: number;\n totalPages: number;\n };\n /** Initial filter configuration */\n initialFilters?: FilterConfig[];\n /** Label for pagination items (e.g., \"atividades\", \"rascunhos\") */\n itemLabel: string;\n /** Search placeholder text */\n searchPlaceholder: string;\n /** Empty state component rendered inside the table */\n emptyState: ReactNode;\n /** Image shown when search returns no results */\n noSearchImage: string;\n /** Callback when table params change (search, sort, pagination) */\n onParamsChange: (params: TableParams) => void;\n /** Callback when a row is clicked */\n onRowClick: (row: T) => void;\n /** Callback when a tab is changed */\n onTabChange: (tab: ActivityTab) => void;\n /** Callback when the create activity button is clicked */\n onCreateActivity: () => void;\n /** Label for the create button (default: \"Criar atividade\") */\n createButtonLabel?: string;\n}\n\n/**\n * Layout component for activity pages (History, Drafts, Models).\n * Wraps BasePageLayout with activity-specific configuration.\n * @returns JSX element representing the activity page layout\n */\nexport function ActivityPageLayout<T extends Record<string, unknown>>({\n activeTab,\n pageTitle,\n headerRightContent,\n testId,\n data,\n headers,\n loading,\n error,\n pagination,\n initialFilters,\n itemLabel,\n searchPlaceholder,\n emptyState,\n noSearchImage,\n onParamsChange,\n onRowClick,\n onTabChange,\n onCreateActivity,\n createButtonLabel = 'Criar atividade',\n}: Readonly<ActivityPageLayoutProps<T>>) {\n return (\n <BasePageLayout\n activeTab={activeTab}\n pageTitle={pageTitle}\n headerRightContent={headerRightContent}\n testId={testId}\n data={data}\n headers={headers}\n loading={loading}\n error={error}\n pagination={pagination}\n initialFilters={initialFilters}\n itemLabel={itemLabel}\n searchPlaceholder={searchPlaceholder}\n emptyState={emptyState}\n noSearchImage={noSearchImage}\n tabs={ACTIVITY_TABS}\n createButtonLabel={createButtonLabel}\n onParamsChange={onParamsChange}\n onRowClick={onRowClick}\n onTabChange={(tab) => onTabChange(tab as ActivityTab)}\n onCreate={onCreateActivity}\n />\n );\n}\n","import type { MouseEvent } from 'react';\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { PencilSimpleIcon } from '@phosphor-icons/react/dist/csr/PencilSimple';\nimport { PAGE_CONFIG } from './config';\nimport type { UnifiedHistoryPageProps } from './types';\nimport EmptyState from '../EmptyState/EmptyState';\nimport IconButton from '../IconButton/IconButton';\nimport { AlertDialog } from '../AlertDialog/AlertDialog';\nimport { EditActivityModal } from './EditActivityModal';\nimport useToastStore from '../Toast/utils/ToastStore';\nimport TypeSelector from '../TypeSelector/TypeSelector';\nimport { createActivityCategoryConfig } from '../TypeSelector/TypeSelector.types';\nimport type { FilterConfig } from '../Filter';\nimport type { TableParams, ColumnConfig } from '../TableProvider/TableProvider';\nimport type {\n ActivityTableItem,\n ActivityFilterOption,\n} from '../../types/activitiesHistory';\nimport type { ExamTableItem } from '../../types/examsHistory';\nimport {\n getSchoolOptionsFromUserData,\n getSchoolYearOptionsFromUserData,\n getClassOptionsFromUserData,\n getSubjectOptionsFromUserData,\n mergeFilterOptions,\n} from '../../utils/filterHelpers';\n\n/**\n * Creator type filter options (for managers only)\n */\nconst CREATOR_TYPE_OPTIONS: ActivityFilterOption[] = [\n { id: 'own', name: 'Minhas' },\n { id: 'teachers', name: 'Dos Professores' },\n];\n\n/**\n * Unified page component for Activities and Exams History\n * Encapsulates all common logic between activities and exams history pages\n */\nexport const UnifiedHistoryPage = ({\n activityCategory,\n data,\n loading,\n error,\n pagination,\n apiFilterOptions,\n onParamsChange,\n userData = null,\n activityImage,\n noSearchImage,\n includeCreatorFilter = false,\n routes,\n currentUserId,\n apiClient,\n}: UnifiedHistoryPageProps) => {\n const navigate = useNavigate();\n const config = PAGE_CONFIG[activityCategory];\n const PageLayout = config.PageLayout;\n const addToast = useToastStore((state) => state.addToast);\n\n /**\n * Whether the owner-only delete action is enabled. Requires an api client and\n * the logged user id, and only applies to activities (not exams).\n */\n const deleteEnabled =\n activityCategory === 'ATIVIDADE' && !!apiClient && !!currentUserId;\n\n /** Activity currently pending deletion confirmation (drives the AlertDialog) */\n const [activityToDelete, setActivityToDelete] = useState<{\n id: string;\n title: string;\n } | null>(null);\n\n /** Activity currently being edited (drives the EditActivityModal) */\n const [activityToEdit, setActivityToEdit] = useState<{ id: string } | null>(\n null\n );\n\n /** Last table params received, used to reload the list after a mutation */\n const lastParamsRef = useRef<TableParams | null>(null);\n\n // Extract user filter options\n const userFilterOptions = useMemo(\n () => ({\n schools: getSchoolOptionsFromUserData(userData),\n schoolYears: getSchoolYearOptionsFromUserData(userData),\n classes: getClassOptionsFromUserData(userData),\n subjects: getSubjectOptionsFromUserData(userData),\n }),\n [userData]\n );\n\n /**\n * Build filter configuration merging userData and API-sourced options\n */\n const initialFilterConfigs = useMemo(\n (): FilterConfig[] => [\n // Add creator type filter only for managers\n ...(includeCreatorFilter\n ? [\n {\n key: 'creator',\n label: 'CRIADO POR',\n categories: [\n {\n key: 'creatorType',\n label: 'Criador',\n selectedIds: [],\n itens: CREATOR_TYPE_OPTIONS,\n },\n ],\n },\n ]\n : []),\n // Status filter\n {\n key: 'status',\n label: 'STATUS',\n categories: [\n {\n key: 'status',\n label: config.statusLabel,\n selectedIds: [],\n itens: config.statusOptions,\n },\n ],\n },\n // Academic data filters\n {\n key: 'academic',\n label: 'DADOS ACADÊMICOS',\n categories: [\n {\n key: 'school',\n label: 'Escola',\n selectedIds: [],\n itens: mergeFilterOptions(\n userFilterOptions.schools,\n apiFilterOptions.schools\n ),\n },\n {\n key: 'schoolYear',\n label: 'Ano',\n selectedIds: [],\n itens: mergeFilterOptions(\n userFilterOptions.schoolYears,\n apiFilterOptions.schoolYears\n ),\n },\n {\n key: 'class',\n label: 'Turma',\n selectedIds: [],\n itens: mergeFilterOptions(\n userFilterOptions.classes,\n apiFilterOptions.classes\n ),\n },\n ],\n },\n // Content filters\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: mergeFilterOptions(\n userFilterOptions.subjects,\n apiFilterOptions.subjects\n ),\n },\n ],\n },\n ],\n [\n apiFilterOptions,\n config.statusOptions,\n config.statusLabel,\n includeCreatorFilter,\n userFilterOptions,\n ]\n );\n\n /**\n * Handle table params change\n */\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n lastParamsRef.current = params;\n onParamsChange(params);\n },\n [onParamsChange]\n );\n\n /**\n * Confirm deletion of the selected activity: calls DELETE /activities/:id,\n * reloads the list with the current params and shows a feedback toast.\n */\n const handleConfirmDelete = useCallback(async () => {\n if (!activityToDelete || !apiClient) {\n return;\n }\n\n try {\n await apiClient.delete(`/activities/${activityToDelete.id}`);\n setActivityToDelete(null);\n if (lastParamsRef.current) {\n onParamsChange(lastParamsRef.current);\n }\n addToast({\n title: 'Atividade excluída com sucesso',\n action: 'success',\n });\n } catch {\n setActivityToDelete(null);\n addToast({ title: 'Erro ao excluir atividade', action: 'warning' });\n }\n }, [activityToDelete, apiClient, onParamsChange, addToast]);\n\n /**\n * Table headers, optionally augmented with an owner-only delete action column\n * inserted right before the navigation (caret) column.\n */\n const headers = useMemo(() => {\n if (!deleteEnabled) {\n return config.tableColumns;\n }\n\n const actionsColumn: ColumnConfig<ActivityTableItem> = {\n key: 'actions',\n label: '',\n sortable: false,\n className: 'w-24',\n render: (_value, row) => {\n if (!row.creatorId || row.creatorId !== currentUserId) {\n return null;\n }\n return (\n <div className=\"flex items-center justify-end gap-2\">\n <IconButton\n icon={<TrashIcon size={20} />}\n size=\"sm\"\n title=\"Excluir\"\n className=\"hover:text-error-500\"\n aria-label=\"Excluir\"\n onClick={(e: MouseEvent) => {\n e.stopPropagation();\n setActivityToDelete({ id: row.id, title: row.title });\n }}\n />\n <IconButton\n icon={<PencilSimpleIcon size={20} />}\n size=\"sm\"\n title=\"Editar\"\n className=\"hover:text-primary-700\"\n aria-label=\"Editar\"\n onClick={(e: MouseEvent) => {\n e.stopPropagation();\n setActivityToEdit({ id: row.id });\n }}\n />\n </div>\n );\n },\n };\n\n const baseColumns = [\n ...(config.tableColumns as ColumnConfig<ActivityTableItem>[]),\n ];\n const navIndex = baseColumns.findIndex((c) => c.key === 'navigation');\n if (navIndex === -1) {\n baseColumns.push(actionsColumn);\n } else {\n baseColumns.splice(navIndex, 0, actionsColumn);\n }\n return baseColumns;\n }, [\n config.tableColumns,\n deleteEnabled,\n currentUserId,\n navigate,\n routes,\n activityCategory,\n ]);\n\n /**\n * TypeSelector config with proper labels, routes, and status options\n */\n const typeSelectorConfig = useMemo(\n () => createActivityCategoryConfig(routes),\n [routes]\n );\n\n /**\n * Handle tab change\n */\n const handleTabChange = useCallback(\n (tab: string) => {\n const currentRoutes = routes[activityCategory];\n switch (tab) {\n case config.tabs.DRAFTS:\n navigate(`${currentRoutes.base}/rascunhos`);\n break;\n case config.tabs.MODELS:\n navigate(`${currentRoutes.base}/modelos`);\n break;\n default:\n navigate(currentRoutes.base);\n }\n },\n [navigate, config, routes, activityCategory]\n );\n\n /**\n * Handle create button click\n */\n const handleCreate = useCallback(() => {\n navigate(routes[activityCategory].create);\n }, [navigate, routes, activityCategory]);\n\n /**\n * Handle row click\n */\n const handleRowClick = useCallback(\n (row: ActivityTableItem | ExamTableItem) => {\n navigate(routes[activityCategory].details(row.id));\n },\n [navigate, routes, activityCategory]\n );\n\n // Build layout props dynamically\n const layoutProps = {\n activeTab: config.activeTab,\n pageTitle: config.pageTitle,\n headerRightContent: (\n <TypeSelector\n value={activityCategory}\n currentTab=\"history\"\n config={typeSelectorConfig}\n />\n ),\n testId: config.testId,\n data: data || [],\n headers,\n loading,\n error,\n pagination,\n initialFilters: initialFilterConfigs,\n itemLabel: config.itemLabel,\n searchPlaceholder: config.searchPlaceholder,\n emptyState: activityImage ? (\n <EmptyState\n image={activityImage}\n title={config.emptyTitle}\n description={config.emptyDescription}\n buttonText={config.buttonText}\n buttonIcon={<PlusIcon size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={handleCreate}\n />\n ) : undefined,\n noSearchImage,\n onParamsChange: handleParamsChange,\n onRowClick: handleRowClick,\n onTabChange: handleTabChange,\n [config.onCreatePropName]: handleCreate,\n };\n\n return (\n <>\n {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}\n <PageLayout {...(layoutProps as any)} />\n {deleteEnabled && (\n <AlertDialog\n isOpen={!!activityToDelete}\n onChangeOpen={(open) => {\n if (!open) {\n setActivityToDelete(null);\n }\n }}\n title=\"Excluir atividade\"\n description={`Tem certeza que deseja excluir a atividade \"${\n activityToDelete?.title ?? ''\n }\"? Esta ação não pode ser desfeita.`}\n submitButtonLabel=\"Excluir\"\n cancelButtonLabel=\"Cancelar\"\n submitAction=\"negative\"\n onSubmit={handleConfirmDelete}\n onCancel={() => setActivityToDelete(null)}\n />\n )}\n {deleteEnabled && (\n <EditActivityModal\n isOpen={!!activityToEdit}\n activityId={activityToEdit?.id}\n apiClient={apiClient}\n onClose={() => setActivityToEdit(null)}\n onSaved={() => {\n setActivityToEdit(null);\n if (lastParamsRef.current) {\n onParamsChange(lastParamsRef.current);\n }\n }}\n />\n )}\n </>\n );\n};\n","import type { ReactNode } from 'react';\nimport { BasePageLayout } from '../BasePageLayout/BasePageLayout';\nimport type { TableParams, ColumnConfig } from '../TableProvider/TableProvider';\nimport type { FilterConfig } from '../Filter/useTableFilter';\n\n/**\n * Enum for exam page tabs\n */\nexport enum ExamTab {\n HISTORY = 'historico',\n DRAFTS = 'rascunhos',\n MODELS = 'modelos',\n}\n\n/**\n * Tab configuration for exam pages\n */\nconst EXAM_TABS = [\n { value: ExamTab.HISTORY, label: 'Histórico', testId: 'menu-item-history' },\n { value: ExamTab.DRAFTS, label: 'Rascunhos', testId: 'menu-item-drafts' },\n { value: ExamTab.MODELS, label: 'Modelos', testId: 'menu-item-models' },\n];\n\n/**\n * Props for the ExamPageLayout component\n */\nexport interface ExamPageLayoutProps<T extends Record<string, unknown>> {\n /** Current active tab */\n activeTab: ExamTab;\n /** Page title displayed in the header */\n pageTitle: string;\n /** Optional content rendered to the right of the page title (e.g., type selector) */\n headerRightContent?: ReactNode;\n /** Test ID for the page container */\n testId: string;\n /** Data to display in the table */\n data: T[];\n /** Table column headers configuration */\n headers: ColumnConfig<T>[];\n /** Whether data is loading */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Pagination configuration */\n pagination: {\n total: number;\n totalPages: number;\n };\n /** Initial filter configuration */\n initialFilters?: FilterConfig[];\n /** Label for pagination items (e.g., \"provas\", \"rascunhos\") */\n itemLabel: string;\n /** Search placeholder text */\n searchPlaceholder: string;\n /** Empty state component rendered inside the table */\n emptyState: ReactNode;\n /** Image shown when search returns no results */\n noSearchImage: string;\n /** Callback when table params change (search, sort, pagination) */\n onParamsChange: (params: TableParams) => void;\n /** Callback when a row is clicked */\n onRowClick: (row: T) => void;\n /** Callback when a tab is changed */\n onTabChange: (tab: ExamTab) => void;\n /** Callback when the create button is clicked */\n onCreateExam: () => void;\n /** Label for the create button (default: \"Criar prova\") */\n createButtonLabel?: string;\n}\n\n/**\n * Layout component for exam pages (History, Drafts, Models).\n * Wraps BasePageLayout with exam-specific configuration.\n * @returns JSX element representing the exam page layout\n */\nexport function ExamPageLayout<T extends Record<string, unknown>>({\n activeTab,\n pageTitle,\n headerRightContent,\n testId,\n data,\n headers,\n loading,\n error,\n pagination,\n initialFilters,\n itemLabel,\n searchPlaceholder,\n emptyState,\n noSearchImage,\n onParamsChange,\n onRowClick,\n onTabChange,\n onCreateExam,\n createButtonLabel = 'Criar prova',\n}: Readonly<ExamPageLayoutProps<T>>) {\n return (\n <BasePageLayout\n activeTab={activeTab}\n pageTitle={pageTitle}\n headerRightContent={headerRightContent}\n testId={testId}\n data={data}\n headers={headers}\n loading={loading}\n error={error}\n pagination={pagination}\n initialFilters={initialFilters}\n itemLabel={itemLabel}\n searchPlaceholder={searchPlaceholder}\n emptyState={emptyState}\n noSearchImage={noSearchImage}\n tabs={EXAM_TABS}\n createButtonLabel={createButtonLabel}\n onParamsChange={onParamsChange}\n onRowClick={onRowClick}\n onTabChange={(tab) => onTabChange(tab as ExamTab)}\n onCreate={onCreateExam}\n />\n );\n}\n","import Badge from '../Badge/Badge';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { renderTextCell } from '../../utils/renderTextCell';\nimport { renderSubjectCell } from '../../utils/renderSubjectCell';\nimport { mapSubjectNameToEnum } from '../../utils/subjectMappers';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport type { ActivityTableItem } from '../../types/activitiesHistory';\nimport {\n ActivityDisplayStatus,\n ActivityBadgeActionType,\n} from '../../types/activitiesHistory';\n\n/**\n * Get badge action based on activity status\n *\n * @param status - Activity display status\n * @returns Badge action type\n */\nexport const getActivityStatusBadgeAction = (\n status: ActivityDisplayStatus\n): ActivityBadgeActionType => {\n const actionMap: Record<ActivityDisplayStatus, ActivityBadgeActionType> = {\n [ActivityDisplayStatus.CONCLUIDA]: ActivityBadgeActionType.SUCCESS,\n [ActivityDisplayStatus.ATIVA]: ActivityBadgeActionType.WARNING,\n [ActivityDisplayStatus.VENCIDA]: ActivityBadgeActionType.ERROR,\n };\n return actionMap[status] || ActivityBadgeActionType.INFO;\n};\n\n/**\n * Column configuration for activities table\n *\n * Columns:\n * - startDate: Data de inicio\n * - deadline: Prazo\n * - title: Titulo\n * - school: Escola\n * - year: Ano\n * - subject: Materia\n * - class: Turma\n * - status: Status (Concluída, Ativa, Vencida)\n * - completionPercentage: Conclusao\n */\nexport const activitiesTableColumns: ColumnConfig<ActivityTableItem>[] = [\n {\n key: 'startDate',\n label: 'Início',\n sortable: true,\n },\n {\n key: 'deadline',\n label: 'Prazo',\n sortable: true,\n },\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[200px]',\n render: renderTextCell,\n },\n {\n key: 'school',\n label: 'Escola',\n sortable: true,\n className: 'max-w-[150px]',\n render: renderTextCell,\n },\n {\n key: 'year',\n label: 'Ano',\n sortable: true,\n },\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[140px]',\n render: (value: unknown) =>\n renderSubjectCell(\n typeof value === 'string' ? value : '-',\n mapSubjectNameToEnum\n ),\n },\n {\n key: 'class',\n label: 'Turma',\n sortable: true,\n className: 'max-w-[120px]',\n render: renderTextCell,\n },\n {\n key: 'status',\n label: 'Status',\n sortable: true,\n render: (value: unknown) => {\n const status = typeof value === 'string' ? value : '';\n // Validate status is a valid ActivityDisplayStatus enum value\n const validStatuses = Object.values(ActivityDisplayStatus);\n const validatedStatus = validStatuses.includes(\n value as ActivityDisplayStatus\n )\n ? (value as ActivityDisplayStatus)\n : ActivityDisplayStatus.ATIVA; // Default fallback\n\n return (\n <Badge\n variant=\"solid\"\n action={getActivityStatusBadgeAction(validatedStatus)}\n size=\"small\"\n >\n {status}\n </Badge>\n );\n },\n },\n {\n key: 'completionPercentage',\n label: 'Conclusão',\n sortable: true,\n render: (value: unknown) => {\n // Sanitize value: coerce to number, default to 0 if NaN, clamp to 0-100 range\n const numericValue = Number(value);\n const sanitizedValue = Number.isNaN(numericValue)\n ? 0\n : Math.max(0, Math.min(100, numericValue));\n\n return (\n <ProgressBar\n value={sanitizedValue}\n variant=\"blue\"\n size=\"medium\"\n layout=\"compact\"\n showPercentage={true}\n compactWidth=\"w-[100px]\"\n />\n );\n },\n },\n {\n key: 'navigation',\n label: '',\n sortable: false,\n className: 'w-12',\n render: () => (\n <div className=\"flex justify-center\">\n <CaretRightIcon size={20} className=\"text-text-600\" />\n </div>\n ),\n },\n];\n","import {\n ActivityPageLayout,\n ActivityTab,\n} from '../ActivityPageLayout/ActivityPageLayout';\nimport { ExamPageLayout, ExamTab } from '../ExamPageLayout/ExamPageLayout';\nimport { ACTIVITY_FILTER_STATUS_OPTIONS } from '../../types/activitiesHistory';\nimport { EXAM_STATUS_OPTIONS } from '../../utils/examFilterHelpers';\nimport { activitiesTableColumns } from '../ActivityPageLayout/activitiesTableConfig';\nimport { examsTableColumns } from '../ExamPageLayout/examsTableConfig';\nimport type { ActivityCategory } from '../TypeSelector/TypeSelector.types';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport type { ActivityTableItem } from '../../types/activitiesHistory';\nimport type { ExamTableItem } from '../../types/examsHistory';\n\n/**\n * Page configuration type for activities or exams\n */\nexport interface PageConfig {\n PageLayout: typeof ActivityPageLayout | typeof ExamPageLayout;\n activeTab: string;\n pageTitle: string;\n emptyTitle: string;\n emptyDescription: string;\n buttonText: string;\n itemLabel: string;\n searchPlaceholder: string;\n testId: string;\n statusOptions: Array<{ id: string; name: string }>;\n statusLabel: string;\n tableColumns:\n | ColumnConfig<ActivityTableItem>[]\n | ColumnConfig<ExamTableItem>[];\n tabs: {\n HISTORY: string;\n DRAFTS: string;\n MODELS: string;\n };\n onCreatePropName: 'onCreateActivity' | 'onCreateExam';\n}\n\n/**\n * Configuration for activities vs exams\n */\nexport const PAGE_CONFIG: Record<ActivityCategory, PageConfig> = {\n ATIVIDADE: {\n PageLayout: ActivityPageLayout,\n activeTab: ActivityTab.HISTORY,\n pageTitle: 'Histórico de atividades',\n emptyTitle: 'Nenhuma atividade encontrada',\n emptyDescription:\n 'Crie uma nova atividade e acompanhe o desempenho dos alunos!',\n buttonText: 'Criar atividade',\n itemLabel: 'atividades',\n searchPlaceholder: 'Buscar atividade',\n testId: 'activities-history-page',\n statusOptions: ACTIVITY_FILTER_STATUS_OPTIONS,\n statusLabel: 'Status da Atividade',\n tableColumns: activitiesTableColumns,\n tabs: {\n HISTORY: ActivityTab.HISTORY,\n DRAFTS: ActivityTab.DRAFTS,\n MODELS: ActivityTab.MODELS,\n },\n onCreatePropName: 'onCreateActivity' as const,\n },\n PROVA: {\n PageLayout: ExamPageLayout,\n activeTab: ExamTab.HISTORY,\n pageTitle: 'Histórico de provas',\n emptyTitle: 'Nenhuma prova encontrada',\n emptyDescription:\n 'Crie uma nova prova e acompanhe o desempenho dos alunos!',\n buttonText: 'Criar prova',\n itemLabel: 'provas',\n searchPlaceholder: 'Buscar prova',\n testId: 'exams-history-page',\n statusOptions: EXAM_STATUS_OPTIONS,\n statusLabel: 'Status da Prova',\n tableColumns: examsTableColumns,\n tabs: {\n HISTORY: ExamTab.HISTORY,\n DRAFTS: ExamTab.DRAFTS,\n MODELS: ExamTab.MODELS,\n },\n onCreatePropName: 'onCreateExam' as const,\n },\n} as const;\n","import type { ChangeEvent, FC } from 'react';\nimport { useCallback, useMemo, useState } from 'react';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { ArrowRightIcon } from '@phosphor-icons/react/dist/csr/ArrowRight';\nimport { FloppyDiskIcon } from '@phosphor-icons/react/dist/csr/FloppyDisk';\nimport Modal from '../Modal/Modal';\nimport Input from '../Input/Input';\nimport TextArea from '../TextArea/TextArea';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport Chips from '../Chips/Chips';\nimport Stepper from '../Stepper/Stepper';\nimport type { StepData } from '../Stepper/Stepper';\nimport DeadlineStep from '../shared/SendModalBase/components/DeadlineStep';\nimport { SendModalError } from '../shared/SendModalBase/components/SendModalError';\nimport { useEditModalForm } from '../shared/SendModalBase/hooks/useEditModalForm';\nimport { splitDateTime } from '../shared/SendModalBase/utils/splitDateTime';\nimport {\n ACTIVITY_TYPE_OPTIONS,\n ActivitySubtype,\n} from '../SendActivityModal/types';\nimport type {\n SendActivityFormData,\n StepErrors,\n} from '../SendActivityModal/types';\nimport {\n validateActivityStep,\n validateDeadlineStep,\n} from '../SendActivityModal/validation';\nimport useToastStore from '../Toast/utils/ToastStore';\nimport { buildISODateTime } from '../ActivityCreate/ActivityCreate.utils';\nimport type { BaseApiClient } from '../../types/api';\n\n/** Fields edited by the quick-edit modal (subset of the send-activity form) */\ntype EditFormData = Partial<\n Pick<\n SendActivityFormData,\n | 'subtype'\n | 'title'\n | 'notification'\n | 'startDate'\n | 'startTime'\n | 'finalDate'\n | 'finalTime'\n >\n>;\n\n/** Subset of the activity payload loaded to pre-fill the form */\ninterface EditableActivity {\n subtype: string | null;\n title: string;\n notification: string | null;\n startDate: string | null;\n finalDate: string | null;\n}\n\n/**\n * Props for the \"Editar atividade\" modal.\n */\ninterface EditActivityModalProps {\n /** Whether the modal is open */\n isOpen: boolean;\n /** Id of the published activity being edited */\n activityId?: string;\n /** API client used to load (GET /activities/:id/quiz) and save (PATCH /activities/:id) */\n apiClient?: BaseApiClient;\n /** Close the modal without saving */\n onClose: () => void;\n /** Fired after a successful save (consumer reloads the list + closes) */\n onSaved: () => void;\n}\n\nconst MAX_STEPS = 2;\n\nconst STEPPER_STEPS: StepData[] = [\n { id: 'activity', label: 'Atividade', state: 'pending' },\n { id: 'deadline', label: 'Prazo', state: 'pending' },\n];\n\n/** Coerce a backend subtype string into the local enum, if valid */\nconst toSubtype = (value?: string | null): ActivitySubtype | undefined =>\n (Object.values(ActivitySubtype) as string[]).includes(value ?? '')\n ? (value as ActivitySubtype)\n : undefined;\n\n/** Build the `PATCH /activities/:id` body from the edited form fields */\nexport const buildEditPayload = (formData: EditFormData) => ({\n subtype: formData.subtype,\n title: (formData.title ?? '').trim(),\n notification: (formData.notification ?? '').trim() || null,\n startDate: buildISODateTime(\n formData.startDate ?? '',\n formData.startTime || '00:00'\n ),\n finalDate: buildISODateTime(\n formData.finalDate ?? '',\n formData.finalTime || '23:59'\n ),\n});\n\n/**\n * Step 1 body: activity type chips + title + notification (mirrors SendActivityModal).\n * @returns the activity step element\n */\nconst ActivityStep: FC<{\n formData: EditFormData;\n errors: StepErrors;\n onSubtypeSelect: (value: ActivitySubtype) => void;\n onTitleChange: (e: ChangeEvent<HTMLInputElement>) => void;\n onNotificationChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;\n}> = ({\n formData,\n errors,\n onSubtypeSelect,\n onTitleChange,\n onNotificationChange,\n}) => (\n <div className=\"flex flex-col gap-6\">\n <div>\n <Text size=\"sm\" weight=\"medium\" color=\"text-text-700\" className=\"mb-3\">\n Tipo de atividade*\n </Text>\n <div className=\"flex flex-wrap gap-2\">\n {ACTIVITY_TYPE_OPTIONS.map((type) => (\n <Chips\n key={type.value}\n selected={formData.subtype === type.value}\n onClick={() => onSubtypeSelect(type.value)}\n >\n {type.label}\n </Chips>\n ))}\n </div>\n <SendModalError error={errors.subtype} />\n </div>\n\n <Input\n label=\"Título\"\n placeholder=\"Digite o título da atividade\"\n value={formData.title || ''}\n onChange={onTitleChange}\n variant=\"rounded\"\n required\n errorMessage={errors.title}\n />\n\n <TextArea\n label=\"Mensagem da notificação\"\n placeholder=\"Digite uma mensagem para a notificação (opcional)\"\n value={formData.notification || ''}\n onChange={onNotificationChange}\n />\n </div>\n);\n\n/**\n * Footer mirroring SendModalFooter, but with a \"Salvar alterações\" submit.\n * @returns the footer element\n */\nconst EditModalFooter: FC<{\n currentStep: number;\n isSaving: boolean;\n onCancel: () => void;\n onPreviousStep: () => void;\n onNextStep: () => void;\n onSubmit: () => void;\n}> = ({\n currentStep,\n isSaving,\n onCancel,\n onPreviousStep,\n onNextStep,\n onSubmit,\n}) => (\n <div className=\"flex flex-col-reverse sm:flex-row items-center justify-between gap-3 w-full\">\n <Button\n variant=\"link\"\n action=\"primary\"\n onClick={onCancel}\n className=\"w-full sm:w-auto\"\n >\n Cancelar\n </Button>\n\n <div className=\"flex flex-col-reverse sm:flex-row items-center gap-2 sm:gap-3 w-full sm:w-auto\">\n {currentStep > 1 && (\n <Button\n variant=\"outline\"\n action=\"primary\"\n onClick={onPreviousStep}\n iconLeft={<CaretLeftIcon size={16} />}\n className=\"w-full sm:w-auto\"\n >\n Anterior\n </Button>\n )}\n\n {currentStep >= MAX_STEPS ? (\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={onSubmit}\n disabled={isSaving}\n iconLeft={<FloppyDiskIcon size={16} />}\n className=\"w-full sm:w-auto\"\n >\n {isSaving ? 'Salvando...' : 'Salvar alterações'}\n </Button>\n ) : (\n <Button\n variant=\"solid\"\n action=\"primary\"\n onClick={onNextStep}\n iconRight={<ArrowRightIcon size={16} />}\n className=\"w-full sm:w-auto\"\n >\n Próximo\n </Button>\n )}\n </div>\n </div>\n);\n\n/**\n * Quick-edit modal for a published activity, styled as a faithful replica of\n * `SendActivityModal` (Modal + Stepper + step bodies + footer) but limited to\n * the editable fields: type, title, notification, start and deadline dates.\n * Loads via `GET /activities/:id/quiz` and saves via `PATCH /activities/:id`.\n * @returns the edit modal element\n */\nexport const EditActivityModal: FC<EditActivityModalProps> = ({\n isOpen,\n activityId,\n apiClient,\n onClose,\n onSaved,\n}) => {\n const addToast = useToastStore((state) => state.addToast);\n\n const [currentStep, setCurrentStep] = useState(1);\n\n /** Fetch the activity and map it to the editable form fields */\n const load = useCallback(async (): Promise<EditFormData> => {\n if (!apiClient || !activityId) return {};\n setCurrentStep(1);\n const response = await apiClient.get<{ data: EditableActivity }>(\n `/activities/${activityId}/quiz`\n );\n const activity = response.data.data;\n const start = splitDateTime(activity.startDate);\n const final = splitDateTime(activity.finalDate);\n return {\n subtype: toSubtype(activity.subtype),\n title: activity.title ?? '',\n notification: activity.notification ?? '',\n startDate: start.date,\n startTime: start.time || '00:00',\n finalDate: final.date,\n finalTime: final.time || '23:59',\n };\n }, [apiClient, activityId]);\n\n const onLoadError = useCallback(() => {\n addToast({\n title: 'Erro ao carregar atividade',\n action: 'warning',\n position: 'top-right',\n });\n onClose();\n }, [addToast, onClose]);\n\n const {\n formData,\n errors,\n setErrors,\n loading,\n saving,\n setSaving,\n updateFormData,\n dateHandlers,\n } = useEditModalForm<EditFormData>({\n isOpen,\n enabled: !!activityId && !!apiClient,\n load,\n onLoadError,\n });\n\n const handleSubtypeSelect = useCallback(\n (value: ActivitySubtype) => updateFormData({ subtype: value }),\n [updateFormData]\n );\n\n const handleTitleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) =>\n updateFormData({ title: e.target.value }),\n [updateFormData]\n );\n\n const handleNotificationChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) =>\n updateFormData({ notification: e.target.value }),\n [updateFormData]\n );\n\n const handleNextStep = useCallback(() => {\n const stepErrors = validateActivityStep(formData);\n if (Object.keys(stepErrors).length > 0) {\n setErrors(stepErrors);\n return;\n }\n setErrors({});\n setCurrentStep(2);\n }, [formData]);\n\n const handlePreviousStep = useCallback(() => {\n setErrors({});\n setCurrentStep(1);\n }, []);\n\n const handleSubmit = useCallback(async () => {\n if (!apiClient || !activityId) return;\n\n // Step 1 was already validated to reach step 2; only re-check the dates.\n const step2Errors = validateDeadlineStep(formData);\n if (Object.keys(step2Errors).length > 0) {\n setErrors(step2Errors);\n return;\n }\n\n setSaving(true);\n try {\n await apiClient.patch(\n `/activities/${activityId}`,\n buildEditPayload(formData)\n );\n addToast({\n title: 'Atividade atualizada com sucesso',\n action: 'success',\n position: 'top-right',\n });\n onSaved();\n } catch {\n addToast({\n title: 'Erro ao atualizar atividade',\n action: 'warning',\n position: 'top-right',\n });\n } finally {\n setSaving(false);\n }\n }, [apiClient, activityId, formData, addToast, onSaved]);\n\n const deadlineErrors = useMemo(\n () => ({\n startDate: errors.startDate,\n startTime: errors.startTime,\n finalDate: errors.finalDate,\n finalTime: errors.finalTime,\n }),\n [errors]\n );\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Editar atividade\"\n size=\"md\"\n contentClassName=\"flex flex-col gap-8 sm:gap-10 max-h-[70vh] overflow-y-auto\"\n footer={\n <EditModalFooter\n currentStep={currentStep}\n isSaving={saving}\n onCancel={onClose}\n onPreviousStep={handlePreviousStep}\n onNextStep={handleNextStep}\n onSubmit={handleSubmit}\n />\n }\n >\n {loading ? (\n <Text size=\"sm\" className=\"text-text-600\">\n Carregando...\n </Text>\n ) : (\n <>\n <Stepper\n steps={STEPPER_STEPS}\n currentStep={currentStep - 1}\n size=\"small\"\n />\n\n {currentStep === 1 ? (\n <ActivityStep\n formData={formData}\n errors={errors}\n onSubtypeSelect={handleSubtypeSelect}\n onTitleChange={handleTitleChange}\n onNotificationChange={handleNotificationChange}\n />\n ) : (\n <DeadlineStep\n startDate={formData.startDate ?? ''}\n startTime={formData.startTime ?? ''}\n finalDate={formData.finalDate ?? ''}\n finalTime={formData.finalTime ?? ''}\n onStartDateChange={dateHandlers.handleStartDateChange}\n onStartTimeChange={dateHandlers.handleStartTimeChange}\n onFinalDateChange={dateHandlers.handleFinalDateChange}\n onFinalTimeChange={dateHandlers.handleFinalTimeChange}\n errors={deadlineErrors}\n />\n )}\n </>\n )}\n </Modal>\n );\n};\n\nexport default EditActivityModal;\n","import { useCallback, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { PlusIcon } from '@phosphor-icons/react/dist/csr/Plus';\nimport { PAGE_CONFIG, getPageLayout } from './config';\nimport type { UnifiedDraftModelPageProps } from './types';\nimport EmptyState from '../EmptyState/EmptyState';\nimport { AlertDialog } from '../AlertDialog/AlertDialog';\nimport TypeSelector from '../TypeSelector/TypeSelector';\nimport { createActivityCategoryConfig } from '../TypeSelector/TypeSelector.types';\nimport type { ActivityModelTableItem } from '../../types/activitiesHistory';\nimport { useActivityDraftModelPage } from '../../hooks/useActivityDraftModelPage';\nimport { ActivityTab } from '../ActivityPageLayout/ActivityPageLayout';\nimport { ExamTab } from '../ExamPageLayout/ExamPageLayout';\n\n/**\n * Unified page component for Activity/Exam Drafts and Models\n * Delegates most logic to useActivityDraftModelPage hook\n */\nexport const UnifiedDraftModelPage = ({\n type,\n activityCategory,\n data,\n loading,\n error,\n pagination,\n onDelete,\n onSend,\n onParamsChange,\n userData = null,\n activityImage,\n noSearchImage,\n routes,\n}: UnifiedDraftModelPageProps) => {\n const navigate = useNavigate();\n const config = PAGE_CONFIG[activityCategory][type];\n const PageLayout = getPageLayout(activityCategory);\n\n /**\n * TypeSelector config with proper labels, routes, and status options\n */\n const typeSelectorConfig = useMemo(\n () => createActivityCategoryConfig(routes),\n [routes]\n );\n\n // Extract subject options from fetched data\n const apiSubjectOptions = useMemo(\n () =>\n data\n .filter(\n (item: ActivityModelTableItem) =>\n item.subjectId && item.subject?.name && item.subject.name !== '-'\n )\n .map((item: ActivityModelTableItem) => ({\n id: item.subjectId!,\n name: item.subject!.name,\n })),\n [data]\n );\n\n // Use shared hook for state and handlers\n const {\n isDeleteDialogOpen,\n setIsDeleteDialogOpen,\n itemToDeleteTitle,\n initialFilterConfigs,\n tableColumns,\n handleConfirmDelete: hookHandleConfirmDelete,\n handleParamsChange,\n handleCreateActivity,\n handleRowClick,\n } = useActivityDraftModelPage({\n activityCategory,\n fetchFn: onParamsChange,\n deleteFn: onDelete,\n userData,\n apiSubjectOptions,\n openSendModal: onSend || (() => {}),\n editUrlType: config.editUrlType,\n errorLogLabel: config.errorLogLabel,\n routes: routes[activityCategory],\n });\n\n // Wrap hook's handleConfirmDelete to close dialog on success\n const handleConfirmDelete = useCallback(async () => {\n const success = await hookHandleConfirmDelete();\n // Only close dialog if delete was successful\n if (success) {\n setIsDeleteDialogOpen(false);\n }\n }, [hookHandleConfirmDelete, setIsDeleteDialogOpen]);\n\n /**\n * Handle tab change - navigate to the corresponding page\n * Component-specific: not provided by hook\n */\n const handleTabChange = useCallback(\n (tab: string) => {\n const currentRoutes = routes[activityCategory];\n switch (tab) {\n case ActivityTab.HISTORY:\n case ExamTab.HISTORY:\n navigate(currentRoutes.base);\n break;\n case ActivityTab.DRAFTS:\n case ExamTab.DRAFTS:\n navigate(`${currentRoutes.base}/rascunhos`);\n break;\n case ActivityTab.MODELS:\n case ExamTab.MODELS:\n navigate(`${currentRoutes.base}/modelos`);\n break;\n default:\n navigate(\n `${currentRoutes.base}/${type === 'drafts' ? 'rascunhos' : 'modelos'}`\n );\n }\n },\n [navigate, routes, type, activityCategory]\n );\n\n // Build layout props dynamically\n const layoutProps = {\n activeTab: config.activeTab,\n pageTitle: config.pageTitle,\n headerRightContent: (\n <TypeSelector\n value={activityCategory}\n currentTab={config.currentTab}\n config={typeSelectorConfig}\n />\n ),\n testId: config.testId,\n data,\n headers: tableColumns,\n loading,\n error,\n pagination,\n initialFilters: initialFilterConfigs,\n itemLabel: config.itemLabel,\n searchPlaceholder: config.searchPlaceholder,\n emptyState: activityImage ? (\n <EmptyState\n image={activityImage}\n title={config.emptyTitle}\n description={config.emptyDescription}\n buttonText={config.buttonText}\n buttonIcon={<PlusIcon size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={handleCreateActivity}\n />\n ) : undefined,\n noSearchImage,\n onParamsChange: handleParamsChange,\n onRowClick: handleRowClick,\n onTabChange: handleTabChange,\n [config.onCreatePropName]: handleCreateActivity,\n };\n\n return (\n <>\n {/* Type assertion needed: PageLayout is conditionally ActivityPageLayout | ExamPageLayout.\n layoutProps uses computed property [config.onCreatePropName] which is either\n 'onCreateActivity' or 'onCreateExam', satisfying both layouts at runtime.\n TypeScript cannot verify this dynamic key matches the expected prop name. */}\n {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}\n <PageLayout {...(layoutProps as any)} />\n\n {/* Delete Confirmation Dialog */}\n <AlertDialog\n isOpen={isDeleteDialogOpen}\n onChangeOpen={setIsDeleteDialogOpen}\n title={config.dialogTitle}\n description={`Tem certeza que deseja excluir o ${config.errorLogLabel} \"${itemToDeleteTitle}\"? Esta ação não pode ser desfeita.`}\n cancelButtonLabel=\"Cancelar\"\n submitButtonLabel=\"Excluir\"\n onSubmit={handleConfirmDelete}\n />\n </>\n );\n};\n","import {\n ActivityPageLayout,\n ActivityTab,\n} from '../ActivityPageLayout/ActivityPageLayout';\nimport { ExamPageLayout, ExamTab } from '../ExamPageLayout/ExamPageLayout';\nimport type { ActivityCategory } from '../TypeSelector/TypeSelector.types';\n\n/**\n * Page config type for drafts or models\n */\ninterface DraftModelConfig {\n activeTab: string;\n pageTitle: string;\n emptyTitle: string;\n emptyDescription: string;\n buttonText: string;\n itemLabel: string;\n searchPlaceholder: string;\n dialogTitle: string;\n editUrlType: 'rascunho' | 'modelo';\n errorLogLabel: string;\n currentTab: 'drafts' | 'models';\n dataKey: 'drafts' | 'models';\n fetchKey: 'fetchDrafts' | 'fetchModels';\n deleteKey: 'deleteDraft' | 'deleteModel';\n testId: string;\n onCreatePropName: 'onCreateActivity' | 'onCreateExam';\n}\n\n/**\n * Page config by category and type\n */\ntype CategoryConfig = {\n drafts: DraftModelConfig;\n models: DraftModelConfig;\n};\n\n/**\n * Configuration for drafts vs models by activity category\n */\nexport const PAGE_CONFIG: Record<ActivityCategory, CategoryConfig> = {\n ATIVIDADE: {\n drafts: {\n activeTab: ActivityTab.DRAFTS,\n pageTitle: 'Rascunhos de atividades',\n emptyTitle: 'Você ainda não tem rascunhos',\n emptyDescription:\n 'Comece a criar uma atividade e salve como rascunho para continuar depois!',\n buttonText: 'Criar atividade',\n itemLabel: 'rascunhos',\n searchPlaceholder: 'Buscar rascunho',\n dialogTitle: 'Excluir rascunho',\n editUrlType: 'rascunho' as const,\n errorLogLabel: 'rascunho',\n currentTab: 'drafts' as const,\n dataKey: 'drafts' as const,\n fetchKey: 'fetchDrafts' as const,\n deleteKey: 'deleteDraft' as const,\n testId: 'activity-drafts-page',\n onCreatePropName: 'onCreateActivity' as const,\n },\n models: {\n activeTab: ActivityTab.MODELS,\n pageTitle: 'Modelos de atividades',\n emptyTitle: 'Você ainda não tem modelos salvos',\n emptyDescription:\n 'Salve uma atividade como modelo para reutilizá-la facilmente no futuro!',\n buttonText: 'Criar atividade',\n itemLabel: 'modelos',\n searchPlaceholder: 'Buscar modelo',\n dialogTitle: 'Excluir modelo',\n editUrlType: 'modelo' as const,\n errorLogLabel: 'modelo',\n currentTab: 'models' as const,\n dataKey: 'models' as const,\n fetchKey: 'fetchModels' as const,\n deleteKey: 'deleteModel' as const,\n testId: 'activity-models-page',\n onCreatePropName: 'onCreateActivity' as const,\n },\n },\n PROVA: {\n drafts: {\n activeTab: ExamTab.DRAFTS,\n pageTitle: 'Rascunhos de provas',\n emptyTitle: 'Você ainda não tem rascunhos',\n emptyDescription:\n 'Comece a criar uma prova e salve como rascunho para continuar depois!',\n buttonText: 'Criar prova',\n itemLabel: 'rascunhos',\n searchPlaceholder: 'Buscar rascunho',\n dialogTitle: 'Excluir rascunho',\n editUrlType: 'rascunho' as const,\n errorLogLabel: 'rascunho',\n currentTab: 'drafts' as const,\n dataKey: 'drafts' as const,\n fetchKey: 'fetchDrafts' as const,\n deleteKey: 'deleteDraft' as const,\n testId: 'exam-drafts-page',\n onCreatePropName: 'onCreateExam' as const,\n },\n models: {\n activeTab: ExamTab.MODELS,\n pageTitle: 'Modelos de provas',\n emptyTitle: 'Você ainda não tem modelos salvos',\n emptyDescription:\n 'Salve uma prova como modelo para reutilizá-la facilmente no futuro!',\n buttonText: 'Criar prova',\n itemLabel: 'modelos',\n searchPlaceholder: 'Buscar modelo',\n dialogTitle: 'Excluir modelo',\n editUrlType: 'modelo' as const,\n errorLogLabel: 'modelo',\n currentTab: 'models' as const,\n dataKey: 'models' as const,\n fetchKey: 'fetchModels' as const,\n deleteKey: 'deleteModel' as const,\n testId: 'exam-models-page',\n onCreatePropName: 'onCreateExam' as const,\n },\n },\n} as const;\n\n/**\n * Get page layout component based on activity category\n */\nexport const getPageLayout = (activityCategory: ActivityCategory) => {\n return activityCategory === 'PROVA' ? ExamPageLayout : ActivityPageLayout;\n};\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport type { FilterConfig } from '../components/Filter';\nimport type {\n ActivityModelTableItem,\n ActivityFilterOption,\n} from '../types/activitiesHistory';\nimport type { ColumnConfig } from '../components/TableProvider/TableProvider';\nimport type {\n ActivityCategory,\n TypeRoutes,\n} from '../components/TypeSelector/TypeSelector.types';\nimport { createExamDraftsModelsTableColumns } from '../components/ExamPageLayout/examDraftsModelsTableConfig';\nimport {\n getSubjectOptionsFromUserData,\n mergeFilterOptions,\n type UserFilterSourceData,\n} from '../utils/filterHelpers';\nimport { createDraftsModelsFiltersConfig } from '../utils/draftModelFilterHelpers';\n\n/**\n * Configuration options for the useActivityDraftModelPage hook\n */\nexport interface UseActivityDraftModelPageOptions {\n /** Activity category (ATIVIDADE or PROVA) */\n activityCategory: ActivityCategory;\n /** Function to fetch data with the given filters */\n fetchFn: (params: {\n page?: number;\n limit?: number;\n search?: string;\n subjectId?: string;\n }) => Promise<void> | void;\n /** Function to delete an item by id */\n deleteFn: (id: string) => Promise<void> | Promise<boolean>;\n /** User data for filter options */\n userData: UserFilterSourceData | null;\n /** Subject options from API response */\n apiSubjectOptions: ActivityFilterOption[];\n /** Function to open the send activity modal */\n openSendModal: (row: ActivityModelTableItem) => void;\n /** URL type segment used in edit/row-click navigation */\n editUrlType: 'rascunho' | 'modelo';\n /** Label used in error log messages */\n errorLogLabel: string;\n /** Routes configuration */\n routes: TypeRoutes;\n}\n\n/**\n * Return value of the useActivityDraftModelPage hook\n */\nexport interface UseActivityDraftModelPageReturn {\n isDeleteDialogOpen: boolean;\n setIsDeleteDialogOpen: (open: boolean) => void;\n itemToDeleteTitle: string;\n initialFilterConfigs: FilterConfig[];\n tableColumns: ColumnConfig<ActivityModelTableItem>[];\n handleSend: (row: ActivityModelTableItem) => void;\n handleDelete: (row: ActivityModelTableItem) => void;\n handleEdit: (row: ActivityModelTableItem) => void;\n handleConfirmDelete: () => Promise<boolean>;\n handleParamsChange: (params: {\n page?: number;\n limit?: number;\n search?: string;\n subjectId?: string;\n }) => void;\n handleCreateActivity: () => void;\n handleRowClick: (row: ActivityModelTableItem) => void;\n}\n\n/**\n * Shared hook encapsulating common logic for Activity/Exam Drafts and Models pages.\n * Manages delete dialog state, send modal, table columns, filter config,\n * and all action/navigation callbacks.\n * @param options - Configuration options for the hook\n * @returns Shared state and callbacks for the page\n */\nexport const useActivityDraftModelPage = ({\n activityCategory,\n fetchFn,\n deleteFn,\n userData,\n apiSubjectOptions,\n openSendModal,\n editUrlType,\n errorLogLabel,\n routes,\n}: UseActivityDraftModelPageOptions): UseActivityDraftModelPageReturn => {\n const navigate = useNavigate();\n\n const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);\n const [itemToDeleteId, setItemToDeleteId] = useState<string | null>(null);\n const [itemToDeleteTitle, setItemToDeleteTitle] = useState<string>('');\n const [currentParams, setCurrentParams] = useState<{\n page?: number;\n limit?: number;\n search?: string;\n subjectId?: string;\n }>({\n page: 1,\n limit: 10,\n });\n\n /**\n * Initial filter configuration: merge userData subjects + subjects from API response\n */\n const initialFilterConfigs = useMemo(\n () =>\n createDraftsModelsFiltersConfig(\n mergeFilterOptions(\n getSubjectOptionsFromUserData(userData),\n apiSubjectOptions\n )\n ),\n [userData, apiSubjectOptions]\n );\n\n /**\n * Handle send activity button click\n * @param row - The row to send\n */\n const handleSend = useCallback(\n (row: ActivityModelTableItem) => {\n openSendModal(row);\n },\n [openSendModal]\n );\n\n /**\n * Handle delete button click — opens confirmation dialog\n * @param row - The row to delete\n */\n const handleDelete = useCallback((row: ActivityModelTableItem) => {\n setItemToDeleteId(row.id);\n setItemToDeleteTitle(row.title);\n setIsDeleteDialogOpen(true);\n }, []);\n\n /**\n * Handle edit button click — navigate to create activity with item pre-loaded\n * @param row - The row to edit\n */\n const handleEdit = useCallback(\n (row: ActivityModelTableItem) => {\n navigate(\n routes.editDraft?.(row.id) ||\n routes.editModel?.(row.id) ||\n `${routes.create}?type=${editUrlType}&id=${row.id}`\n );\n },\n [navigate, routes, editUrlType]\n );\n\n /**\n * Handle confirm delete action\n * @returns true if delete was successful, false otherwise\n */\n const handleConfirmDelete = useCallback(async (): Promise<boolean> => {\n if (!itemToDeleteId) return false;\n\n try {\n await deleteFn(itemToDeleteId);\n await fetchFn(currentParams);\n return true;\n } catch (err) {\n console.error(`Erro ao deletar ${errorLogLabel}:`, err);\n return false;\n }\n }, [itemToDeleteId, deleteFn, fetchFn, currentParams, errorLogLabel]);\n\n /**\n * Create table columns with action callbacks.\n * The send button label follows the category: \"Enviar prova\" for exams,\n * \"Enviar atividade\" for activities.\n */\n const sendLabel =\n activityCategory === 'PROVA' ? 'Enviar prova' : 'Enviar atividade';\n const tableColumns = useMemo(\n () =>\n createExamDraftsModelsTableColumns(\n {\n onSend: handleSend,\n onDelete: handleDelete,\n onEdit: handleEdit,\n },\n sendLabel\n ),\n [handleSend, handleDelete, handleEdit, sendLabel]\n );\n\n /**\n * Handle table params change\n */\n const handleParamsChange = useCallback(\n (params: {\n page?: number;\n limit?: number;\n search?: string;\n subjectId?: string;\n }) => {\n const filters = {\n page: params.page || 1,\n limit: params.limit || 10,\n search: params.search,\n subjectId: params.subjectId,\n };\n setCurrentParams(filters);\n fetchFn(filters);\n },\n [fetchFn]\n );\n\n /**\n * Fetch initial data on component mount\n */\n useEffect(() => {\n fetchFn({ page: 1, limit: 10 });\n }, [fetchFn]);\n\n /**\n * Handle create activity button click\n */\n const handleCreateActivity = useCallback(() => {\n navigate(routes.create);\n }, [navigate, routes]);\n\n /**\n * Handle row click — navigate to edit item\n * @param row - The row clicked\n */\n const handleRowClick = useCallback(\n (row: ActivityModelTableItem) => {\n navigate(\n routes.editDraft?.(row.id) ||\n routes.editModel?.(row.id) ||\n `${routes.create}?type=${editUrlType}&id=${row.id}`\n );\n },\n [navigate, routes, editUrlType]\n );\n\n return {\n isDeleteDialogOpen,\n setIsDeleteDialogOpen,\n itemToDeleteTitle,\n initialFilterConfigs,\n tableColumns,\n handleSend,\n handleDelete,\n handleEdit,\n handleConfirmDelete,\n handleParamsChange,\n handleCreateActivity,\n handleRowClick,\n };\n};\n","import type { MouseEvent } from 'react';\nimport Button from '../Button/Button';\nimport IconButton from '../IconButton/IconButton';\nimport { renderTextCell } from '../../utils/renderTextCell';\nimport { renderSubjectCell } from '../../utils/renderSubjectCell';\nimport { mapSubjectNameToEnum } from '../../utils/subjectMappers';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport { PaperPlaneTiltIcon } from '@phosphor-icons/react/dist/csr/PaperPlaneTilt';\nimport { TrashIcon } from '@phosphor-icons/react/dist/csr/Trash';\nimport { PencilSimpleIcon } from '@phosphor-icons/react/dist/csr/PencilSimple';\nimport type { ActivityModelTableItem } from '../../types/activitiesHistory';\n\n/**\n * Callbacks interface for table action buttons\n */\nexport interface ExamTableCallbacks {\n /** Callback when send exam button is clicked */\n onSend: (row: ActivityModelTableItem) => void;\n /** Callback when delete button is clicked */\n onDelete: (row: ActivityModelTableItem) => void;\n /** Callback when edit button is clicked */\n onEdit: (row: ActivityModelTableItem) => void;\n}\n\n/**\n * Factory function to create column configuration for drafts and models tables\n * (used by both exam and activity pages).\n * Returns columns with action buttons that use the provided callbacks\n * @param callbacks - Object containing onSend, onDelete, and onEdit handlers\n * @param sendLabel - Label for the send button (e.g. \"Enviar prova\" / \"Enviar atividade\")\n * @returns Column configuration array for TableProvider\n */\nexport const createExamDraftsModelsTableColumns = (\n callbacks: ExamTableCallbacks,\n sendLabel = 'Enviar prova'\n): ColumnConfig<ActivityModelTableItem>[] => [\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[300px] truncate',\n render: renderTextCell,\n },\n {\n key: 'savedAt',\n label: 'Salvo em',\n sortable: true,\n },\n {\n key: 'subject',\n label: 'Disciplina',\n sortable: true,\n className: 'max-w-[200px]',\n render: (value: unknown) => {\n // Handle both object format (SubjectData) and string format\n if (value && typeof value === 'object' && 'name' in value) {\n const subjectData = value as { name: string };\n return renderSubjectCell(subjectData.name || '-', mapSubjectNameToEnum);\n }\n return renderSubjectCell(\n typeof value === 'string' ? value : '-',\n mapSubjectNameToEnum\n );\n },\n },\n {\n key: 'actions',\n label: '',\n sortable: false,\n className: 'w-[280px]',\n render: (_value: unknown, row: ActivityModelTableItem) => (\n <div className=\"flex items-center justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"small\"\n iconLeft={<PaperPlaneTiltIcon size={16} />}\n onClick={(e: MouseEvent) => {\n e.stopPropagation();\n callbacks.onSend(row);\n }}\n >\n {sendLabel}\n </Button>\n <IconButton\n icon={<TrashIcon size={20} />}\n className=\"hover:text-error-500\"\n aria-label=\"Deletar\"\n onClick={(e: MouseEvent) => {\n e.stopPropagation();\n callbacks.onDelete(row);\n }}\n />\n <IconButton\n icon={<PencilSimpleIcon size={20} />}\n className=\"hover:text-primary-700\"\n aria-label=\"Editar\"\n onClick={(e: MouseEvent) => {\n e.stopPropagation();\n callbacks.onEdit(row);\n }}\n />\n </div>\n ),\n },\n];\n","import type { FilterConfig } from '../components/Filter';\nimport type { ActivityFilterOption } from '../types/activitiesHistory';\n\n/**\n * Create filter configuration for drafts/models pages\n * Shared utility used by both UnifiedDraftModelPage component and useActivityDraftModelPage hook\n */\nexport const createDraftsModelsFiltersConfig = (\n subjectOptions: ActivityFilterOption[]\n): FilterConfig[] => [\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: subjectOptions,\n },\n ],\n },\n];\n","import Text from '../Text/Text';\n\n/**\n * Loading state component for Chat\n * Displayed when user info is not yet available\n */\nexport function ChatLoading() {\n return (\n <div className=\"flex items-center justify-center h-96\">\n <Text size=\"sm\" className=\"text-text-500\">\n Carregando...\n </Text>\n </div>\n );\n}\n\nexport default ChatLoading;\n","import { useState, useCallback } from 'react';\nimport type {\n ForumApiClient,\n ForumTopic,\n ForumReply,\n ForumPagination,\n} from '../types/forum';\n\ntype UseForumState = {\n topics: ForumTopic[];\n pagination: ForumPagination | null;\n selectedTopic: ForumTopic | null;\n replies: ForumReply[];\n isLoadingTopics: boolean;\n isLoadingTopic: boolean;\n isSubmitting: boolean;\n error: string | null;\n};\n\nexport type UseForumReturn = UseForumState & {\n fetchTopics: (params?: { limit?: number; offset?: number }) => Promise<void>;\n fetchTopic: (topicId: string) => Promise<void>;\n createTopic: (body: {\n content: string;\n imageUrl?: string;\n countsForGrade?: boolean;\n }) => Promise<void>;\n updateTopic: (\n topicId: string,\n body: { content?: string; imageUrl?: string | null }\n ) => Promise<void>;\n deleteTopic: (topicId: string) => Promise<void>;\n createReply: (\n topicId: string,\n body: { content: string; imageUrl?: string }\n ) => Promise<void>;\n updateReply: (\n replyId: string,\n body: { content?: string; imageUrl?: string | null }\n ) => Promise<void>;\n deleteReply: (replyId: string) => Promise<void>;\n selectTopic: (topic: ForumTopic | null) => void;\n clearError: () => void;\n};\n\n/* -- State updaters (extracted to reduce nesting depth — SonarQube) -- */\n\nconst replaceTopicById =\n (topicId: string, body: { content?: string; imageUrl?: string | null }) =>\n (prev: ForumTopic[]) =>\n prev.map((t) => (t.id === topicId ? { ...t, ...body } : t));\n\nconst removeTopicById = (topicId: string) => (prev: ForumTopic[]) =>\n prev.filter((t) => t.id !== topicId);\n\nconst replaceReplyById =\n (replyId: string, body: { content?: string; imageUrl?: string | null }) =>\n (prev: ForumReply[]) =>\n prev.map((r) => (r.id === replyId ? { ...r, ...body } : r));\n\nconst updateSelectedTopic =\n (topicId: string, body: { content?: string; imageUrl?: string | null }) =>\n (prev: ForumTopic | null) =>\n prev?.id === topicId ? { ...prev, ...body } : prev;\n\nconst clearSelectedTopicIfMatch =\n (topicId: string, clearReplies: () => void) => (prev: ForumTopic | null) => {\n if (prev?.id === topicId) {\n clearReplies();\n return null;\n }\n return prev;\n };\n\nconst removeReplyById = (replyId: string) => (prev: ForumReply[]) =>\n prev.filter((r) => r.id !== replyId);\n\nexport const createUseForum = (apiClient: ForumApiClient) => {\n return (): UseForumReturn => {\n const [topics, setTopics] = useState<ForumTopic[]>([]);\n const [pagination, setPagination] = useState<ForumPagination | null>(null);\n const [selectedTopic, setSelectedTopic] = useState<ForumTopic | null>(null);\n const [replies, setReplies] = useState<ForumReply[]>([]);\n const [isLoadingTopics, setIsLoadingTopics] = useState(false);\n const [isLoadingTopic, setIsLoadingTopic] = useState(false);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n /** Wraps a mutation call with isSubmitting / error handling. */\n const withSubmit = useCallback(\n async (action: () => Promise<void>, errorMsg: string) => {\n setIsSubmitting(true);\n setError(null);\n try {\n await action();\n } catch {\n setError(errorMsg);\n throw new Error(errorMsg);\n } finally {\n setIsSubmitting(false);\n }\n },\n []\n );\n\n const clearReplies = useCallback(() => setReplies([]), []);\n\n const fetchTopics = useCallback(\n async (params?: { limit?: number; offset?: number }) => {\n setIsLoadingTopics(true);\n setError(null);\n try {\n const response = await apiClient.getTopics(params);\n setTopics(response.topics);\n setPagination(response.pagination);\n } catch {\n setError('Erro ao carregar os tópicos.');\n } finally {\n setIsLoadingTopics(false);\n }\n },\n [apiClient]\n );\n\n const fetchTopic = useCallback(\n async (topicId: string) => {\n setIsLoadingTopic(true);\n setError(null);\n try {\n const response = await apiClient.getTopic(topicId);\n setSelectedTopic(response.topic);\n setReplies(response.replies);\n } catch {\n setError('Erro ao carregar o tópico.');\n } finally {\n setIsLoadingTopic(false);\n }\n },\n [apiClient]\n );\n\n const createTopic = useCallback(\n async (body: {\n content: string;\n imageUrl?: string;\n countsForGrade?: boolean;\n }) =>\n withSubmit(\n () => apiClient.createTopic(body),\n 'Erro ao criar o tópico.'\n ),\n [apiClient, withSubmit]\n );\n\n const updateTopic = useCallback(\n async (\n topicId: string,\n body: { content?: string; imageUrl?: string | null }\n ) =>\n withSubmit(async () => {\n await apiClient.updateTopic(topicId, body);\n setTopics(replaceTopicById(topicId, body));\n setSelectedTopic(updateSelectedTopic(topicId, body));\n }, 'Erro ao atualizar o tópico.'),\n [apiClient, withSubmit]\n );\n\n const deleteTopic = useCallback(\n async (topicId: string) =>\n withSubmit(async () => {\n await apiClient.deleteTopic(topicId);\n setTopics(removeTopicById(topicId));\n setSelectedTopic(clearSelectedTopicIfMatch(topicId, clearReplies));\n }, 'Erro ao excluir o tópico.'),\n [apiClient, withSubmit]\n );\n\n const createReply = useCallback(\n async (topicId: string, body: { content: string; imageUrl?: string }) =>\n withSubmit(\n () => apiClient.createReply(topicId, body),\n 'Erro ao criar a resposta.'\n ),\n [apiClient, withSubmit]\n );\n\n const updateReply = useCallback(\n async (\n replyId: string,\n body: { content?: string; imageUrl?: string | null }\n ) =>\n withSubmit(async () => {\n await apiClient.updateReply(replyId, body);\n setReplies(replaceReplyById(replyId, body));\n }, 'Erro ao atualizar a resposta.'),\n [apiClient, withSubmit]\n );\n\n const deleteReply = useCallback(\n async (replyId: string) =>\n withSubmit(async () => {\n await apiClient.deleteReply(replyId);\n setReplies(removeReplyById(replyId));\n }, 'Erro ao excluir a resposta.'),\n [apiClient, withSubmit]\n );\n\n const selectTopic = useCallback((topic: ForumTopic | null) => {\n setSelectedTopic(topic);\n setReplies([]);\n }, []);\n\n const clearError = useCallback(() => {\n setError(null);\n }, []);\n\n return {\n topics,\n pagination,\n selectedTopic,\n replies,\n isLoadingTopics,\n isLoadingTopic,\n isSubmitting,\n error,\n fetchTopics,\n fetchTopic,\n createTopic,\n updateTopic,\n deleteTopic,\n createReply,\n updateReply,\n deleteReply,\n selectTopic,\n clearError,\n };\n };\n};\n","import { useState, useEffect, useCallback } from 'react';\nimport Modal from '../Modal/Modal';\nimport Button from '../Button/Button';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport { cn } from '../../utils/utils';\nimport {\n PERIOD_TABS,\n type StudentsHighlightPeriod,\n} from '../../hooks/useStudentsHighlight';\nimport Text from '../Text/Text';\n\n/**\n * Period selection type - either a fixed period or custom date range\n */\nexport interface PeriodSelection {\n /** Selection type: 'fixed' for predefined periods, 'custom' for date range */\n type: 'fixed' | 'custom';\n /** Selected period when type is 'fixed' */\n period?: StudentsHighlightPeriod;\n /** Start date when type is 'custom' (ISO date string YYYY-MM-DD) */\n startDate?: string;\n /** End date when type is 'custom' (ISO date string YYYY-MM-DD) */\n endDate?: string;\n}\n\n/**\n * Default period selection (1 year)\n */\nexport const DEFAULT_PERIOD_SELECTION: PeriodSelection = {\n type: 'fixed',\n period: '1_YEAR',\n};\n\ninterface PeriodSelectorModalProps {\n /** Whether the modal is open */\n readonly isOpen: boolean;\n /** Callback when modal is closed */\n readonly onClose: () => void;\n /** Callback when period selection is applied */\n readonly onApply: (selection: PeriodSelection) => void;\n /** Current period selection */\n readonly currentSelection?: PeriodSelection;\n}\n\n/**\n * Format date to display format (DD/MM/YYYY)\n */\nfunction formatDateDisplay(isoDate: string | undefined): string {\n if (!isoDate) return '';\n const [year, month, day] = isoDate.split('-');\n return `${day}/${month}/${year}`;\n}\n\n/**\n * Get display label for a period selection\n */\nexport function getPeriodSelectionLabel(selection: PeriodSelection): string {\n if (selection.type === 'fixed') {\n const tab = PERIOD_TABS.find((t) => t.value === selection.period);\n return tab?.label ?? '1 ano';\n }\n if (selection.startDate && selection.endDate) {\n return `${formatDateDisplay(selection.startDate)} - ${formatDateDisplay(selection.endDate)}`;\n }\n return '1 ano';\n}\n\n/**\n * PeriodSelectorModal - Modal for selecting report period\n *\n * Allows selecting either a fixed period (7 days, 1 month, etc.) or\n * a custom date range with start and end dates.\n *\n * @example\n * ```tsx\n * <PeriodSelectorModal\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * onApply={(selection) => setPeriodSelection(selection)}\n * currentSelection={periodSelection}\n * />\n * ```\n */\nexport function PeriodSelectorModal({\n isOpen,\n onClose,\n onApply,\n currentSelection = DEFAULT_PERIOD_SELECTION,\n}: PeriodSelectorModalProps) {\n // Local state for the form\n const [selectedPeriod, setSelectedPeriod] = useState<\n StudentsHighlightPeriod | ''\n >('');\n const [startDate, setStartDate] = useState('');\n const [endDate, setEndDate] = useState('');\n const [dateError, setDateError] = useState<string | null>(null);\n\n // Reset form when modal opens\n useEffect(() => {\n if (isOpen) {\n if (currentSelection.type === 'fixed') {\n setSelectedPeriod(currentSelection.period ?? '1_YEAR');\n setStartDate('');\n setEndDate('');\n } else {\n setSelectedPeriod('');\n setStartDate(currentSelection.startDate ?? '');\n setEndDate(currentSelection.endDate ?? '');\n }\n setDateError(null);\n }\n }, [isOpen, currentSelection]);\n\n // Handle fixed period selection\n const handlePeriodChange = useCallback((value: string) => {\n setSelectedPeriod(value as StudentsHighlightPeriod);\n // Clear custom dates when selecting fixed period\n setStartDate('');\n setEndDate('');\n setDateError(null);\n }, []);\n\n // Handle custom date changes\n const handleStartDateChange = useCallback((value: string) => {\n setStartDate(value);\n // Clear fixed period when entering custom dates\n setSelectedPeriod('');\n setDateError(null);\n }, []);\n\n const handleEndDateChange = useCallback((value: string) => {\n setEndDate(value);\n // Clear fixed period when entering custom dates\n setSelectedPeriod('');\n setDateError(null);\n }, []);\n\n // Validate dates\n const validateDates = useCallback((): boolean => {\n if (!startDate || !endDate) {\n return false;\n }\n const start = new Date(startDate);\n const end = new Date(endDate);\n if (end < start) {\n setDateError('Data final deve ser maior ou igual à data inicial');\n return false;\n }\n return true;\n }, [startDate, endDate]);\n\n // Handle apply\n const handleApply = useCallback(() => {\n if (selectedPeriod) {\n // Fixed period selected\n onApply({\n type: 'fixed',\n period: selectedPeriod,\n });\n onClose();\n } else if (startDate && endDate) {\n // Custom dates selected\n if (validateDates()) {\n onApply({\n type: 'custom',\n startDate,\n endDate,\n });\n onClose();\n }\n }\n }, [selectedPeriod, startDate, endDate, onApply, onClose, validateDates]);\n\n // Handle clear\n const handleClear = useCallback(() => {\n onApply(DEFAULT_PERIOD_SELECTION);\n onClose();\n }, [onApply, onClose]);\n\n // Check if apply button should be enabled\n const canApply = selectedPeriod || (startDate && endDate);\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title=\"Selecionar período\"\n size=\"sm\"\n footer={\n <div className=\"flex items-center justify-end gap-3 w-full\">\n <Button variant=\"link\" onClick={handleClear}>\n Limpar filtros\n </Button>\n <Button variant=\"outline\" onClick={onClose}>\n Cancelar\n </Button>\n <Button onClick={handleApply} disabled={!canApply}>\n Aplicar\n </Button>\n </div>\n }\n >\n <div className=\"flex flex-col gap-6\">\n {/* Fixed period selector */}\n <div className=\"flex flex-col gap-2\">\n <Text className=\"text-sm font-medium text-text-700\">\n Selecionar período fixo\n </Text>\n <Select value={selectedPeriod} onValueChange={handlePeriodChange}>\n <SelectTrigger className=\"w-full\">\n <SelectValue placeholder=\"Selecione um período\" />\n </SelectTrigger>\n <SelectContent>\n {PERIOD_TABS.map((tab) => (\n <SelectItem key={tab.value} value={tab.value}>\n {tab.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Divider */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex-1 h-px bg-border-200\" />\n <span className=\"text-sm text-text-400\">Ou</span>\n <div className=\"flex-1 h-px bg-border-200\" />\n </div>\n\n {/* Custom date range */}\n <div className=\"flex flex-col gap-2\">\n <Text className=\"text-sm font-medium text-text-700\">\n Escolher data\n </Text>\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"flex flex-col gap-1\">\n <Text className=\"text-xs text-text-500\">Data inicial</Text>\n <input\n type=\"date\"\n value={startDate}\n onChange={(e) => handleStartDateChange(e.target.value)}\n className={cn(\n 'w-full h-10 px-3 rounded-lg border text-sm',\n 'border-border-300 text-text-700',\n 'focus:outline-none focus:ring-2 focus:ring-indicator-info focus:border-transparent',\n 'placeholder:text-text-400'\n )}\n />\n </div>\n <div className=\"flex flex-col gap-1\">\n <Text className=\"text-xs text-text-500\">Data final</Text>\n <input\n type=\"date\"\n value={endDate}\n onChange={(e) => handleEndDateChange(e.target.value)}\n className={cn(\n 'w-full h-10 px-3 rounded-lg border text-sm',\n 'border-border-300 text-text-700',\n 'focus:outline-none focus:ring-2 focus:ring-indicator-info focus:border-transparent',\n 'placeholder:text-text-400'\n )}\n />\n </div>\n </div>\n {dateError && (\n <p className=\"text-sm text-indicator-error mt-1\">{dateError}</p>\n )}\n </div>\n </div>\n </Modal>\n );\n}\n\nexport default PeriodSelectorModal;\n","import { useMemo } from 'react';\nimport TableProvider, {\n type ColumnConfig,\n} from '../TableProvider/TableProvider';\n\n/**\n * Props for the PrintableUsersTable component.\n */\nexport interface PrintableUsersTableProps<T extends Record<string, unknown>> {\n /** Row data shared by both printed tables */\n readonly data: T[];\n /** Full ordered list of columns as displayed on screen */\n readonly columns: ColumnConfig<T>[];\n /**\n * Column index at which to split. The first printed table renders columns\n * [0, splitAfter] inclusive, the second renders the anchor column followed\n * by columns [splitAfter + 1, end).\n *\n * Example: columns.length = 8, splitAfter = 3 → first table shows cols 0..3,\n * second shows anchor + cols 4..7.\n */\n readonly splitAfter: number;\n /** Key of the anchor column (usually \"name\") repeated as first column of the second half */\n readonly anchorColumnKey: string;\n}\n\n/**\n * Inline CSS rules applied only during print to make table content fit on\n * the page. Consumers should configure `@page { size: ... }` themselves\n * because page orientation is a page-level concern that can clash with\n * other print rules in the host application.\n */\nconst PRINT_STYLES = `\n@media print {\n [data-print-table] .overflow-x-auto,\n [data-print-table] div[class*=\"overflow-x-auto\"] {\n overflow: visible !important;\n }\n\n [data-print-table] table {\n font-size: 9px !important;\n width: 100% !important;\n table-layout: fixed !important;\n page-break-inside: auto;\n }\n\n [data-print-table] table th,\n [data-print-table] table td {\n white-space: normal !important;\n word-break: break-word !important;\n overflow-wrap: anywhere !important;\n padding: 4px 6px !important;\n min-width: 0 !important;\n max-width: none !important;\n }\n\n [data-print-table] table tr {\n page-break-inside: avoid;\n page-break-after: auto;\n }\n\n [data-print-table] table thead {\n display: table-header-group;\n }\n}\n`;\n\n/**\n * Renders the same row data as two stacked tables optimized for PDF print.\n *\n * On screen the component is hidden (`hidden print:block`); during\n * `window.print()` it replaces the single scrollable TableProvider, so wide\n * tables no longer clip columns to the viewport width. An inline `<style>`\n * tag embeds the `@media print` rules so the component is drop-in with no\n * extra CSS wiring in the consumer.\n *\n * The anchor column (by convention \"name\") is repeated as the first column\n * of the second printed table so the reader can match rows across halves.\n *\n * NOTE: Set `@page { size: A4 landscape }` (or equivalent) in the host app\n * global print stylesheet — this component does not define page orientation\n * to avoid conflicts with other print rules.\n */\nfunction PrintableUsersTable<T extends Record<string, unknown>>({\n data,\n columns,\n splitAfter,\n anchorColumnKey,\n}: PrintableUsersTableProps<T>) {\n const { firstHeaders, secondHeaders } = useMemo(() => {\n const normalizedSplitAfter = Math.min(\n Math.max(splitAfter, 0),\n Math.max(columns.length - 1, 0)\n );\n const anchor = columns.find((col) => col.key === anchorColumnKey);\n const first = columns.slice(0, normalizedSplitAfter + 1);\n const rest = columns.slice(normalizedSplitAfter + 1);\n const second =\n anchor &&\n rest.length > 0 &&\n !rest.some((col) => col.key === anchorColumnKey)\n ? [anchor, ...rest]\n : rest;\n return { firstHeaders: first, secondHeaders: second };\n }, [columns, splitAfter, anchorColumnKey]);\n\n if (columns.length === 0) {\n return null;\n }\n\n return (\n <div className=\"hidden print:block space-y-4\" data-print-table>\n <style>{PRINT_STYLES}</style>\n <TableProvider<T>\n data={data}\n headers={firstHeaders}\n variant=\"borderless\"\n containerClassName=\"bg-background rounded-xl p-4 space-y-2\"\n />\n {secondHeaders.length > 0 && (\n <TableProvider<T>\n data={data}\n headers={secondHeaders}\n variant=\"borderless\"\n containerClassName=\"bg-background rounded-xl p-4 space-y-2\"\n />\n )}\n </div>\n );\n}\n\nexport default PrintableUsersTable;\n","import Text from '../Text/Text';\nimport { TableProvider } from '../TableProvider';\nimport Menu, { MenuContent, MenuItem } from '../Menu/Menu';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport { PeriodSelector } from '../PeriodSelector';\nimport { GeneralOverviewSection } from '../GeneralOverviewSection';\nimport { AreaKnowledgeSelector, ESSAY_AREA_ID } from '../AreaKnowledgeSelector';\nimport { SimulatedSubjectMenu } from '../SimulatedSubjectMenu';\nimport { SimulatedStudentRanking } from '../SimulatedStudentRanking';\nimport { PerformanceDistributionChart } from '../PerformanceDistributionChart';\nimport { SimulatedStudentDetailsModal } from '../SimulatedStudentDetailsModal';\nimport { SimulatedContentDetailsModal } from '../SimulatedContentDetailsModal';\nimport { EssayCompetenciesTable } from '../EssayCompetencies';\nimport { EssayStudentDetailsModal } from '../EssayStudentDetailsModal';\nimport { SimulatedFiltersModal } from '../SimulatedFilters';\nimport type { SimulatedStudentItem } from '../SimulatedStudentsOverview/types';\nimport {\n isStudentsData,\n isClassesData,\n isMunicipalitiesData,\n} from '../SimulatedStudentsOverview/utils';\nimport type { SimulatedContentItem } from '../SimulatedContentsPerformance/types';\nimport { SimulatedViewTab, type SimulatedPerformanceViewProps } from './types';\nimport { ReactNode, useMemo } from 'react';\n\n/**\n * Reusable section wrapper handling loading/error states\n */\nfunction SectionContent({\n loading,\n error,\n minHeight,\n children,\n}: {\n readonly loading: boolean;\n readonly error: string | null;\n readonly minHeight: string;\n readonly children: ReactNode;\n}) {\n if (loading) {\n return <SkeletonCard className={minHeight} />;\n }\n\n if (error) {\n return (\n <div\n className={`flex items-center justify-center ${minHeight} bg-background border border-border-50 rounded-xl p-5`}\n >\n <Text size=\"sm\" className=\"text-text-500\">\n {error}\n </Text>\n </div>\n );\n }\n\n return <>{children}</>;\n}\n\n/**\n * Componente de apresentação para a view de Simulados.\n * Renderiza a UI com base nos dados e handlers do hook useSimulatedPerformance.\n */\nexport function SimulatedPerformanceView({\n api,\n period,\n scoreType,\n selectedAreaKnowledgeId,\n selectedAreaRelatedIds,\n selectedSubjectId,\n simulatedViewTab,\n isEssaySelected,\n filters,\n aggregationType,\n generalOverview,\n aggregatedOverview,\n studentsOverview,\n contentsPerformance,\n handlePeriodChange,\n handleAreaKnowledgeChange,\n handleSubjectChange,\n handleViewTabChange,\n handleFiltersApply,\n handleStudentsParamsChange,\n handleContentsParamsChange,\n handleStudentRowClick,\n handleContentRowClick,\n studentModal,\n contentModal,\n filtersModal,\n studentsTableColumns,\n contentsTableColumns,\n noSearchImage,\n}: Readonly<SimulatedPerformanceViewProps>) {\n // Transform aggregated data to ranking format based on aggregation type\n const { highlightItems, attentionItems, highlightTitle, attentionTitle } =\n useMemo(() => {\n const data = aggregatedOverview.data;\n\n if (isStudentsData(data, aggregationType)) {\n return {\n highlightItems:\n data.topHighlights?.map((s, index) => ({\n position: index + 1,\n name: s.name,\n average: s.average,\n userInstitutionId: s.userInstitutionId,\n })) || [],\n attentionItems:\n data.topDifficulties?.map((s, index) => ({\n position: index + 1,\n name: s.name,\n average: s.average,\n userInstitutionId: s.userInstitutionId,\n })) || [],\n highlightTitle: 'Estudantes em destaque',\n attentionTitle: 'Estudantes com maior dificuldade',\n };\n }\n\n if (isClassesData(data, aggregationType)) {\n return {\n highlightItems:\n data.topHighlights?.map((c, index) => ({\n position: index + 1,\n name: `${c.className} - ${c.schoolName}`,\n average: c.average,\n subtitle: `${c.studentCount} estudantes`,\n userInstitutionId: c.classId,\n })) || [],\n attentionItems:\n data.topDifficulties?.map((c, index) => ({\n position: index + 1,\n name: `${c.className} - ${c.schoolName}`,\n average: c.average,\n subtitle: `${c.studentCount} estudantes`,\n userInstitutionId: c.classId,\n })) || [],\n highlightTitle: 'Turmas em destaque',\n attentionTitle: 'Turmas com maior dificuldade',\n };\n }\n\n if (isMunicipalitiesData(data, aggregationType)) {\n return {\n highlightItems:\n data.topHighlights?.map((m, index) => ({\n position: index + 1,\n name: `${m.municipality} - ${m.state}`,\n average: m.average,\n subtitle: `${m.schoolCount} escolas, ${m.studentCount} estudantes`,\n })) || [],\n attentionItems:\n data.topDifficulties?.map((m, index) => ({\n position: index + 1,\n name: `${m.municipality} - ${m.state}`,\n average: m.average,\n subtitle: `${m.schoolCount} escolas, ${m.studentCount} estudantes`,\n })) || [],\n highlightTitle: 'Municípios em destaque',\n attentionTitle: 'Municípios com maior dificuldade',\n };\n }\n\n return {\n highlightItems: [],\n attentionItems: [],\n highlightTitle: 'Em destaque',\n attentionTitle: 'Com maior dificuldade',\n };\n }, [aggregatedOverview.data, aggregationType]);\n return (\n <>\n {/* Period tabs */}\n <PeriodSelector\n value={period}\n onChange={handlePeriodChange}\n excludeValues={['3_MONTHS']}\n />\n\n {/* General Overview Section */}\n <GeneralOverviewSection\n data={generalOverview.data}\n loading={generalOverview.loading}\n error={generalOverview.error}\n scoreType={scoreType}\n />\n\n {/* Filters: Area Knowledge and Subject */}\n <div className=\"flex flex-col gap-4 w-full\">\n {/* Area Knowledge Selector */}\n <AreaKnowledgeSelector\n areas={generalOverview.data?.areas || []}\n selectedAreaId={selectedAreaKnowledgeId}\n onAreaChange={handleAreaKnowledgeChange}\n loading={generalOverview.loading}\n includeEssay={!!generalOverview.data?.essay}\n />\n\n {/* Subject Menu - Hidden when essay is selected */}\n {selectedAreaKnowledgeId !== ESSAY_AREA_ID && (\n <SimulatedSubjectMenu\n api={api}\n areaKnowledgeId={selectedAreaKnowledgeId}\n relatedIds={selectedAreaRelatedIds}\n selectedSubjectId={selectedSubjectId}\n onSubjectChange={handleSubjectChange}\n loading={studentsOverview.loading}\n />\n )}\n </div>\n\n {/* Ranking Section (adapts to aggregation type: students/classes/municipalities) */}\n <SectionContent\n loading={aggregatedOverview.loading}\n error={aggregatedOverview.error}\n minHeight=\"min-h-[200px]\"\n >\n <SimulatedStudentRanking\n highlightStudents={highlightItems}\n attentionStudents={attentionItems}\n highlightTitle={highlightTitle}\n attentionTitle={attentionTitle}\n scoreType={scoreType}\n />\n </SectionContent>\n\n {/* View tabs: Students vs Skills */}\n <Menu\n defaultValue={SimulatedViewTab.STUDENTS}\n value={simulatedViewTab}\n variant=\"breadcrumb\"\n className=\"px-0!\"\n onValueChange={handleViewTabChange}\n >\n <MenuContent className=\"px-0!\">\n <MenuItem variant=\"menu2\" value={SimulatedViewTab.STUDENTS}>\n Desempenho por estudante\n </MenuItem>\n <MenuItem variant=\"menu2\" value={SimulatedViewTab.SKILLS}>\n Desempenho por habilidade\n </MenuItem>\n </MenuContent>\n </Menu>\n\n {/* Students view content */}\n {simulatedViewTab === SimulatedViewTab.STUDENTS && (\n <>\n {/* Performance Distribution Chart */}\n {!studentsOverview.error && (\n <PerformanceDistributionChart\n counters={studentsOverview.data?.counters}\n totalStudents={studentsOverview.data?.totalStudents}\n loading={studentsOverview.loading}\n />\n )}\n\n {/* Students Table */}\n {studentsOverview.error ? (\n <div className=\"bg-background border border-border-50 rounded-xl p-5\">\n <Text\n as=\"h3\"\n size=\"lg\"\n weight=\"bold\"\n className=\"text-text-950 mb-4\"\n >\n Desempenho por estudante\n </Text>\n <div className=\"flex items-center justify-center py-8 bg-error-50 rounded-lg\">\n <Text size=\"sm\" className=\"text-error-500\">\n {studentsOverview.error}\n </Text>\n </div>\n </div>\n ) : (\n <div\n className={`bg-background border border-border-50 rounded-xl p-5 transition-opacity duration-200 ${\n studentsOverview.isRefreshing\n ? 'opacity-60 pointer-events-none'\n : ''\n }`}\n >\n <TableProvider<SimulatedStudentItem>\n data={studentsOverview.data?.students?.data || []}\n headers={studentsTableColumns}\n variant=\"borderless\"\n loading={studentsOverview.loading}\n enableSearch\n enableTableSort\n enablePagination\n enableRowClick\n onRowClick={handleStudentRowClick}\n onParamsChange={handleStudentsParamsChange}\n rowKey=\"studentId\"\n paginationConfig={{\n itemLabel: 'estudantes',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage:\n studentsOverview.data?.students?.limit || 10,\n totalItems: studentsOverview.data?.students?.total || 0,\n }}\n searchPlaceholder=\"Buscar estudante\"\n noSearchResultState={\n noSearchImage ? { image: noSearchImage } : undefined\n }\n headerContent={\n <Text\n as=\"h3\"\n size=\"lg\"\n weight=\"bold\"\n className=\"text-text-950\"\n >\n Desempenho por estudante\n </Text>\n }\n />\n </div>\n )}\n </>\n )}\n\n {/* Skills/Contents view content */}\n {simulatedViewTab === SimulatedViewTab.SKILLS &&\n !isEssaySelected &&\n (contentsPerformance.error ? (\n <div className=\"bg-background border border-border-50 rounded-xl p-5\">\n <Text\n as=\"h3\"\n size=\"lg\"\n weight=\"bold\"\n className=\"text-text-950 mb-4\"\n >\n Desempenho por habilidade\n </Text>\n <div className=\"flex items-center justify-center py-8 bg-error-50 rounded-lg\">\n <Text size=\"sm\" className=\"text-error-500\">\n {contentsPerformance.error}\n </Text>\n </div>\n </div>\n ) : (\n <div\n className={`bg-background border border-border-50 rounded-xl p-5 transition-opacity duration-200 ${\n contentsPerformance.isRefreshing\n ? 'opacity-60 pointer-events-none'\n : ''\n }`}\n >\n <TableProvider<SimulatedContentItem>\n data={contentsPerformance.data?.data || []}\n headers={contentsTableColumns}\n variant=\"borderless\"\n loading={contentsPerformance.loading}\n enableSearch\n enableTableSort\n enablePagination\n enableRowClick\n onRowClick={handleContentRowClick}\n onParamsChange={handleContentsParamsChange}\n rowKey=\"contentId\"\n paginationConfig={{\n itemLabel: 'habilidades',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: contentsPerformance.data?.limit || 10,\n totalItems: contentsPerformance.data?.total || 0,\n }}\n searchPlaceholder=\"Buscar habilidade\"\n noSearchResultState={\n noSearchImage ? { image: noSearchImage } : undefined\n }\n headerContent={\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Desempenho por habilidade\n </Text>\n }\n />\n </div>\n ))}\n\n {/* Essay Competencies Table */}\n {simulatedViewTab === SimulatedViewTab.SKILLS && isEssaySelected && (\n <div className=\"bg-background border border-border-50 rounded-xl p-5\">\n <EssayCompetenciesTable\n api={api}\n period={period}\n schoolIds={filters.schoolIds}\n schoolYearIds={filters.schoolYearIds}\n classIds={filters.classIds}\n />\n </div>\n )}\n\n {/* Simulated Student Details Modal */}\n {isEssaySelected ? (\n <EssayStudentDetailsModal\n api={api}\n isOpen={studentModal.isOpen}\n onClose={studentModal.close}\n userInstitutionId={studentModal.student?.userInstitutionId ?? null}\n studentName={studentModal.student?.name}\n period={period}\n schoolIds={filters.schoolIds}\n schoolYearIds={filters.schoolYearIds}\n classIds={filters.classIds}\n />\n ) : (\n <SimulatedStudentDetailsModal\n api={api}\n isOpen={studentModal.isOpen}\n onClose={studentModal.close}\n simulationType=\"enem-1\"\n userInstitutionId={studentModal.student?.userInstitutionId ?? null}\n studentName={studentModal.student?.name}\n period={period}\n />\n )}\n\n {/* Simulated Content Details Modal */}\n <SimulatedContentDetailsModal\n api={api}\n isOpen={contentModal.isOpen}\n onClose={contentModal.close}\n activityFilters={{\n types: ['SIMULADO'],\n subtypes: [],\n statuses: ['CONCLUIDA'],\n }}\n contentId={contentModal.content?.contentId ?? null}\n contentName={contentModal.content?.contentName}\n period={period}\n filters={filters}\n />\n\n {/* Simulados Filters Modal */}\n <SimulatedFiltersModal\n isOpen={filtersModal.isOpen}\n onClose={filtersModal.close}\n onApply={handleFiltersApply}\n initialFilters={filters}\n api={api}\n />\n </>\n );\n}\n","import { useCallback, useMemo, useState } from 'react';\nimport { UserCircleIcon } from '@phosphor-icons/react/dist/csr/UserCircle';\nimport { PageContainer } from '../PageContainer/PageContainer';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { TableProvider } from '../TableProvider';\nimport type { ColumnConfig, TableParams } from '../TableProvider';\nimport type { FilterConfig } from '../Filter/useTableFilter';\nimport { useUserAccessData } from '../SimulatedFilters/hooks';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseSimulations } from '../../hooks/useSimulations';\nimport type { SimulationsStudentItem } from '../../types/simulations';\nimport { SimulationsDetailModal } from './SimulationsDetailModal';\n\nexport interface SimulationsPageProps {\n /** API client used to fetch students and simulations */\n readonly api: BaseApiClient;\n /** Image shown when a search returns no results */\n readonly noSearchImage?: string;\n}\n\nconst DEFAULT_LIMIT = 10;\n\n/**\n * Teacher-facing simulations page: a paginated list of students with how many\n * simulations each answered, plus a \"Ver simulados\" action that opens a nested\n * detail modal.\n */\nexport function SimulationsPage({ api, noSearchImage }: SimulationsPageProps) {\n const useSimulations = useMemo(() => createUseSimulations(api), [api]);\n const { fetchStudents } = useSimulations();\n const { classes } = useUserAccessData(api);\n\n const [students, setStudents] = useState<SimulationsStudentItem[]>([]);\n const [total, setTotal] = useState(0);\n const [loading, setLoading] = useState(true);\n const [selected, setSelected] = useState<{\n userInstitutionId: string;\n name: string;\n } | null>(null);\n\n // Build a single-category \"Turma\" filter so the TableProvider's native filter\n // modal handles multi-selection. The category key is `classIds`, so the\n // selected ids surface in TableParams as `params.classIds`.\n const initialFilters = useMemo<FilterConfig[]>(\n () => [\n {\n key: 'turma',\n label: 'Turma',\n categories: [\n {\n key: 'classIds',\n label: 'Turma',\n selectedIds: [],\n itens: classes.map((c) => ({ id: c.id, name: c.name })),\n },\n ],\n },\n ],\n [classes]\n );\n\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n const classIds = Array.isArray(params.classIds)\n ? (params.classIds as string[])\n : undefined;\n setLoading(true);\n fetchStudents({\n page: params.page,\n limit: params.limit,\n search: params.search?.trim() || undefined,\n classIds: classIds?.length ? classIds : undefined,\n })\n .then((result) => {\n setStudents(result.data);\n setTotal(result.total);\n })\n .catch(() => {\n setStudents([]);\n setTotal(0);\n })\n .finally(() => setLoading(false));\n },\n [fetchStudents]\n );\n\n const columns = useMemo<ColumnConfig<SimulationsStudentItem>[]>(\n () => [\n {\n key: 'name',\n label: 'Estudante',\n render: (_value, row) => (\n <span className=\"flex items-center gap-2\">\n <UserCircleIcon size={24} weight=\"fill\" className=\"text-info-700\" />\n <Text size=\"sm\" className=\"text-text-950\">\n {row.name}\n </Text>\n </span>\n ),\n },\n {\n key: 'class',\n label: 'Turma',\n render: (_value, row) => (\n <Text size=\"sm\" className=\"text-text-900\">\n {row.class ?? '-'}\n </Text>\n ),\n },\n {\n key: 'simulationsCount',\n label: 'Simulados',\n render: (_value, row) => (\n <Text size=\"sm\" className=\"text-text-900\">\n {row.simulationsCount}\n </Text>\n ),\n },\n {\n key: 'actions',\n label: '',\n render: (_value, row) => (\n <div className=\"flex justify-end\">\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() =>\n setSelected({\n userInstitutionId: row.userInstitutionId,\n name: row.name,\n })\n }\n >\n Ver simulados\n </Button>\n </div>\n ),\n },\n ],\n []\n );\n\n return (\n <PageContainer innerClassName=\"max-w-[1150px]\">\n <div className=\"mb-4 flex flex-col gap-1\">\n <Text size=\"2xl\" weight=\"bold\" className=\"text-text-950\">\n Simulados\n </Text>\n <Text size=\"sm\" className=\"text-text-600\">\n Veja o resultado de todos os simulados realizados por cada estudante\n </Text>\n </div>\n\n <TableProvider<SimulationsStudentItem>\n data={students}\n headers={columns}\n loading={loading}\n variant=\"borderless\"\n enableSearch\n enableFilters\n enablePagination\n initialFilters={initialFilters}\n rowKey=\"userInstitutionId\"\n searchPlaceholder=\"Buscar estudante\"\n onParamsChange={handleParamsChange}\n paginationConfig={{\n itemLabel: 'estudantes',\n itemsPerPageOptions: [10, 20, 50],\n defaultItemsPerPage: DEFAULT_LIMIT,\n totalItems: total,\n }}\n noSearchResultState={{ image: noSearchImage }}\n />\n\n <SimulationsDetailModal\n api={api}\n isOpen={selected !== null}\n onClose={() => setSelected(null)}\n student={selected}\n />\n </PageContainer>\n );\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { UserCircleIcon } from '@phosphor-icons/react/dist/csr/UserCircle';\nimport Modal from '../Modal/Modal';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport TextArea from '../TextArea/TextArea';\nimport { CardAccordation } from '../Accordation';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport { StatCard } from '../shared/StatCard';\nimport { AlternativesList } from '../Alternative/Alternative';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nimport { OptionStatus } from '../../enums/Options';\nimport {\n getQuestionStatusBadgeConfig,\n QUESTION_STATUS,\n type QuestionStatus,\n} from '../../utils/studentActivityCorrection';\nimport { cn } from '../../utils/utils';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseSimulations } from '../../hooks/useSimulations';\nimport type {\n SimulationsListData,\n SimulationDetailData,\n SimulationDetailQuestion,\n StudentSimulationItem,\n NoteData,\n} from '../../types/simulations';\n\nexport interface SimulationsDetailModalProps {\n /** API client used to fetch the student's simulations */\n readonly api: BaseApiClient;\n readonly isOpen: boolean;\n readonly onClose: () => void;\n /** The student whose simulations are shown (null closes the modal) */\n readonly student: { userInstitutionId: string; name: string } | null;\n}\n\ninterface DetailState {\n loading: boolean;\n error: string | null;\n data: SimulationDetailData | null;\n}\n\ninterface NoteState {\n loading: boolean;\n data: NoteData | null;\n}\n\n/** Map the simulation question status to the shared correction status. */\nconst QUESTION_STATUS_MAP: Record<\n SimulationDetailQuestion['status'],\n QuestionStatus\n> = {\n CORRECT: QUESTION_STATUS.CORRETA,\n INCORRECT: QUESTION_STATUS.INCORRETA,\n BLANK: QUESTION_STATUS.EM_BRANCO,\n};\n\n// ---------------------------------------------------------------------------\n// Level 2 — Question (reuses the shared alternatives renderer + status badge)\n// ---------------------------------------------------------------------------\n\nfunction QuestionItem({\n question,\n index,\n}: {\n readonly question: SimulationDetailQuestion;\n readonly index: number;\n}) {\n const badge = getQuestionStatusBadgeConfig(\n QUESTION_STATUS_MAP[question.status]\n );\n\n const alternatives = question.options.map((option) => {\n let status: OptionStatus;\n if (option.isCorrect) {\n status = OptionStatus.CORRECT;\n } else if (option.isSelected) {\n status = OptionStatus.INCORRECT;\n } else {\n status = OptionStatus.NEUTRAL;\n }\n return { label: option.option, value: option.id, status };\n });\n\n return (\n <CardAccordation\n value={question.questionId}\n trigger={\n <div className=\"flex flex-1 items-center justify-between gap-3 py-3\">\n <Text size=\"sm\" weight=\"bold\" className=\"text-text-950\">\n Questão {index + 1}\n </Text>\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',\n badge.bgColor,\n badge.textColor\n )}\n >\n {badge.label}\n </span>\n </div>\n }\n contentClassName=\"px-3 pb-3\"\n >\n <div className=\"flex flex-col gap-3\">\n <HtmlMathRenderer\n content={question.statement}\n className=\"text-sm text-text-800\"\n />\n <CardAccordation\n value={`${question.questionId}-options`}\n trigger={\n <div className=\"flex-1 py-2\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-950\">\n Alternativas\n </Text>\n </div>\n }\n contentClassName=\"px-3 pb-3\"\n >\n <AlternativesList\n mode=\"readonly\"\n layout=\"compact\"\n name={`question-${question.questionId}`}\n alternatives={alternatives}\n selectedValue={question.selectedOptionId ?? ''}\n />\n </CardAccordation>\n </div>\n </CardAccordation>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Note (\"Observação\")\n// ---------------------------------------------------------------------------\n\nfunction NoteRow({\n note,\n loading,\n onSave,\n}: {\n readonly note: NoteData | null;\n readonly loading: boolean;\n readonly onSave: (text: string) => Promise<void>;\n}) {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const startEditing = () => {\n setDraft(note?.note ?? '');\n setError(null);\n setEditing(true);\n };\n\n const handleSave = async () => {\n if (!draft.trim()) return;\n setSaving(true);\n setError(null);\n try {\n await onSave(draft.trim());\n setEditing(false);\n } catch {\n // Keep the editing UI open (draft preserved) and surface the failure.\n setError('Erro ao salvar a observação. Tente novamente.');\n } finally {\n setSaving(false);\n }\n };\n\n if (loading) {\n return <SkeletonCard className=\"h-14\" />;\n }\n\n if (editing) {\n return (\n <div className=\"flex flex-col gap-2 rounded-xl border border-border-200 p-3\">\n <TextArea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Escreva uma observação para este simulado\"\n rows={3}\n />\n {error && (\n <Text size=\"sm\" className=\"text-error-600\">\n {error}\n </Text>\n )}\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={() => setEditing(false)}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n size=\"small\"\n onClick={handleSave}\n disabled={saving || !draft.trim()}\n >\n Salvar\n </Button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-between gap-3 rounded-xl border border-border-200 p-3\">\n <div className=\"flex min-w-0 flex-col\">\n <Text size=\"sm\" weight=\"bold\" className=\"text-text-950\">\n Observação\n </Text>\n {note?.note && (\n <Text size=\"sm\" className=\"truncate text-text-700\">\n {note.note}\n </Text>\n )}\n </div>\n <Button variant=\"solid\" size=\"small\" onClick={startEditing}>\n {note?.note ? 'Editar' : 'Incluir'}\n </Button>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Level 1 — Simulation\n// ---------------------------------------------------------------------------\n\nfunction SimulationItem({\n simulation,\n index,\n expanded,\n onToggle,\n detail,\n note,\n onSaveNote,\n}: {\n readonly simulation: StudentSimulationItem;\n readonly index: number;\n readonly expanded: boolean;\n readonly onToggle: () => void;\n readonly detail: DetailState | undefined;\n readonly note: NoteState | undefined;\n readonly onSaveNote: (text: string) => Promise<void>;\n}) {\n return (\n <CardAccordation\n value={simulation.id}\n expanded={expanded}\n onToggleExpanded={onToggle}\n trigger={\n <div className=\"flex-1 py-4\">\n <Text weight=\"bold\" className=\"text-text-950\">\n {simulation.title?.trim()\n ? simulation.title.trim()\n : `Simulado ${index + 1}`}\n </Text>\n </div>\n }\n contentClassName=\"px-3 pb-4\"\n >\n {detail?.loading && <SkeletonCard className=\"h-40\" />}\n {detail?.error && (\n <Text size=\"sm\" className=\"text-error-600\">\n {detail.error}\n </Text>\n )}\n {detail?.data && (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex gap-3\">\n <StatCard\n label=\"Nº de questões corretas\"\n value={detail.data.counts.correct}\n variant=\"correct\"\n className=\"flex-1\"\n />\n <StatCard\n label=\"Nº de questões incorretas\"\n value={detail.data.counts.incorrect}\n variant=\"incorrect\"\n className=\"flex-1\"\n />\n <StatCard\n label=\"Nº de questões em branco\"\n value={detail.data.counts.blank}\n variant=\"blank\"\n className=\"flex-1\"\n />\n </div>\n\n <NoteRow\n note={note?.data ?? null}\n loading={note?.loading ?? false}\n onSave={onSaveNote}\n />\n\n <div className=\"flex flex-col gap-2\">\n <Text weight=\"bold\" className=\"text-text-950\">\n Respostas\n </Text>\n {detail.data.questions.map((question, qIndex) => (\n <QuestionItem\n key={question.questionId}\n question={question}\n index={qIndex}\n />\n ))}\n </div>\n </div>\n )}\n </CardAccordation>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Modal\n// ---------------------------------------------------------------------------\n\n/**\n * Modal that shows a single student's answered simulations as a nested\n * accordion (simulation -> question -> options), lazily loading each\n * simulation's detail when it is expanded. Reuses the shared StatCard and\n * AlternativesList components so the detail matches the activity correction UI.\n */\nexport function SimulationsDetailModal({\n api,\n isOpen,\n onClose,\n student,\n}: SimulationsDetailModalProps) {\n const useSimulations = useMemo(() => createUseSimulations(api), [api]);\n const {\n fetchStudentSimulations,\n fetchSimulationDetail,\n fetchNote,\n saveNote,\n } = useSimulations();\n\n const [list, setList] = useState<SimulationsListData | null>(null);\n const [listLoading, setListLoading] = useState(false);\n const [listError, setListError] = useState<string | null>(null);\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const [details, setDetails] = useState<Record<string, DetailState>>({});\n const [notes, setNotes] = useState<Record<string, NoteState>>({});\n\n // Guards async state updates against the modal unmounting mid-request.\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Bumped on every new session (open / student change) so in-flight responses\n // from a previous student are ignored instead of writing into the new one.\n const requestEpochRef = useRef(0);\n useEffect(() => {\n requestEpochRef.current += 1;\n }, [isOpen, student?.userInstitutionId]);\n\n const isStaleResponse = useCallback(\n (epoch: number) => !mountedRef.current || requestEpochRef.current !== epoch,\n []\n );\n\n useEffect(() => {\n if (!isOpen || !student) return;\n\n setList(null);\n setExpandedId(null);\n setDetails({});\n setNotes({});\n setListLoading(true);\n setListError(null);\n\n let active = true;\n fetchStudentSimulations(student.userInstitutionId)\n .then((data) => {\n if (active) setList(data);\n })\n .catch(() => {\n if (active) setListError('Erro ao carregar os simulados do estudante');\n })\n .finally(() => {\n if (active) setListLoading(false);\n });\n\n return () => {\n active = false;\n };\n }, [isOpen, student, fetchStudentSimulations]);\n\n const handleToggle = useCallback(\n (simulationId: string) => {\n if (!student) return;\n const requestEpoch = requestEpochRef.current;\n const next = expandedId === simulationId ? null : simulationId;\n setExpandedId(next);\n\n if (next && !details[simulationId]) {\n setDetails((prev) => ({\n ...prev,\n [simulationId]: { loading: true, error: null, data: null },\n }));\n fetchSimulationDetail(student.userInstitutionId, simulationId)\n .then((data) => {\n if (isStaleResponse(requestEpoch)) return;\n setDetails((prev) => ({\n ...prev,\n [simulationId]: { loading: false, error: null, data },\n }));\n })\n .catch(() => {\n if (isStaleResponse(requestEpoch)) return;\n setDetails((prev) => ({\n ...prev,\n [simulationId]: {\n loading: false,\n error: 'Erro ao carregar o simulado',\n data: null,\n },\n }));\n });\n\n setNotes((prev) => ({\n ...prev,\n [simulationId]: { loading: true, data: null },\n }));\n fetchNote(student.userInstitutionId, simulationId)\n .then((data) => {\n if (isStaleResponse(requestEpoch)) return;\n setNotes((prev) => ({\n ...prev,\n [simulationId]: { loading: false, data },\n }));\n })\n .catch(() => {\n if (isStaleResponse(requestEpoch)) return;\n setNotes((prev) => ({\n ...prev,\n [simulationId]: { loading: false, data: null },\n }));\n });\n }\n },\n [\n student,\n expandedId,\n details,\n fetchSimulationDetail,\n fetchNote,\n isStaleResponse,\n ]\n );\n\n const makeSaveNote = useCallback(\n (simulationId: string) => async (text: string) => {\n if (!student) return;\n const requestEpoch = requestEpochRef.current;\n const saved = await saveNote(\n student.userInstitutionId,\n simulationId,\n text\n );\n if (isStaleResponse(requestEpoch)) return;\n setNotes((prev) => ({\n ...prev,\n [simulationId]: { loading: false, data: saved },\n }));\n },\n [student, saveNote, isStaleResponse]\n );\n\n return (\n <Modal isOpen={isOpen} onClose={onClose} title=\"Simulados\" size=\"xl\">\n {student && (\n <div className=\"flex max-h-[70vh] flex-col gap-4 overflow-y-auto pr-1\">\n <div className=\"flex items-center justify-between gap-3\">\n <span className=\"flex items-center gap-2\">\n <UserCircleIcon\n size={24}\n weight=\"fill\"\n className=\"text-info-700\"\n />\n <Text weight=\"bold\" className=\"text-text-950\">\n {student.name}\n </Text>\n </span>\n <Text size=\"sm\" className=\"text-info-700\">\n {list?.student.simulationsAnswered ?? 0} simulados respondidos\n </Text>\n </div>\n\n {listLoading && <SkeletonCard className=\"h-20\" />}\n {listError && (\n <Text size=\"sm\" className=\"text-error-600\">\n {listError}\n </Text>\n )}\n {list?.simulations.data.length === 0 && !listLoading && (\n <Text size=\"sm\" className=\"text-text-600\">\n Este estudante ainda não respondeu nenhum simulado.\n </Text>\n )}\n\n {list && (\n <div className=\"flex flex-col gap-3\">\n {list.simulations.data.map((simulation, index) => (\n <SimulationItem\n key={simulation.id}\n simulation={simulation}\n index={index}\n expanded={expandedId === simulation.id}\n onToggle={() => handleToggle(simulation.id)}\n detail={details[simulation.id]}\n note={notes[simulation.id]}\n onSaveNote={makeSaveNote(simulation.id)}\n />\n ))}\n </div>\n )}\n </div>\n )}\n </Modal>\n );\n}\n","import { useRef, useEffect, useState } from 'react';\nimport { useReactToPrint } from 'react-to-print';\nimport QRCode from 'qrcode';\nimport {\n PageContainer,\n CardContainer,\n AnswerSheetCard,\n PrintStyles,\n} from './GabaritoCard';\n\nexport interface AnswerSheetPreviewProps {\n studentName: string;\n qrCodeUrl: string;\n totalQuestions: number;\n examTitle?: string;\n schoolName?: string;\n className?: string;\n onComplete?: () => void;\n}\n\nexport function AnswerSheetPreview({\n studentName,\n qrCodeUrl,\n totalQuestions,\n examTitle,\n schoolName,\n className,\n onComplete,\n}: Readonly<AnswerSheetPreviewProps>) {\n const contentRef = useRef<HTMLDivElement>(null);\n const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string>('');\n const printedRef = useRef(false);\n\n useEffect(() => {\n QRCode.toDataURL(qrCodeUrl, { width: 160 })\n .then(setQrCodeDataUrl)\n .catch(console.error);\n }, [qrCodeUrl]);\n\n const handlePrint = useReactToPrint({\n contentRef,\n documentTitle: ' ',\n pageStyle: `\n @page {\n size: A4 portrait;\n margin: 0mm !important;\n }\n @media print {\n * {\n -webkit-print-color-adjust: exact !important;\n print-color-adjust: exact !important;\n }\n html, body {\n width: 210mm !important;\n margin: 0mm !important;\n padding: 0mm !important;\n }\n }\n `,\n onAfterPrint: () => {\n if (onComplete) {\n onComplete();\n }\n },\n });\n\n useEffect(() => {\n if (!printedRef.current && qrCodeDataUrl) {\n printedRef.current = true;\n const timer = setTimeout(() => {\n handlePrint();\n }, 500);\n return () => clearTimeout(timer);\n }\n }, [qrCodeDataUrl, handlePrint]);\n\n return (\n <>\n <PrintStyles />\n <PageContainer>\n <CardContainer ref={contentRef}>\n <AnswerSheetCard\n studentName={studentName}\n qrCodeDataUrl={qrCodeDataUrl}\n totalQuestions={totalQuestions}\n examTitle={examTitle}\n schoolName={schoolName}\n className={className}\n />\n </CardContainer>\n </PageContainer>\n </>\n );\n}\n\nexport default AnswerSheetPreview;\n","import { useRef, useEffect, useState } from 'react';\nimport { useReactToPrint } from 'react-to-print';\nimport QRCode from 'qrcode';\nimport {\n PageContainer,\n CardContainer,\n AnswerSheetCard,\n PrintStyles,\n} from './GabaritoCard';\n\nexport interface AnswerSheetData {\n studentName: string;\n qrCodeUrl: string;\n totalQuestions: number;\n examTitle?: string;\n schoolName?: string;\n className?: string;\n}\n\nexport interface AnswerSheetsBatchPreviewProps {\n answerSheets: AnswerSheetData[];\n onComplete?: () => void;\n}\n\nexport function AnswerSheetsBatchPreview({\n answerSheets,\n onComplete,\n}: Readonly<AnswerSheetsBatchPreviewProps>) {\n const contentRef = useRef<HTMLDivElement>(null);\n const [qrCodeDataUrls, setQrCodeDataUrls] = useState<string[]>([]);\n const printedRef = useRef(false);\n\n useEffect(() => {\n const generateQRCodes = async () => {\n const urls = await Promise.all(\n answerSheets.map((sheet) =>\n QRCode.toDataURL(sheet.qrCodeUrl, { width: 160 })\n )\n );\n setQrCodeDataUrls(urls);\n };\n\n generateQRCodes();\n }, [answerSheets]);\n\n const handlePrint = useReactToPrint({\n contentRef,\n documentTitle: ' ',\n pageStyle: `\n @page {\n size: A4 portrait;\n margin: 0mm !important;\n }\n @media print {\n * {\n -webkit-print-color-adjust: exact !important;\n print-color-adjust: exact !important;\n }\n html, body {\n width: 210mm !important;\n margin: 0mm !important;\n padding: 0mm !important;\n }\n }\n `,\n onAfterPrint: () => {\n if (onComplete) {\n onComplete();\n }\n },\n });\n\n useEffect(() => {\n if (!printedRef.current && qrCodeDataUrls.length === answerSheets.length) {\n printedRef.current = true;\n const timer = setTimeout(() => {\n handlePrint();\n }, 500);\n return () => clearTimeout(timer);\n }\n }, [qrCodeDataUrls, answerSheets.length, handlePrint]);\n\n return (\n <>\n <PrintStyles />\n <PageContainer>\n <div ref={contentRef}>\n {answerSheets.map((sheet, index) => (\n <CardContainer key={sheet.studentName + index}>\n <AnswerSheetCard\n studentName={sheet.studentName}\n qrCodeDataUrl={qrCodeDataUrls[index] || ''}\n totalQuestions={sheet.totalQuestions}\n examTitle={sheet.examTitle}\n schoolName={sheet.schoolName}\n className={sheet.className}\n />\n </CardContainer>\n ))}\n </div>\n </PageContainer>\n </>\n );\n}\n\nexport default AnswerSheetsBatchPreview;\n","import { Menu, MenuContent, MenuItem } from '../Menu/Menu';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nexport interface ExamDetailsHeaderProps {\n examTitle: string;\n examDate: string;\n school: string;\n classroomName: string;\n createdAt: string;\n onBack: () => void;\n onDownloadExam: () => void;\n /** Custom label for the back breadcrumb item */\n backLabel?: string;\n /** Custom label for the download button */\n downloadLabel?: string;\n}\n\n/**\n * Header component for exam details page\n * Includes breadcrumb navigation, title, subtitle, and download button\n */\nexport const ExamDetailsHeader = ({\n examTitle,\n examDate,\n school,\n classroomName,\n createdAt,\n onBack,\n onDownloadExam,\n backLabel = 'Provas',\n downloadLabel = 'Baixar prova',\n}: ExamDetailsHeaderProps) => {\n return (\n <div className=\"flex flex-col gap-4\">\n {/* Breadcrumb */}\n <Menu\n value=\"current-page\"\n defaultValue=\"current-page\"\n variant=\"breadcrumb\"\n className=\"px-0!\"\n >\n <MenuContent variant=\"breadcrumb\">\n <MenuItem\n variant=\"breadcrumb\"\n value=\"provas\"\n onClick={onBack}\n separator\n >\n {backLabel}\n </MenuItem>\n <MenuItem variant=\"breadcrumb\" value=\"current-page\">\n {examTitle}\n </MenuItem>\n </MenuContent>\n </Menu>\n\n {/* Title + Download Button */}\n <div className=\"flex justify-between items-start gap-4\">\n <div className=\"flex flex-col gap-1\">\n <Text as=\"h1\" size=\"xl\" weight=\"bold\">\n {examTitle}\n </Text>\n <Text size=\"sm\" color=\"secondary\">\n Data da prova {examDate} • {school} • {classroomName} • Criada em{' '}\n {createdAt}\n </Text>\n </div>\n <Button\n action=\"primary\"\n onClick={onDownloadExam}\n iconLeft={<DownloadSimpleIcon size={18} />}\n >\n {downloadLabel}\n </Button>\n </div>\n </div>\n );\n};\n\nexport default ExamDetailsHeader;\n","import { StarIcon, MedalIcon, WarningCircleIcon } from '@phosphor-icons/react';\nimport Text from '../Text/Text';\n\nexport interface ExamStatsCardsProps {\n averageScore: number;\n mostCorrectQuestions: number[];\n mostIncorrectQuestions: number[];\n unansweredQuestions: number[];\n /** Custom title for the section */\n title?: string;\n}\n\n/**\n * Format question numbers for display\n * Example: [1, 2, 4] -> \"01, 02 e 04\"\n */\nexport const formatQuestions = (questions: number[]): string => {\n if (questions.length === 0) return '-';\n\n const formatted = questions.map((q) => q.toString().padStart(2, '0'));\n\n if (formatted.length === 1) return formatted[0];\n if (formatted.length === 2) return `${formatted[0]} e ${formatted[1]}`;\n\n const lastQuestion = formatted.pop();\n return `${formatted.join(', ')} e ${lastQuestion}`;\n};\n\n/**\n * Stats cards component for exam details\n * Displays average score and question statistics\n * Layout matches ActivityDetails from analytica-frontend-lib\n */\nexport const ExamStatsCards = ({\n averageScore,\n mostCorrectQuestions,\n mostIncorrectQuestions,\n unansweredQuestions,\n title = 'Resultados da atividade',\n}: ExamStatsCardsProps) => {\n return (\n <div className=\"flex flex-col gap-4\">\n <Text as=\"h2\" size=\"lg\" weight=\"semibold\">\n {title}\n </Text>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5\">\n {/* Average Score Card */}\n <div className=\"border border-border-50 rounded-xl py-4 px-3 flex flex-col items-center justify-center gap-1 bg-warning-background\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-warning-300\">\n <StarIcon size={16} className=\"text-white\" weight=\"regular\" />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-warning-600\">\n Média da Turma\n </Text>\n <Text className=\"text-xl font-bold text-warning-600\">\n {averageScore.toFixed(1)}\n </Text>\n </div>\n\n {/* Most Correct Questions Card */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-success-200\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-indicator-positive\">\n <MedalIcon size={16} className=\"text-text-950\" weight=\"regular\" />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-success-700\">\n Questões com mais acertos\n </Text>\n <Text className=\"text-xl font-bold text-success-700\">\n {formatQuestions(mostCorrectQuestions)}\n </Text>\n </div>\n\n {/* Most Incorrect Questions Card */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-error-100\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-indicator-negative\">\n <WarningCircleIcon\n size={16}\n className=\"text-white\"\n weight=\"regular\"\n />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-error-700\">\n Questões com mais erros\n </Text>\n <Text className=\"text-xl font-bold text-error-700\">\n {formatQuestions(mostIncorrectQuestions)}\n </Text>\n </div>\n\n {/* Unanswered Questions Card */}\n <div className=\"border border-border-50 rounded-xl py-2 px-3 flex flex-col items-center justify-center gap-1 bg-info-background\">\n <div className=\"w-[30px] h-[30px] rounded-2xl flex items-center justify-center bg-info-500\">\n <WarningCircleIcon\n size={16}\n className=\"text-white\"\n weight=\"regular\"\n />\n </div>\n <Text className=\"text-2xs font-bold uppercase text-center text-info-700\">\n Questões não respondidas\n </Text>\n <Text className=\"text-xl font-bold text-info-700\">\n {formatQuestions(unansweredQuestions)}\n </Text>\n </div>\n </div>\n </div>\n );\n};\n\nexport default ExamStatsCards;\n","import { useMemo } from 'react';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport TableProvider from '../TableProvider/TableProvider';\nimport type { TableParams } from '../TableProvider/TableProvider';\nimport type {\n ExamStudentTableItem,\n ExamDetailsPagination,\n} from '../../types/examDetails';\nimport { createExamStudentsTableColumns } from './examStudentsTableConfig';\n\nexport interface ExamStudentsTableProps {\n students: ExamStudentTableItem[];\n loading: boolean;\n pagination: ExamDetailsPagination;\n onParamsChange: (params: TableParams) => void;\n onDownloadAnswerSheet: (studentId: string) => void;\n onViewAnswers: (studentId: string) => void;\n onDownloadAllAnswerSheets: () => void;\n loadingStudentId: string | null;\n batchLoading: boolean;\n /** Custom title for the section */\n title?: string;\n /** Custom label for the download all button */\n downloadAllLabel?: string;\n /** Custom loading label for the download all button */\n downloadAllLoadingLabel?: string;\n /** Custom item label for pagination */\n itemLabel?: string;\n}\n\n/**\n * Students results table component for exam details\n * Uses TableProvider from analytica-frontend-lib with server-side pagination\n */\nexport const ExamStudentsTable = ({\n students,\n loading,\n pagination,\n onParamsChange,\n onDownloadAnswerSheet,\n onViewAnswers,\n onDownloadAllAnswerSheets,\n loadingStudentId,\n batchLoading,\n title = 'Resultados por aluno',\n downloadAllLabel = 'Baixar todos os gabaritos',\n downloadAllLoadingLabel = 'Carregando...',\n itemLabel = 'alunos',\n}: ExamStudentsTableProps) => {\n const columns = useMemo(\n () =>\n createExamStudentsTableColumns(\n onDownloadAnswerSheet,\n onViewAnswers,\n loadingStudentId\n ),\n [onDownloadAnswerSheet, onViewAnswers, loadingStudentId]\n );\n\n return (\n <div className=\"flex flex-col gap-4\">\n {/* Header with title and download all button */}\n <div className=\"flex justify-between items-center\">\n <Text as=\"h2\" size=\"lg\" weight=\"semibold\">\n {title}\n </Text>\n <Button\n action=\"primary\"\n onClick={onDownloadAllAnswerSheets}\n iconLeft={<DownloadSimpleIcon size={18} />}\n disabled={batchLoading}\n >\n {batchLoading ? downloadAllLoadingLabel : downloadAllLabel}\n </Button>\n </div>\n\n {/* Table using TableProvider with pagination */}\n <TableProvider\n data={students}\n headers={columns}\n loading={loading}\n variant=\"borderless\"\n enableSearch={false}\n enableFilters={false}\n enableTableSort={true}\n enablePagination={true}\n enableRowClick={false}\n paginationConfig={{\n itemLabel,\n defaultItemsPerPage: 10,\n totalItems: pagination.total,\n totalPages: pagination.totalPages,\n }}\n onParamsChange={onParamsChange}\n containerClassName=\"bg-background rounded-xl p-6\"\n />\n </div>\n );\n};\n\nexport default ExamStudentsTable;\n","import type { MouseEvent } from 'react';\nimport Badge from '../Badge/Badge';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { TruncatedText } from '../TruncatedText/TruncatedText';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport {\n StudentAnswerStatus,\n StudentAnswerDisplayStatus,\n type ExamStudentTableItem,\n} from '../../types/examDetails';\n\n/**\n * Map status to badge action type\n */\nexport const getExamStudentStatusBadgeAction = (\n status: StudentAnswerStatus\n): 'warning' | 'success' => {\n switch (status) {\n case StudentAnswerStatus.ANSWER_SHEET_RECEIVED:\n return 'success';\n case StudentAnswerStatus.AWAITING_ANSWER_SHEET:\n default:\n return 'warning';\n }\n};\n\n/**\n * Map status to display text\n */\nexport const getExamStudentStatusDisplayText = (\n status: StudentAnswerStatus\n): string => {\n switch (status) {\n case StudentAnswerStatus.ANSWER_SHEET_RECEIVED:\n return StudentAnswerDisplayStatus.ANSWER_SHEET_RECEIVED;\n case StudentAnswerStatus.AWAITING_ANSWER_SHEET:\n default:\n return StudentAnswerDisplayStatus.AWAITING_ANSWER_SHEET;\n }\n};\n\n/**\n * Create column configuration for exam students table\n * Requires callbacks to be passed for action buttons\n * @param onDownloadAnswerSheet - Callback for downloading answer sheet\n * @param onViewAnswers - Callback for viewing student answers\n * @param loadingStudentId - ID of the student whose gabarito is being loaded\n */\nexport const createExamStudentsTableColumns = (\n onDownloadAnswerSheet: (studentId: string) => void,\n onViewAnswers: (studentId: string) => void,\n loadingStudentId: string | null\n): ColumnConfig<ExamStudentTableItem>[] => [\n {\n key: 'studentName',\n label: 'Aluno',\n sortable: true,\n className: 'max-w-[220px]',\n render: (_value: unknown, row: ExamStudentTableItem) => (\n <div className=\"flex items-center gap-2 min-w-0\">\n <div className=\"w-8 h-8 rounded-full bg-warning-200 flex items-center justify-center shrink-0\">\n <Text as=\"span\" size=\"sm\" weight=\"bold\" color=\"text-warning-700\">\n {row.studentName.charAt(0).toUpperCase()}\n </Text>\n </div>\n <TruncatedText size=\"sm\">{row.studentName}</TruncatedText>\n </div>\n ),\n },\n {\n key: 'status',\n label: 'Status',\n sortable: true,\n render: (value: unknown) => {\n const status = value as StudentAnswerStatus;\n return (\n <Badge\n variant=\"solid\"\n action={getExamStudentStatusBadgeAction(status)}\n size=\"small\"\n >\n {getExamStudentStatusDisplayText(status)}\n </Badge>\n );\n },\n },\n {\n key: 'answerReceivedAt',\n label: 'Gabarito recebido em',\n sortable: true,\n render: (value: unknown) => (\n <Text as=\"span\" size=\"sm\">\n {(value as string) || '-'}\n </Text>\n ),\n },\n {\n key: 'score',\n label: 'Nota',\n sortable: true,\n render: (value: unknown) => (\n <Text as=\"span\" size=\"sm\">\n {value === null ? '-' : (value as number).toFixed(1)}\n </Text>\n ),\n },\n {\n key: 'downloadAnswerSheet',\n label: 'Gabarito',\n sortable: false,\n render: (_value: unknown, row: ExamStudentTableItem) => {\n const isLoading = loadingStudentId === row.studentId;\n return (\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"small\"\n disabled={isLoading}\n onClick={(e: MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n onDownloadAnswerSheet(row.studentId);\n }}\n >\n {isLoading ? 'Carregando...' : 'Baixar gabarito'}\n </Button>\n );\n },\n },\n {\n key: 'viewAnswers',\n label: 'Resultado',\n sortable: false,\n render: (_value: unknown, row: ExamStudentTableItem) => {\n const hasResponse =\n row.status === StudentAnswerStatus.ANSWER_SHEET_RECEIVED;\n return (\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"small\"\n disabled={!hasResponse}\n onClick={(e: MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n onViewAnswers(row.studentId);\n }}\n >\n Ver respostas\n </Button>\n );\n },\n },\n];\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { BaseApiClient } from '../../types/api';\nimport type { TableParams } from '../TableProvider/TableProvider';\nimport type {\n StudentActivityCorrectionData,\n QuestionsAnswersByStudentResponse,\n} from '../../utils/studentActivityCorrection';\nimport { convertApiResponseToCorrectionData } from '../../utils/studentActivityCorrection';\nimport CorrectActivityModal from '../CorrectActivityModal/CorrectActivityModal';\nimport { AnswerSheetPreview } from '../ExamPageLayout/GabaritoPreview';\nimport {\n AnswerSheetsBatchPreview,\n type AnswerSheetData,\n} from '../ExamPageLayout/GabaritosBatchPreview';\nimport { ExamDetailsHeader } from './ExamDetailsHeader';\nimport { ExamStatsCards } from './ExamStatsCards';\nimport { ExamStudentsTable } from './ExamStudentsTable';\nimport type {\n ExamStudentTableItem,\n ExamDetailsData,\n ExamDetailsPagination,\n} from '../../types/examDetails';\nimport { createUseQuestionsList } from '../../hooks/useQuestionsList';\nimport {\n useQuestionsPdfPrint,\n QuestionsPdfContent,\n} from '../QuestionsPdfGenerator/QuestionsPdfGenerator';\nimport type { PreviewQuestion } from '../ActivityPreview/ActivityPreview';\nimport type { Question } from '../../types/questions';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport Text from '../Text/Text';\n\n/**\n * Interface for single student answer sheet API response\n */\ninterface AnswerSheetResponse {\n data: {\n student: { id: string; name: string };\n activity: { id: string; title: string; totalQuestions: number };\n qrCodeUrl: string;\n schoolClass: string | null;\n };\n}\n\n/**\n * Interface for batch answer sheets API response\n */\ninterface BatchAnswerSheetsResponse {\n data: {\n exam: { id: string; title: string; totalQuestions: number };\n students: Array<{\n student: { id: string; name: string };\n qrCodeUrl: string;\n schoolClass: string | null;\n }>;\n };\n}\n\n/**\n * Interface for student correction API response\n */\ninterface StudentCorrectionResponse {\n data: QuestionsAnswersByStudentResponse['data'] & {\n studentId: string;\n studentName: string;\n observation: string | null;\n attachment: string | null;\n answerSheetImageUrl: string | null;\n };\n}\n\n/**\n * Props for the ExamDetailsPage component\n */\nexport interface ExamDetailsPageProps {\n /** Exam ID to display details for */\n examId: string;\n /** Institution ID for API calls */\n institutionId: string;\n /** API client instance */\n apiClient: BaseApiClient;\n /** Exam data fetched from API */\n examData: ExamDetailsData | null;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Pagination data */\n pagination: ExamDetailsPagination;\n /** Function to fetch exam details */\n fetchExamDetails: (\n examId: string,\n params: { page: number; limit: number }\n ) => void;\n /** Callback when back button is clicked */\n onBack: () => void;\n /** Number of students per page (default: 10) */\n studentsPerPage?: number;\n}\n\n/**\n * Loading skeleton for exam details\n */\nconst ExamDetailsSkeleton = () => (\n <div className=\"flex flex-col gap-6 p-6\">\n <SkeletonCard className=\"h-24\" />\n <SkeletonCard className=\"h-32\" />\n <SkeletonCard className=\"h-64\" />\n </div>\n);\n\n/**\n * Error state component\n */\nconst ExamDetailsError = ({\n error,\n onBack,\n}: {\n error: string;\n onBack: () => void;\n}) => (\n <div className=\"flex flex-col items-center justify-center gap-4 p-6 min-h-[400px]\">\n <Text size=\"lg\" color=\"secondary\">\n {error}\n </Text>\n <button\n onClick={onBack}\n className=\"text-primary-500 hover:text-primary-600 underline\"\n >\n Voltar para provas\n </button>\n </div>\n);\n\n/**\n * Reusable Exam Details Page component\n *\n * Displays exam information, statistics, and student results.\n * Includes PDF download, answer sheet generation, and correction modal.\n */\nexport const ExamDetailsPage = ({\n examId,\n institutionId,\n apiClient,\n examData,\n loading,\n error,\n pagination,\n fetchExamDetails,\n onBack,\n studentsPerPage = 10,\n}: ExamDetailsPageProps) => {\n const [currentPage, setCurrentPage] = useState(1);\n const [isInitialLoad, setIsInitialLoad] = useState(true);\n\n // Answer sheet preview states\n const [showAnswerSheetPreview, setShowAnswerSheetPreview] = useState(false);\n const [answerSheetPreviewData, setAnswerSheetPreviewData] =\n useState<AnswerSheetData | null>(null);\n const [showBatchAnswerSheetPreview, setShowBatchAnswerSheetPreview] =\n useState(false);\n const [batchAnswerSheetsData, setBatchAnswerSheetsData] = useState<\n AnswerSheetData[]\n >([]);\n\n // Loading states for answer sheet downloads\n const [loadingStudentId, setLoadingStudentId] = useState<string | null>(null);\n const [batchAnswerSheetLoading, setBatchAnswerSheetLoading] = useState(false);\n\n // Correction modal states\n const [showCorrectionModal, setShowCorrectionModal] = useState(false);\n const [correctionData, setCorrectionData] =\n useState<StudentActivityCorrectionData | null>(null);\n const [answerSheetImageUrl, setAnswerSheetImageUrl] = useState<string | null>(\n null\n );\n // Exam PDF download states\n const [examQuestions, setExamQuestions] = useState<PreviewQuestion[]>([]);\n const [isLoadingExamPdf, setIsLoadingExamPdf] = useState(false);\n const [shouldPrintExam, setShouldPrintExam] = useState(false);\n\n // Hook factory for fetching questions\n const hookFactoryRef = useRef<ReturnType<\n typeof createUseQuestionsList\n > | null>(null);\n hookFactoryRef.current ??= createUseQuestionsList(apiClient);\n const { fetchQuestionsByIds } = hookFactoryRef.current();\n\n // PDF print hook\n const { contentRef: examPdfRef, handlePrint: handlePrintExam } =\n useQuestionsPdfPrint(examQuestions);\n\n /**\n * Transform Question to PreviewQuestion format\n */\n const transformQuestionToPreview = useCallback(\n (question: Question): PreviewQuestion => {\n return {\n id: question.id,\n questionType: question.questionType,\n statement: question.statement,\n question: question.options\n ? {\n options: question.options.map((opt) => ({\n id: opt.id,\n option: opt.option,\n })),\n correctOptionIds: question.options\n .filter((opt) => opt.isCorrect)\n .map((opt) => opt.id),\n }\n : undefined,\n };\n },\n []\n );\n\n /**\n * Effect to trigger print when examQuestions are loaded and shouldPrintExam is true\n */\n useEffect(() => {\n if (\n shouldPrintExam &&\n examQuestions.length > 0 &&\n examPdfRef.current &&\n handlePrintExam\n ) {\n // Small delay to ensure content is rendered\n const timer = setTimeout(() => {\n handlePrintExam();\n setShouldPrintExam(false);\n setIsLoadingExamPdf(false);\n }, 100);\n return () => clearTimeout(timer);\n }\n }, [shouldPrintExam, examQuestions.length, examPdfRef, handlePrintExam]);\n\n /**\n * Fetch exam details on mount and when page changes\n */\n useEffect(() => {\n if (examId) {\n fetchExamDetails(examId, { page: currentPage, limit: studentsPerPage });\n if (isInitialLoad) {\n setIsInitialLoad(false);\n }\n }\n }, [examId, currentPage, fetchExamDetails, isInitialLoad, studentsPerPage]);\n\n /**\n * Handle table params change (pagination)\n */\n const handleParamsChange = useCallback((params: TableParams) => {\n if (params.page) {\n setCurrentPage(params.page);\n }\n }, []);\n\n /**\n * Transform students data for TableProvider\n */\n const studentsTableData: ExamStudentTableItem[] = useMemo(\n () =>\n examData?.students.map((student) => ({\n ...student,\n })) ?? [],\n [examData?.students]\n );\n\n /**\n * Download exam PDF\n * Fetches the exam questions and generates a printable PDF\n */\n const handleDownloadExam = useCallback(async () => {\n if (!examId || isLoadingExamPdf) return;\n\n setIsLoadingExamPdf(true);\n\n try {\n // Try to fetch questions from quiz endpoint first\n let questions: Question[] = [];\n\n try {\n const quizResponse = await apiClient.get<{\n data: { questions?: Question[]; questionIds?: string[] };\n }>(`/activities/${examId}/quiz`);\n\n if (quizResponse.data.data.questions) {\n questions = quizResponse.data.data.questions;\n } else if (quizResponse.data.data.questionIds) {\n questions = await fetchQuestionsByIds(\n quizResponse.data.data.questionIds\n );\n }\n } catch {\n // If quiz endpoint fails, try activity endpoint\n const activityResponse = await apiClient.get<{\n data: { questions?: Question[]; questionIds?: string[] };\n }>(`/activities/${examId}`);\n\n if (activityResponse.data.data.questions) {\n questions = activityResponse.data.data.questions;\n } else if (activityResponse.data.data.questionIds) {\n questions = await fetchQuestionsByIds(\n activityResponse.data.data.questionIds\n );\n }\n }\n\n if (questions.length === 0) {\n console.error('Nenhuma questão encontrada para esta prova');\n setIsLoadingExamPdf(false);\n return;\n }\n\n // Transform questions to PreviewQuestion format\n const previewQuestions = questions.map(transformQuestionToPreview);\n setExamQuestions(previewQuestions);\n setShouldPrintExam(true);\n } catch (err) {\n console.error('Erro ao baixar prova:', err);\n setIsLoadingExamPdf(false);\n }\n }, [\n examId,\n isLoadingExamPdf,\n apiClient,\n fetchQuestionsByIds,\n transformQuestionToPreview,\n ]);\n\n /**\n * Download student's answer sheet\n * Fetches the answer sheet data and displays the AnswerSheetPreview component\n */\n const handleDownloadAnswerSheet = useCallback(\n async (studentId: string) => {\n if (!examId || !institutionId) return;\n\n setLoadingStudentId(studentId);\n\n try {\n const studentFrontendUrl = globalThis.location.origin;\n\n const response = await apiClient.get<AnswerSheetResponse>(\n `/activities/${examId}/answer-sheet/${studentId}`,\n { params: { studentFrontendUrl, institutionId } }\n );\n\n const { student, activity, qrCodeUrl, schoolClass } =\n response.data.data;\n const [school, className] = schoolClass?.split(' - ') || ['', ''];\n\n setAnswerSheetPreviewData({\n studentName: student.name,\n qrCodeUrl,\n totalQuestions: activity.totalQuestions,\n examTitle: examData?.title,\n schoolName: school || undefined,\n className: className || undefined,\n });\n setShowAnswerSheetPreview(true);\n } catch (err) {\n console.error('Erro ao gerar gabarito:', err);\n } finally {\n setLoadingStudentId(null);\n }\n },\n [examId, institutionId, apiClient, examData?.title]\n );\n\n /**\n * View student's answers\n * Fetches correction data and opens the correction modal\n */\n const handleViewAnswers = useCallback(\n async (studentId: string) => {\n if (!examId) return;\n\n try {\n const response = await apiClient.get<StudentCorrectionResponse>(\n `/exams/${examId}/students/${studentId}/correction`\n );\n\n const data = response.data.data;\n\n // Use the library's converter to transform API response\n const transformedData = convertApiResponseToCorrectionData(\n { data: { answers: data.answers, statistics: data.statistics } },\n data.studentId,\n data.studentName,\n data.observation ?? undefined,\n data.attachment ?? undefined\n );\n\n setCorrectionData(transformedData);\n setAnswerSheetImageUrl(data.answerSheetImageUrl);\n setShowCorrectionModal(true);\n } catch (err) {\n console.error('Erro ao buscar dados de correção:', err);\n }\n },\n [examId, apiClient]\n );\n\n /**\n * Close correction modal and reset state\n */\n const handleCloseCorrectionModal = useCallback(() => {\n setShowCorrectionModal(false);\n setCorrectionData(null);\n setAnswerSheetImageUrl(null);\n }, []);\n\n /**\n * Open scanned answer sheet in new tab\n */\n const handleViewScannedAnswerSheet = useCallback(() => {\n if (answerSheetImageUrl) {\n globalThis.open(answerSheetImageUrl, '_blank');\n }\n }, [answerSheetImageUrl]);\n\n /**\n * Download all answer sheets for all students\n * Fetches batch answer sheet data and displays AnswerSheetsBatchPreview component\n */\n const handleDownloadAllAnswerSheets = useCallback(async () => {\n if (!examId || !institutionId) return;\n\n setBatchAnswerSheetLoading(true);\n\n try {\n const studentFrontendUrl = globalThis.location.origin;\n\n const response = await apiClient.get<BatchAnswerSheetsResponse>(\n `/exams/${examId}/answer-sheets`,\n { params: { studentFrontendUrl, institutionId } }\n );\n\n const { exam, students } = response.data.data;\n\n const answerSheets: AnswerSheetData[] = students.map((s) => {\n const [school, className] = s.schoolClass?.split(' - ') || ['', ''];\n return {\n studentName: s.student.name,\n qrCodeUrl: s.qrCodeUrl,\n totalQuestions: exam.totalQuestions,\n examTitle: exam.title,\n schoolName: school || undefined,\n className: className || undefined,\n };\n });\n\n setBatchAnswerSheetsData(answerSheets);\n setShowBatchAnswerSheetPreview(true);\n } catch (err) {\n console.error('Erro ao gerar gabaritos:', err);\n } finally {\n setBatchAnswerSheetLoading(false);\n }\n }, [examId, institutionId, apiClient]);\n\n // Only show skeleton on initial load\n if (isInitialLoad && loading) {\n return <ExamDetailsSkeleton />;\n }\n\n if (error || !examData) {\n return (\n <ExamDetailsError\n error={error || 'Prova não encontrada'}\n onBack={onBack}\n />\n );\n }\n\n return (\n <div className=\"flex flex-col gap-6 p-6\">\n <ExamDetailsHeader\n examTitle={examData.title}\n examDate={examData.startDate}\n school={examData.school}\n classroomName={examData.className}\n createdAt={examData.createdAt}\n onBack={onBack}\n onDownloadExam={handleDownloadExam}\n />\n\n <ExamStatsCards\n averageScore={examData.stats.averageScore}\n mostCorrectQuestions={examData.stats.mostCorrectQuestions}\n mostIncorrectQuestions={examData.stats.mostIncorrectQuestions}\n unansweredQuestions={examData.stats.unansweredQuestions}\n />\n\n <ExamStudentsTable\n students={studentsTableData}\n loading={loading}\n pagination={pagination}\n onParamsChange={handleParamsChange}\n onDownloadAnswerSheet={handleDownloadAnswerSheet}\n onViewAnswers={handleViewAnswers}\n onDownloadAllAnswerSheets={handleDownloadAllAnswerSheets}\n loadingStudentId={loadingStudentId}\n batchLoading={batchAnswerSheetLoading}\n />\n\n {/* Hidden Answer Sheet Preview for printing - single student */}\n {showAnswerSheetPreview && answerSheetPreviewData && (\n <div style={{ position: 'fixed', top: '-9999px', left: '-9999px' }}>\n <AnswerSheetPreview\n studentName={answerSheetPreviewData.studentName}\n qrCodeUrl={answerSheetPreviewData.qrCodeUrl}\n totalQuestions={answerSheetPreviewData.totalQuestions}\n examTitle={answerSheetPreviewData.examTitle}\n schoolName={answerSheetPreviewData.schoolName}\n className={answerSheetPreviewData.className}\n onComplete={() => {\n setShowAnswerSheetPreview(false);\n setAnswerSheetPreviewData(null);\n }}\n />\n </div>\n )}\n\n {/* Hidden Batch Answer Sheets Preview for printing - all students */}\n {showBatchAnswerSheetPreview && batchAnswerSheetsData.length > 0 && (\n <div style={{ position: 'fixed', top: '-9999px', left: '-9999px' }}>\n <AnswerSheetsBatchPreview\n answerSheets={batchAnswerSheetsData}\n onComplete={() => {\n setShowBatchAnswerSheetPreview(false);\n setBatchAnswerSheetsData([]);\n }}\n />\n </div>\n )}\n\n {/* Hidden Exam PDF content for printing */}\n <div style={{ display: 'none' }}>\n <QuestionsPdfContent ref={examPdfRef} questions={examQuestions} />\n </div>\n\n {/* Exam Correction Modal - uses library component with scanned answer sheet support */}\n <CorrectActivityModal\n isOpen={showCorrectionModal}\n onClose={handleCloseCorrectionModal}\n data={correctionData}\n isViewOnly={true}\n answerSheetImageUrl={answerSheetImageUrl}\n onViewScannedAnswerSheet={handleViewScannedAnswerSheet}\n />\n </div>\n );\n};\n\nexport default ExamDetailsPage;\n"]}
|