analytica-frontend-lib 1.6.21 → 1.6.22

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.
Files changed (405) hide show
  1. package/dist/AccessReportModal/index.js +1 -1
  2. package/dist/AccessReportModal/index.mjs +1 -1
  3. package/dist/AccessibilityWidget/index.js +1 -1
  4. package/dist/AccessibilityWidget/index.mjs +1 -1
  5. package/dist/Accordation/index.js +1 -1
  6. package/dist/Accordation/index.mjs +1 -1
  7. package/dist/ActivitiesHistory/index.js +19 -19
  8. package/dist/ActivitiesHistory/index.mjs +18 -18
  9. package/dist/ActivityCardQuestionBanks/index.js +19 -19
  10. package/dist/ActivityCardQuestionBanks/index.mjs +18 -18
  11. package/dist/ActivityCardQuestionPreview/index.js +19 -19
  12. package/dist/ActivityCardQuestionPreview/index.mjs +18 -18
  13. package/dist/ActivityCreate/ActivityCreate.utils/index.js +3 -3
  14. package/dist/ActivityCreate/ActivityCreate.utils/index.mjs +2 -2
  15. package/dist/ActivityCreate/ActivityCreateContent/index.js +19 -19
  16. package/dist/ActivityCreate/ActivityCreateContent/index.mjs +18 -18
  17. package/dist/ActivityCreate/index.js +19 -19
  18. package/dist/ActivityCreate/index.mjs +18 -18
  19. package/dist/ActivityDetails/index.js +19 -19
  20. package/dist/ActivityDetails/index.mjs +18 -18
  21. package/dist/ActivityFilters/index.js +19 -19
  22. package/dist/ActivityFilters/index.mjs +18 -18
  23. package/dist/ActivityPageLayout/index.js +19 -19
  24. package/dist/ActivityPageLayout/index.mjs +18 -18
  25. package/dist/ActivityPreview/index.js +19 -19
  26. package/dist/ActivityPreview/index.mjs +18 -18
  27. package/dist/Alert/index.js +1 -1
  28. package/dist/Alert/index.mjs +1 -1
  29. package/dist/AlertDialog/index.js +1 -1
  30. package/dist/AlertDialog/index.mjs +1 -1
  31. package/dist/AlertManager/index.js +19 -19
  32. package/dist/AlertManager/index.mjs +18 -18
  33. package/dist/AlertManagerView/index.js +1 -1
  34. package/dist/AlertManagerView/index.mjs +1 -1
  35. package/dist/Alternative/index.js +1 -1
  36. package/dist/Alternative/index.mjs +1 -1
  37. package/dist/AnnouncementModal/index.js +1 -1
  38. package/dist/AnnouncementModal/index.mjs +1 -1
  39. package/dist/AnnouncementsGate/index.js +1 -1
  40. package/dist/AnnouncementsGate/index.mjs +1 -1
  41. package/dist/AnswerKeyModal/index.js +1 -1
  42. package/dist/AnswerKeyModal/index.mjs +1 -1
  43. package/dist/AppHeader/index.js +1 -1
  44. package/dist/AppHeader/index.mjs +1 -1
  45. package/dist/AppLayout/index.js +1 -1
  46. package/dist/AppLayout/index.mjs +1 -1
  47. package/dist/Avatar/index.js +1 -1
  48. package/dist/Avatar/index.mjs +1 -1
  49. package/dist/Badge/index.js +1 -1
  50. package/dist/Badge/index.mjs +1 -1
  51. package/dist/BreadcrumbMenu/index.js +1 -1
  52. package/dist/BreadcrumbMenu/index.mjs +1 -1
  53. package/dist/Button/index.js +1 -1
  54. package/dist/Button/index.mjs +1 -1
  55. package/dist/Calendar/index.js +1 -1
  56. package/dist/Calendar/index.mjs +1 -1
  57. package/dist/CalendarCard/index.js +1 -1
  58. package/dist/CalendarCard/index.mjs +1 -1
  59. package/dist/Card/index.js +1 -1
  60. package/dist/Card/index.mjs +1 -1
  61. package/dist/ChartComponents/index.js +1 -1
  62. package/dist/ChartComponents/index.mjs +1 -1
  63. package/dist/Chat/index.js +1 -1
  64. package/dist/Chat/index.mjs +1 -1
  65. package/dist/Chatbot/index.js +1 -1
  66. package/dist/Chatbot/index.mjs +1 -1
  67. package/dist/CheckBox/index.js +1 -1
  68. package/dist/CheckBox/index.mjs +1 -1
  69. package/dist/CheckBoxGroup/index.js +19 -19
  70. package/dist/CheckBoxGroup/index.mjs +18 -18
  71. package/dist/Chips/index.js +1 -1
  72. package/dist/Chips/index.mjs +1 -1
  73. package/dist/ChoroplethMap/index.js +1 -1
  74. package/dist/ChoroplethMap/index.mjs +1 -1
  75. package/dist/ColorPicker/index.js +1 -1
  76. package/dist/ColorPicker/index.mjs +1 -1
  77. package/dist/ComparatorChart/ComparatorView/index.js +1 -1
  78. package/dist/ComparatorChart/ComparatorView/index.mjs +1 -1
  79. package/dist/CorrectActivityModal/index.js +6 -6
  80. package/dist/CorrectActivityModal/index.mjs +5 -5
  81. package/dist/Divider/index.js +1 -1
  82. package/dist/Divider/index.mjs +1 -1
  83. package/dist/DownloadButton/index.js +1 -1
  84. package/dist/DownloadButton/index.mjs +1 -1
  85. package/dist/DownloadModal/index.js +1 -1
  86. package/dist/DownloadModal/index.mjs +1 -1
  87. package/dist/DragHandleButton/index.js +1 -1
  88. package/dist/DragHandleButton/index.mjs +1 -1
  89. package/dist/DropdownMenu/index.js +1 -1
  90. package/dist/DropdownMenu/index.mjs +1 -1
  91. package/dist/EmptyState/index.js +1 -1
  92. package/dist/EmptyState/index.mjs +1 -1
  93. package/dist/EnemClassroomStartModal/index.d.ts +0 -2
  94. package/dist/EnemClassroomStartModal/index.d.ts.map +1 -1
  95. package/dist/EnemClassroomStartModal/index.js +4 -6
  96. package/dist/EnemClassroomStartModal/index.js.map +1 -1
  97. package/dist/EnemClassroomStartModal/index.mjs +3 -5
  98. package/dist/EssayThemePicker/index.js +1 -1
  99. package/dist/EssayThemePicker/index.mjs +1 -1
  100. package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +19 -19
  101. package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +18 -18
  102. package/dist/ExamPageLayout/examsTableConfig/index.js +1 -1
  103. package/dist/ExamPageLayout/examsTableConfig/index.mjs +1 -1
  104. package/dist/ExamPageLayout/index.js +19 -19
  105. package/dist/ExamPageLayout/index.mjs +18 -18
  106. package/dist/FileAttachment/index.js +1 -1
  107. package/dist/FileAttachment/index.mjs +1 -1
  108. package/dist/FileDropzone/index.js +1 -1
  109. package/dist/FileDropzone/index.mjs +1 -1
  110. package/dist/Forum/index.js +4 -4
  111. package/dist/Forum/index.mjs +3 -3
  112. package/dist/HtmlMathRenderer/index.js +1 -1
  113. package/dist/HtmlMathRenderer/index.mjs +1 -1
  114. package/dist/IconButton/index.js +1 -1
  115. package/dist/IconButton/index.mjs +1 -1
  116. package/dist/IconRoundedButton/index.js +1 -1
  117. package/dist/IconRoundedButton/index.mjs +1 -1
  118. package/dist/ImagePreviewCard/index.js +1 -1
  119. package/dist/ImagePreviewCard/index.mjs +1 -1
  120. package/dist/ImageUpload/index.js +1 -1
  121. package/dist/ImageUpload/index.mjs +1 -1
  122. package/dist/LatexRenderer/index.js +1 -1
  123. package/dist/LatexRenderer/index.mjs +1 -1
  124. package/dist/LevelBar/index.js +1 -1
  125. package/dist/LevelBar/index.mjs +1 -1
  126. package/dist/Menu/index.js +1 -1
  127. package/dist/Menu/index.mjs +1 -1
  128. package/dist/MetricBox/index.js +1 -1
  129. package/dist/MetricBox/index.mjs +1 -1
  130. package/dist/Modal/index.js +1 -1
  131. package/dist/Modal/index.mjs +1 -1
  132. package/dist/ModalComponents/index.js +1 -1
  133. package/dist/ModalComponents/index.mjs +1 -1
  134. package/dist/MultiSearchSelect/index.js +1 -1
  135. package/dist/MultiSearchSelect/index.mjs +1 -1
  136. package/dist/MultipleChoice/index.js +1 -1
  137. package/dist/MultipleChoice/index.mjs +1 -1
  138. package/dist/NavButton/index.js +1 -1
  139. package/dist/NavButton/index.mjs +1 -1
  140. package/dist/NoSearchResult/index.js +1 -1
  141. package/dist/NoSearchResult/index.mjs +1 -1
  142. package/dist/NotFound/index.js +1 -1
  143. package/dist/NotFound/index.mjs +1 -1
  144. package/dist/NotificationCard/index.js +1 -1
  145. package/dist/NotificationCard/index.mjs +1 -1
  146. package/dist/PerformanceDistributionChart/index.js +1 -1
  147. package/dist/PerformanceDistributionChart/index.mjs +1 -1
  148. package/dist/PerformanceQuestionsData/index.js +1 -1
  149. package/dist/PerformanceQuestionsData/index.mjs +1 -1
  150. package/dist/PerformanceRanking/index.js +1 -1
  151. package/dist/PerformanceRanking/index.mjs +1 -1
  152. package/dist/PerformanceReport/index.js +1 -1
  153. package/dist/PerformanceReport/index.mjs +1 -1
  154. package/dist/PerformanceReportModal/index.js +1 -1
  155. package/dist/PerformanceReportModal/index.mjs +1 -1
  156. package/dist/PeriodSelector/index.js +1 -1
  157. package/dist/PeriodSelector/index.mjs +1 -1
  158. package/dist/PrintableUsersTable/index.js +19 -19
  159. package/dist/PrintableUsersTable/index.mjs +18 -18
  160. package/dist/ProficiencyChart/index.js +1 -1
  161. package/dist/ProficiencyChart/index.mjs +1 -1
  162. package/dist/ProgressBar/index.js +1 -1
  163. package/dist/ProgressBar/index.mjs +1 -1
  164. package/dist/ProgressCircle/index.js +1 -1
  165. package/dist/ProgressCircle/index.mjs +1 -1
  166. package/dist/ProgressModal/index.js +1 -1
  167. package/dist/ProgressModal/index.mjs +1 -1
  168. package/dist/QuestionsData/index.js +1 -1
  169. package/dist/QuestionsData/index.mjs +1 -1
  170. package/dist/QuestionsPerformanceCard/index.js +19 -19
  171. package/dist/QuestionsPerformanceCard/index.mjs +18 -18
  172. package/dist/Quiz/QuizResult/index.d.ts.map +1 -1
  173. package/dist/Quiz/QuizResult/index.js +4 -4
  174. package/dist/Quiz/QuizResult/index.mjs +3 -3
  175. package/dist/Quiz/TeacherFeedbackSection/index.js +3 -3
  176. package/dist/Quiz/TeacherFeedbackSection/index.mjs +2 -2
  177. package/dist/Quiz/TeacherQuestionComment/index.js +3 -3
  178. package/dist/Quiz/TeacherQuestionComment/index.mjs +2 -2
  179. package/dist/Quiz/index.d.ts.map +1 -1
  180. package/dist/Quiz/index.js +6 -6
  181. package/dist/Quiz/index.mjs +5 -5
  182. package/dist/Quiz/useQuizStore/index.d.ts +6 -0
  183. package/dist/Quiz/useQuizStore/index.d.ts.map +1 -1
  184. package/dist/Quiz/useQuizStore/index.js +2 -2
  185. package/dist/Quiz/useQuizStore/index.mjs +1 -1
  186. package/dist/QuizTimer/index.js +3 -3
  187. package/dist/QuizTimer/index.mjs +2 -2
  188. package/dist/Radio/index.js +1 -1
  189. package/dist/Radio/index.mjs +1 -1
  190. package/dist/RangeGauge/index.js +1 -1
  191. package/dist/RangeGauge/index.mjs +1 -1
  192. package/dist/RecommendedLessonCreate/index.js +19 -19
  193. package/dist/RecommendedLessonCreate/index.mjs +18 -18
  194. package/dist/RecommendedLessonDetails/index.js +1 -1
  195. package/dist/RecommendedLessonDetails/index.mjs +1 -1
  196. package/dist/RecommendedLessonsHistory/index.js +19 -19
  197. package/dist/RecommendedLessonsHistory/index.mjs +18 -18
  198. package/dist/ReportDetailModal/index.js +1 -1
  199. package/dist/ReportDetailModal/index.mjs +1 -1
  200. package/dist/RestrictedAccess/index.js +1 -1
  201. package/dist/RestrictedAccess/index.mjs +1 -1
  202. package/dist/RichEditor/index.js +3 -3
  203. package/dist/RichEditor/index.mjs +2 -2
  204. package/dist/{RichEditorCore-F5QPQLEK.js → RichEditorCore-NEIZKFKE.js} +2 -2
  205. package/dist/{RichEditorCore-F5QPQLEK.js.map → RichEditorCore-NEIZKFKE.js.map} +1 -1
  206. package/dist/{RichEditorCore-QZYPHNRY.mjs → RichEditorCore-TLAFZ5XG.mjs} +2 -2
  207. package/dist/ScoreCircle/index.js +1 -1
  208. package/dist/ScoreCircle/index.mjs +1 -1
  209. package/dist/Search/index.js +1 -1
  210. package/dist/Search/index.mjs +1 -1
  211. package/dist/SearchSelect/index.js +1 -1
  212. package/dist/SearchSelect/index.mjs +1 -1
  213. package/dist/Select/index.js +1 -1
  214. package/dist/Select/index.mjs +1 -1
  215. package/dist/SelectionButton/index.js +1 -1
  216. package/dist/SelectionButton/index.mjs +1 -1
  217. package/dist/SendActivityModal/SendActivityModal.js +19 -19
  218. package/dist/SendActivityModal/SendActivityModal.mjs +18 -18
  219. package/dist/SendActivityModal/index.js +19 -19
  220. package/dist/SendActivityModal/index.mjs +18 -18
  221. package/dist/SendLessonModal/index.js +19 -19
  222. package/dist/SendLessonModal/index.mjs +18 -18
  223. package/dist/SimpleBarChart/index.js +1 -1
  224. package/dist/SimpleBarChart/index.mjs +1 -1
  225. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +19 -19
  226. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +18 -18
  227. package/dist/SimulatedPerformance/useSimulatedPerformance/index.js +1 -1
  228. package/dist/SimulatedPerformance/useSimulatedPerformance/index.mjs +1 -1
  229. package/dist/SimulatedStudentSimulationsModal/index.js +19 -19
  230. package/dist/SimulatedStudentSimulationsModal/index.mjs +5 -5
  231. package/dist/SimulationsPage/index.js +19 -19
  232. package/dist/SimulationsPage/index.mjs +18 -18
  233. package/dist/Skeleton/index.js +1 -1
  234. package/dist/Skeleton/index.mjs +1 -1
  235. package/dist/StatisticsCard/index.js +1 -1
  236. package/dist/StatisticsCard/index.mjs +1 -1
  237. package/dist/Stepper/index.js +1 -1
  238. package/dist/Stepper/index.mjs +1 -1
  239. package/dist/StudentLessonProgressModal/index.js +1 -1
  240. package/dist/StudentLessonProgressModal/index.mjs +1 -1
  241. package/dist/StudentPerformanceDetailsModal/index.js +1 -1
  242. package/dist/StudentPerformanceDetailsModal/index.mjs +1 -1
  243. package/dist/StudentRanking/index.js +1 -1
  244. package/dist/StudentRanking/index.mjs +1 -1
  245. package/dist/SubjectIcons/index.js +1 -1
  246. package/dist/SubjectIcons/index.mjs +1 -1
  247. package/dist/SubjectMenuOverflow/index.js +1 -1
  248. package/dist/SubjectMenuOverflow/index.mjs +1 -1
  249. package/dist/Support/TicketModal/index.js +1 -1
  250. package/dist/Support/TicketModal/index.mjs +1 -1
  251. package/dist/Support/index.js +1 -1
  252. package/dist/Support/index.mjs +1 -1
  253. package/dist/Table/TablePagination/index.js +1 -1
  254. package/dist/Table/TablePagination/index.mjs +1 -1
  255. package/dist/Table/index.js +1 -1
  256. package/dist/Table/index.mjs +1 -1
  257. package/dist/TableProvider/index.js +19 -19
  258. package/dist/TableProvider/index.mjs +18 -18
  259. package/dist/Text/index.js +1 -1
  260. package/dist/Text/index.mjs +1 -1
  261. package/dist/TextArea/index.js +1 -1
  262. package/dist/TextArea/index.mjs +1 -1
  263. package/dist/ThemeToggle/index.js +1 -1
  264. package/dist/ThemeToggle/index.mjs +1 -1
  265. package/dist/TimeChart/index.js +1 -1
  266. package/dist/TimeChart/index.mjs +1 -1
  267. package/dist/TimeReport/index.js +1 -1
  268. package/dist/TimeReport/index.mjs +1 -1
  269. package/dist/Toast/Toaster/index.js +1 -1
  270. package/dist/Toast/Toaster/index.mjs +1 -1
  271. package/dist/Toast/index.js +1 -1
  272. package/dist/Toast/index.mjs +1 -1
  273. package/dist/ToggleSwitch/index.js +1 -1
  274. package/dist/ToggleSwitch/index.mjs +1 -1
  275. package/dist/TokenValidation/index.js +1 -1
  276. package/dist/TokenValidation/index.mjs +1 -1
  277. package/dist/Tooltip/index.js +1 -1
  278. package/dist/Tooltip/index.mjs +1 -1
  279. package/dist/TruncatedText/index.js +1 -1
  280. package/dist/TruncatedText/index.mjs +1 -1
  281. package/dist/TypeSelector/index.js +1 -1
  282. package/dist/TypeSelector/index.mjs +1 -1
  283. package/dist/UnifiedDraftModelPage/index.js +19 -19
  284. package/dist/UnifiedDraftModelPage/index.mjs +18 -18
  285. package/dist/UnifiedHistoryPage/index.js +19 -19
  286. package/dist/UnifiedHistoryPage/index.mjs +18 -18
  287. package/dist/VerticalBarChart/index.js +1 -1
  288. package/dist/VerticalBarChart/index.mjs +1 -1
  289. package/dist/VideoPlayer/index.js +1 -1
  290. package/dist/VideoPlayer/index.mjs +1 -1
  291. package/dist/Whiteboard/index.js +1 -1
  292. package/dist/Whiteboard/index.mjs +1 -1
  293. package/dist/{chunk-JBAGQ7L5.js → chunk-3VG5MEMY.js} +14 -13
  294. package/dist/chunk-3VG5MEMY.js.map +1 -0
  295. package/dist/{chunk-ED23HOE6.mjs → chunk-5NIU4XJY.mjs} +7 -6
  296. package/dist/chunk-5NIU4XJY.mjs.map +1 -0
  297. package/dist/{chunk-DDZDNGBU.mjs → chunk-64WGFT5E.mjs} +3 -1
  298. package/dist/chunk-64WGFT5E.mjs.map +1 -0
  299. package/dist/{chunk-QN32O3J3.js → chunk-6MK23BTK.js} +62 -62
  300. package/dist/{chunk-QN32O3J3.js.map → chunk-6MK23BTK.js.map} +1 -1
  301. package/dist/{chunk-XX4WE4TL.js → chunk-726PIA5M.js} +1 -1
  302. package/dist/chunk-726PIA5M.js.map +1 -0
  303. package/dist/{chunk-VZJKHIHN.mjs → chunk-7KALNPIG.mjs} +4 -4
  304. package/dist/{chunk-NQNEG4KR.mjs → chunk-7VKFZHWP.mjs} +11 -10
  305. package/dist/chunk-7VKFZHWP.mjs.map +1 -0
  306. package/dist/{chunk-GYQUB3VW.js → chunk-BBFV3NDF.js} +8 -6
  307. package/dist/chunk-BBFV3NDF.js.map +1 -0
  308. package/dist/{chunk-I3Z2BHGN.js → chunk-BJHYT75K.js} +3 -3
  309. package/dist/{chunk-I3Z2BHGN.js.map → chunk-BJHYT75K.js.map} +1 -1
  310. package/dist/{chunk-CFTQ24UT.mjs → chunk-CMYBNZ74.mjs} +2 -2
  311. package/dist/{chunk-R7NS5KFE.mjs → chunk-CWUNF7U6.mjs} +3 -3
  312. package/dist/{chunk-NAOHJW6H.mjs → chunk-DLHV7CZN.mjs} +6 -6
  313. package/dist/{chunk-7EXSCXPK.mjs → chunk-DMYETMZG.mjs} +2 -2
  314. package/dist/{chunk-RQPSA4QV.js → chunk-GQUTHDV2.js} +3 -3
  315. package/dist/{chunk-RQPSA4QV.js.map → chunk-GQUTHDV2.js.map} +1 -1
  316. package/dist/{chunk-OZWPOXVU.mjs → chunk-GVGDUCVF.mjs} +2 -2
  317. package/dist/{chunk-QFWY46XU.js → chunk-IMDS5NQL.js} +16 -16
  318. package/dist/{chunk-QFWY46XU.js.map → chunk-IMDS5NQL.js.map} +1 -1
  319. package/dist/{chunk-KYGFYAU7.js → chunk-JHN35HPH.js} +3 -3
  320. package/dist/{chunk-KYGFYAU7.js.map → chunk-JHN35HPH.js.map} +1 -1
  321. package/dist/{chunk-DMZTDCUF.js → chunk-JPQI2RIV.js} +3 -3
  322. package/dist/{chunk-DMZTDCUF.js.map → chunk-JPQI2RIV.js.map} +1 -1
  323. package/dist/{chunk-LXX5KU3O.mjs → chunk-LMDAF6LA.mjs} +2 -2
  324. package/dist/{chunk-FL4W4S6J.mjs → chunk-MOSP7FR5.mjs} +1 -1
  325. package/dist/chunk-MOSP7FR5.mjs.map +1 -0
  326. package/dist/{chunk-HZXXADG4.js → chunk-NLPBW3SW.js} +44 -43
  327. package/dist/chunk-NLPBW3SW.js.map +1 -0
  328. package/dist/{chunk-TAABXGZL.mjs → chunk-O3JP6ZMQ.mjs} +2 -2
  329. package/dist/{chunk-MFO4S7T5.js → chunk-PJOBOBNJ.js} +3 -3
  330. package/dist/{chunk-MFO4S7T5.js.map → chunk-PJOBOBNJ.js.map} +1 -1
  331. package/dist/{chunk-CWGYVJTQ.mjs → chunk-PORNR2NU.mjs} +3 -3
  332. package/dist/chunk-QF3UKRIJ.js +33 -0
  333. package/dist/chunk-QF3UKRIJ.js.map +1 -0
  334. package/dist/chunk-RAHXO5YU.mjs +33 -0
  335. package/dist/chunk-RAHXO5YU.mjs.map +1 -0
  336. package/dist/{chunk-BTZANNVO.js → chunk-RQ65U5G2.js} +3 -3
  337. package/dist/{chunk-BTZANNVO.js.map → chunk-RQ65U5G2.js.map} +1 -1
  338. package/dist/{chunk-Q56I63FO.mjs → chunk-RVGLLMWG.mjs} +2 -2
  339. package/dist/{chunk-VVRRAF6H.js → chunk-S2S7TK3L.js} +49 -36
  340. package/dist/chunk-S2S7TK3L.js.map +1 -0
  341. package/dist/{chunk-INIH23HU.js → chunk-VF6QXUX3.js} +2 -2
  342. package/dist/{chunk-INIH23HU.js.map → chunk-VF6QXUX3.js.map} +1 -1
  343. package/dist/{chunk-QWC5GC2Q.mjs → chunk-WY7V5UJN.mjs} +2 -2
  344. package/dist/{chunk-OCKO7T7L.js → chunk-XQAYZBLL.js} +3 -3
  345. package/dist/{chunk-OCKO7T7L.js.map → chunk-XQAYZBLL.js.map} +1 -1
  346. package/dist/{chunk-ATGLKKXI.js → chunk-Y7ZBWTD7.js} +17 -17
  347. package/dist/{chunk-ATGLKKXI.js.map → chunk-Y7ZBWTD7.js.map} +1 -1
  348. package/dist/{chunk-7XWWI5EY.js → chunk-YFIB3YNQ.js} +11 -11
  349. package/dist/{chunk-7XWWI5EY.js.map → chunk-YFIB3YNQ.js.map} +1 -1
  350. package/dist/{chunk-UCWTF336.mjs → chunk-YWGJAB63.mjs} +2 -2
  351. package/dist/{chunk-4MPXVPBQ.mjs → chunk-YYHS2TM6.mjs} +43 -30
  352. package/dist/chunk-YYHS2TM6.mjs.map +1 -0
  353. package/dist/components/Quiz/Quiz.utils.d.ts +8 -0
  354. package/dist/components/Quiz/Quiz.utils.d.ts.map +1 -1
  355. package/dist/hooks/useAppContent/index.js +19 -19
  356. package/dist/hooks/useAppContent/index.mjs +18 -18
  357. package/dist/hooks/useComparator/index.js +1 -1
  358. package/dist/hooks/useComparator/index.mjs +1 -1
  359. package/dist/hooks/useDraftAutoSave/index.js +3 -3
  360. package/dist/hooks/useDraftAutoSave/index.mjs +2 -2
  361. package/dist/index.js +19 -19
  362. package/dist/index.mjs +18 -18
  363. package/dist/types/enemClassroom/index.d.ts +29 -2
  364. package/dist/types/enemClassroom/index.d.ts.map +1 -1
  365. package/dist/types/enemClassroom/index.js +4 -2
  366. package/dist/types/enemClassroom/index.js.map +1 -1
  367. package/dist/types/enemClassroom/index.mjs +3 -1
  368. package/dist/types/enemClassroom.d.ts +29 -2
  369. package/dist/types/enemClassroom.d.ts.map +1 -1
  370. package/dist/types/questionTypes/index.js +1 -1
  371. package/dist/types/questionTypes/index.mjs +1 -1
  372. package/dist/utils/index.js +1 -1
  373. package/dist/utils/index.mjs +1 -1
  374. package/dist/utils/renderSubjectCell/index.js +1 -1
  375. package/dist/utils/renderSubjectCell/index.mjs +1 -1
  376. package/dist/utils/renderTextCell/index.js +1 -1
  377. package/dist/utils/renderTextCell/index.mjs +1 -1
  378. package/package.json +1 -1
  379. package/dist/chunk-4MPXVPBQ.mjs.map +0 -1
  380. package/dist/chunk-DDZDNGBU.mjs.map +0 -1
  381. package/dist/chunk-ED23HOE6.mjs.map +0 -1
  382. package/dist/chunk-FL4W4S6J.mjs.map +0 -1
  383. package/dist/chunk-GYQUB3VW.js.map +0 -1
  384. package/dist/chunk-HZXXADG4.js.map +0 -1
  385. package/dist/chunk-JBAGQ7L5.js.map +0 -1
  386. package/dist/chunk-ND5M3DZA.mjs +0 -21
  387. package/dist/chunk-ND5M3DZA.mjs.map +0 -1
  388. package/dist/chunk-NQNEG4KR.mjs.map +0 -1
  389. package/dist/chunk-NYKBCQUO.js +0 -21
  390. package/dist/chunk-NYKBCQUO.js.map +0 -1
  391. package/dist/chunk-VVRRAF6H.js.map +0 -1
  392. package/dist/chunk-XX4WE4TL.js.map +0 -1
  393. /package/dist/{RichEditorCore-QZYPHNRY.mjs.map → RichEditorCore-TLAFZ5XG.mjs.map} +0 -0
  394. /package/dist/{chunk-VZJKHIHN.mjs.map → chunk-7KALNPIG.mjs.map} +0 -0
  395. /package/dist/{chunk-CFTQ24UT.mjs.map → chunk-CMYBNZ74.mjs.map} +0 -0
  396. /package/dist/{chunk-R7NS5KFE.mjs.map → chunk-CWUNF7U6.mjs.map} +0 -0
  397. /package/dist/{chunk-NAOHJW6H.mjs.map → chunk-DLHV7CZN.mjs.map} +0 -0
  398. /package/dist/{chunk-7EXSCXPK.mjs.map → chunk-DMYETMZG.mjs.map} +0 -0
  399. /package/dist/{chunk-OZWPOXVU.mjs.map → chunk-GVGDUCVF.mjs.map} +0 -0
  400. /package/dist/{chunk-LXX5KU3O.mjs.map → chunk-LMDAF6LA.mjs.map} +0 -0
  401. /package/dist/{chunk-TAABXGZL.mjs.map → chunk-O3JP6ZMQ.mjs.map} +0 -0
  402. /package/dist/{chunk-CWGYVJTQ.mjs.map → chunk-PORNR2NU.mjs.map} +0 -0
  403. /package/dist/{chunk-Q56I63FO.mjs.map → chunk-RVGLLMWG.mjs.map} +0 -0
  404. /package/dist/{chunk-QWC5GC2Q.mjs.map → chunk-WY7V5UJN.mjs.map} +0 -0
  405. /package/dist/{chunk-UCWTF336.mjs.map → chunk-YWGJAB63.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7XWWI5EY.js","../src/components/SimulationsPage/SimulationsDetailModal.tsx","../src/components/shared/SimulationSummaryCards.tsx","../src/utils/simulatedScore.ts","../src/utils/questionDuration.ts","../src/hooks/useSimulationCardDetails.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7XWWI5EY.js","sourcesContent":[null,"import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { ChangeEvent } from 'react';\nimport { PaperclipIcon } from '@phosphor-icons/react/dist/csr/Paperclip';\nimport { XIcon } from '@phosphor-icons/react/dist/csr/X';\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 { QuestionCommentField } from '../shared/QuestionCommentField';\nimport {\n TrueFalseStatementList,\n type TrueFalseStatement,\n} from '../shared/TrueFalseStatementList';\nimport { ImageAnswerView } from '../shared/ImageAnswerView';\nimport { DEFAULT_IMAGE_TOLERANCE } from '../../utils/image/imageAnswer.utils';\nimport { AlternativesList } from '../Alternative/Alternative';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nimport { OptionStatus } from '../../enums/Options';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport {\n getQuestionStatusBadgeConfig,\n QUESTION_STATUS,\n type QuestionStatus,\n} from '../../utils/studentActivityCorrection';\nimport {\n buildSimulationMeta,\n ContentCards,\n DataCard,\n SectionTitle,\n SimulationCardShell,\n SimulationStatCards,\n StudentSummaryHeader,\n} from '../shared/SimulationSummaryCards';\nimport { cn } from '../../utils/utils';\nimport { formatQuestionDuration } from '../../utils/questionDuration';\nimport { formatTimeSpent } from '../../utils/activityDetailsUtils';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseSimulations } from '../../hooks/useSimulations';\nimport {\n useSimulationCardDetails,\n type SimulationCardHandlers,\n type SimulationDetailState,\n type SimulationNoteState,\n} from '../../hooks/useSimulationCardDetails';\nimport type {\n SimulationsListData,\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\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 PENDING: QUESTION_STATUS.PENDENTE,\n};\n\n/** Label of the inner accordion holding the student's answer. */\nfunction getAnswerAccordionTitle(questionType: string): string {\n if (questionType === QUESTION_TYPE.DISSERTATIVA) {\n return 'Resposta do aluno';\n }\n if (questionType === QUESTION_TYPE.VERDADEIRO_FALSO) {\n return 'Afirmações';\n }\n if (questionType === QUESTION_TYPE.IMAGEM) {\n return 'Imagem';\n }\n return 'Alternativas';\n}\n\n// ---------------------------------------------------------------------------\n// Level 2 — Question (reuses the shared alternatives renderer + status badge)\n// ---------------------------------------------------------------------------\n\nexport interface SimulationQuestionItemProps {\n readonly question: SimulationDetailQuestion;\n /** Zero-based position in the simulation; shown as \"Questão N\". */\n readonly index: number;\n /** Persist the teacher comment on this question; an empty string clears it. */\n readonly onSaveComment: (comment: string) => Promise<void>;\n}\n\n/**\n * One question of a student's simulation: an accordion with the status badge\n * in its header and, inside, the statement, the student's answer (alternatives,\n * true/false marks, essay text or image click, by question type) and the\n * teacher comment field.\n *\n * Exported so consumers that list a student's simulations in their own layout\n * (the Desempenho report of the teacher app) render questions exactly as the\n * Simulados page does.\n */\nexport function SimulationQuestionItem({\n question,\n index,\n onSaveComment,\n}: SimulationQuestionItemProps) {\n const badge = getQuestionStatusBadgeConfig(\n QUESTION_STATUS_MAP[question.status]\n );\n\n // The subject only joins the label when the question has one mapped in the\n // knowledge matrix; without it the row falls back to a plain \"Questão N\".\n const label = [`Questão ${index + 1}`, question.subject]\n .filter(Boolean)\n .join(' - ');\n\n // Time spent sits on the right, next to the badge, and not glued to the\n // label: it is a measurement, read down the column against the other\n // questions, not part of the question's name. Null when it was never\n // measured, and then the row shows nothing rather than `00:00:00`.\n const duration = formatQuestionDuration(question.timeSpent);\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 // An essay has no alternatives to show — it has the text the student wrote.\n // Rendering the \"Alternativas\" accordion for it produced an empty box and hid\n // the answer entirely, leaving the teacher to comment on nothing.\n const isEssay = question.questionType === QUESTION_TYPE.DISSERTATIVA;\n\n // True/false never writes `option_id`, so `isSelected` is always false and the\n // student's marks live in each option's `selectedValue`. Sending it through\n // the alternatives branch showed the answer key as if it were the student's\n // answer, and no mark at all.\n const isTrueFalse = question.questionType === QUESTION_TYPE.VERDADEIRO_FALSO;\n const trueFalseStatements: TrueFalseStatement[] = question.options.map(\n (option) => ({\n id: option.id,\n statement: option.option,\n studentMark: option.selectedValue ?? null,\n isTrue: option.isCorrect,\n })\n );\n\n // IMAGEM has no alternatives either: the answer is a point on an image. The\n // alternatives branch rendered the answer key's raw JSON as an option label,\n // painted green, and never showed where the student clicked.\n const isImage = question.questionType === QUESTION_TYPE.IMAGEM;\n\n /**\n * Render the student's answer, shaped by the question type.\n */\n const renderAnswerArea = () => {\n if (isImage) {\n return (\n <ImageAnswerView\n imageUrl={question.additionalContent ?? ''}\n correctPoint={question.correctPoint}\n studentPoint={\n question.imageAnswer\n ? {\n x: question.imageAnswer.coordinateX,\n y: question.imageAnswer.coordinateY,\n }\n : null\n }\n toleranceRadius={question.imageTolerance ?? DEFAULT_IMAGE_TOLERANCE}\n />\n );\n }\n\n if (isEssay) {\n return (\n <div className=\"rounded-lg border border-border-100 bg-background-50 p-3\">\n {question.answer ? (\n <HtmlMathRenderer\n content={question.answer}\n className=\"text-sm text-text-800\"\n />\n ) : (\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhuma resposta fornecida\n </Text>\n )}\n </div>\n );\n }\n\n if (isTrueFalse) {\n return <TrueFalseStatementList statements={trueFalseStatements} />;\n }\n\n return (\n <AlternativesList\n mode=\"readonly\"\n layout=\"compact\"\n name={`question-${question.questionId}`}\n alternatives={alternatives}\n selectedValue={question.selectedOptionId ?? ''}\n />\n );\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=\"min-w-0 text-text-950\">\n {label}\n </Text>\n <div className=\"flex shrink-0 items-center gap-2\">\n {duration && (\n <Text\n size=\"xs\"\n className=\"whitespace-nowrap tabular-nums text-text-600\"\n >\n {duration}\n </Text>\n )}\n <span\n className={cn(\n 'inline-flex shrink-0 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 </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}-answer`}\n trigger={\n <div className=\"flex-1 py-2\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-950\">\n {getAnswerAccordionTitle(question.questionType)}\n </Text>\n </div>\n }\n contentClassName=\"px-3 pb-3\"\n >\n {renderAnswerArea()}\n </CardAccordation>\n <QuestionCommentField\n value={question.teacherComment ?? ''}\n onSave={onSaveComment}\n />\n </div>\n </CardAccordation>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Note (\"Observação\")\n// ---------------------------------------------------------------------------\n\nexport interface SimulationNoteRowProps {\n /** Current observation, null when the teacher never wrote one. */\n readonly note: NoteData | null;\n readonly loading: boolean;\n /**\n * Message of a failed load. While it is set the row offers a retry instead\n * of the editor: writing over a note that could not be read would replace\n * it, and this save has no version to protect it.\n */\n readonly loadError?: string | null;\n /** Load the observation again; required whenever `loadError` can be set. */\n readonly onRetry?: () => void;\n /**\n * Persist the observation. `text` is already trimmed and non-empty; `file`\n * is a newly chosen attachment still to be uploaded (null when none), and\n * `existingAttachment` is the URL of the saved file the teacher kept — null\n * when there was none, when it was removed, or when a new file replaces it.\n */\n readonly onSave: (\n text: string,\n file: File | null,\n existingAttachment: string | null\n ) => Promise<void>;\n}\n\n/**\n * Human label of an attachment URL: its file name, or a generic word when the\n * URL carries none.\n */\nfunction getAttachmentLabel(url: string): string {\n const lastSegment = url.split('?')[0].split('/').pop() ?? '';\n try {\n return decodeURIComponent(lastSegment) || 'Anexo';\n } catch {\n return lastSegment || 'Anexo';\n }\n}\n\n/**\n * Grey pill naming an attached file. Links to the file when `href` is given\n * and offers a remove button when `onRemove` is given — the same chip the\n * activity correction modal uses for its observation attachment.\n */\nfunction AttachmentChip({\n label,\n href,\n onRemove,\n}: {\n readonly label: string;\n readonly href?: string;\n readonly onRemove?: () => void;\n}) {\n const content = (\n <>\n <PaperclipIcon size={18} className=\"shrink-0 text-text-800\" />\n <span className=\"truncate text-md font-medium text-text-800\">\n {label}\n </span>\n </>\n );\n\n return (\n <div className=\"flex h-10 min-w-0 max-w-[220px] items-center gap-2 rounded-full bg-secondary-500 px-5\">\n {href ? (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex min-w-0 items-center gap-2 hover:underline\"\n >\n {content}\n </a>\n ) : (\n content\n )}\n {onRemove && (\n <button\n type=\"button\"\n onClick={onRemove}\n className=\"shrink-0 text-text-700 hover:text-text-950\"\n aria-label={`Remover ${label}`}\n >\n <XIcon size={18} />\n </button>\n )}\n </div>\n );\n}\n\n/**\n * The simulation-wide teacher observation: a row with the saved text, the\n * attached file (if any) and an \"Incluir\"/\"Editar\" button that swaps into a\n * textarea with \"Anexar\" and \"Salvar\" — the same flow as the observation of\n * the activity correction modal.\n *\n * Exported for the same reason as {@link SimulationQuestionItem}.\n */\nexport function SimulationNoteRow({\n note,\n loading,\n loadError,\n onRetry,\n onSave,\n}: SimulationNoteRowProps) {\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 /** File chosen in this editing session, not uploaded yet. */\n const [pendingFile, setPendingFile] = useState<File | null>(null);\n /** Saved attachment the teacher is keeping; null once removed. */\n const [keptAttachment, setKeptAttachment] = useState<string | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const savedAttachment = note?.attachment ?? null;\n\n const startEditing = () => {\n setDraft(note?.note ?? '');\n setPendingFile(null);\n setKeptAttachment(savedAttachment);\n setError(null);\n setEditing(true);\n };\n\n const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null;\n if (file) setPendingFile(file);\n // Reset so the same file can be picked again after being removed.\n event.target.value = '';\n };\n\n const handleSave = async () => {\n if (!draft.trim()) return;\n setSaving(true);\n setError(null);\n try {\n await onSave(\n draft.trim(),\n pendingFile,\n pendingFile ? null : keptAttachment\n );\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 /** Left side of the footer: the chosen/kept file, or the \"Anexar\" button. */\n const renderAttachmentControl = () => {\n if (pendingFile) {\n return (\n <AttachmentChip\n label={pendingFile.name}\n onRemove={() => setPendingFile(null)}\n />\n );\n }\n if (keptAttachment) {\n return (\n <AttachmentChip\n label={getAttachmentLabel(keptAttachment)}\n href={keptAttachment}\n onRemove={() => setKeptAttachment(null)}\n />\n );\n }\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"medium\"\n onClick={() => fileInputRef.current?.click()}\n className=\"flex items-center gap-2\"\n >\n <PaperclipIcon size={18} />\n Anexar\n </Button>\n );\n };\n\n if (loading) {\n return <SkeletonCard className=\"h-14\" />;\n }\n\n if (loadError) {\n return (\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-200 bg-background p-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n Observação\n </Text>\n <Text size=\"sm\" className=\"text-error-600\">\n {loadError}\n </Text>\n </div>\n {onRetry && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"medium\"\n onClick={onRetry}\n className=\"shrink-0\"\n >\n Tentar novamente\n </Button>\n )}\n </div>\n );\n }\n\n if (editing) {\n return (\n <div className=\"flex flex-col gap-4 rounded-lg border border-border-200 bg-background p-4\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n Observação\n </Text>\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 {/* Only images: the pre-signed upload endpoint accepts nothing else. */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n accept=\"image/*\"\n onChange={handleFileChange}\n aria-label=\"Selecionar arquivo\"\n />\n <div className=\"flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {renderAttachmentControl()}\n <Button\n type=\"button\"\n variant=\"solid\"\n size=\"medium\"\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-2 rounded-lg border border-border-200 bg-background p-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <Text size=\"md\" 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 <div className=\"flex shrink-0 items-center gap-2\">\n {savedAttachment && (\n <AttachmentChip\n label={getAttachmentLabel(savedAttachment)}\n href={savedAttachment}\n />\n )}\n <Button\n type=\"button\"\n variant=\"solid\"\n size=\"medium\"\n onClick={startEditing}\n >\n {note?.note ? 'Editar' : 'Incluir'}\n </Button>\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Level 1 — Simulation\n// ---------------------------------------------------------------------------\n\n/**\n * Observation and question list of one simulado, loaded when its card is\n * first expanded.\n */\nexport function SimulationAnswers({\n detail,\n note,\n onRetryNote,\n onSaveNote,\n onSaveQuestionComment,\n}: {\n readonly detail: SimulationDetailState | undefined;\n readonly note: SimulationNoteState | undefined;\n /** Load the observation again after a failed request */\n readonly onRetryNote: () => void;\n readonly onSaveNote: SimulationNoteRowProps['onSave'];\n readonly onSaveQuestionComment: (\n questionId: string,\n comment: string\n ) => Promise<void>;\n}) {\n if (!detail || detail.loading) {\n return <SkeletonCard className=\"h-40\" />;\n }\n\n if (detail.error) {\n return (\n <Text size=\"sm\" className=\"text-error-600\">\n {detail.error}\n </Text>\n );\n }\n\n if (!detail.data) return null;\n\n return (\n <>\n <SimulationNoteRow\n note={note?.data ?? null}\n loading={note?.loading ?? false}\n loadError={note?.error ?? null}\n onRetry={onRetryNote}\n onSave={onSaveNote}\n />\n\n <div className=\"flex flex-col gap-2 pt-2\">\n <Text as=\"h4\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Respostas\n </Text>\n {detail.data.questions.map((question, qIndex) => (\n <SimulationQuestionItem\n key={question.questionId}\n question={question}\n index={qIndex}\n onSaveComment={(comment) =>\n onSaveQuestionComment(question.questionId, comment)\n }\n />\n ))}\n </div>\n </>\n );\n}\n\nfunction SimulationItem({\n simulation,\n index,\n expanded,\n onToggle,\n detail,\n note,\n onRetryNote,\n onSaveNote,\n onSaveQuestionComment,\n}: SimulationCardHandlers & {\n readonly simulation: StudentSimulationItem;\n readonly index: number;\n readonly expanded: boolean;\n readonly onToggle: () => void;\n}) {\n const title = simulation.title?.trim()\n ? simulation.title.trim()\n : `Simulado ${index + 1}`;\n const meta = buildSimulationMeta(simulation);\n\n return (\n <SimulationCardShell\n value={simulation.id}\n title={title}\n meta={meta}\n correct={simulation.correctCount}\n totalQuestions={simulation.totalQuestions}\n expanded={expanded}\n onToggle={onToggle}\n >\n <SimulationStatCards\n score={simulation.score}\n correct={simulation.correctCount}\n incorrect={simulation.incorrectCount}\n blank={simulation.blankCount}\n />\n {/* Essays awaiting grading used to be counted as blank. */}\n {detail?.data && detail.data.counts.pending > 0 && (\n <Text size=\"sm\" className=\"text-text-600\">\n {`${detail.data.counts.pending} ${detail.data.counts.pending === 1 ? 'questão dissertativa aguarda' : 'questões dissertativas aguardam'} correção`}\n </Text>\n )}\n <ContentCards\n best={simulation.bestContent ?? null}\n worst={simulation.worstContent ?? null}\n />\n {expanded && (\n <SimulationAnswers\n detail={detail}\n note={note}\n onRetryNote={onRetryNote}\n onSaveNote={onSaveNote}\n onSaveQuestionComment={onSaveQuestionComment}\n />\n )}\n </SimulationCardShell>\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 { fetchStudentSimulations } = 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\n const {\n expandedId,\n details,\n notes,\n toggle,\n retryNote,\n makeSaveNote,\n makeSaveQuestionComment,\n } = useSimulationCardDetails({\n api,\n userInstitutionId: student?.userInstitutionId ?? null,\n isOpen,\n });\n\n useEffect(() => {\n if (!isOpen || !student) return;\n\n setList(null);\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 return (\n <Modal isOpen={isOpen} onClose={onClose} title=\"Simulados\" size=\"xl\">\n {student && (\n <div className=\"flex max-h-[70vh] flex-col gap-6 overflow-y-auto pr-1\">\n <StudentSummaryHeader\n name={student.name}\n location={[\n list?.student.school,\n list?.student.class,\n list?.student.schoolYear,\n ]}\n />\n\n {listLoading && (\n <>\n <SkeletonCard className=\"h-20\" />\n <SkeletonCard className=\"h-40\" />\n </>\n )}\n {listError && (\n <Text size=\"sm\" className=\"text-error-600\">\n {listError}\n </Text>\n )}\n\n {list && (\n <section className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de simulados</SectionTitle>\n <div className=\"flex flex-col gap-2 md:flex-row\">\n <DataCard\n label=\"Simulados realizados\"\n value={String(list.student.simulationsAnswered)}\n />\n {list.student.totalTimeSeconds !== undefined && (\n <DataCard\n label=\"Tempo total\"\n value={formatTimeSpent(list.student.totalTimeSeconds)}\n />\n )}\n </div>\n </section>\n )}\n\n {list && (\n <section className=\"flex flex-col gap-3\">\n <SectionTitle>Simulados realizados</SectionTitle>\n {list.simulations.data.length === 0 ? (\n <div className=\"flex items-center justify-center rounded-xl border border-border-50 bg-background p-6\">\n <Text size=\"sm\" className=\"text-text-600\">\n Este estudante ainda não respondeu nenhum simulado.\n </Text>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {list.simulations.data.map((simulation, index) => (\n <SimulationItem\n // Keyed by the student too: the comment fields below keep\n // a dirty draft through a `value` change so an in-flight\n // save cannot discard it, and a note written for one\n // student must never survive into another. Remounting\n // resets it for free.\n key={`${student.userInstitutionId}-${simulation.id}`}\n simulation={simulation}\n index={index}\n expanded={expandedId === simulation.id}\n onToggle={() => toggle(simulation.id)}\n detail={details[simulation.id]}\n note={notes[simulation.id]}\n onRetryNote={() => retryNote(simulation.id)}\n onSaveNote={makeSaveNote(simulation.id)}\n onSaveQuestionComment={makeSaveQuestionComment(\n simulation.id\n )}\n />\n ))}\n </div>\n )}\n </section>\n )}\n </div>\n )}\n </Modal>\n );\n}\n","import type { ReactNode } from 'react';\nimport { ExamIcon } from '@phosphor-icons/react/dist/csr/Exam';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport { MinusCircleIcon } from '@phosphor-icons/react/dist/csr/MinusCircle';\nimport Text from '../Text/Text';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { CardAccordation } from '../Accordation';\nimport { UserIcon } from '../UserIcon/UserIcon';\nimport { cn } from '../../utils/utils';\nimport { formatScoreOutOfTen } from '../../utils/simulatedScore';\nimport {\n formatDateToBrazilian,\n formatTimeSpent,\n} from '../../utils/activityDetailsUtils';\n\n/**\n * Summary blocks of a student's simulados: header, \"Dados de simulados\" cards,\n * the coloured stat cards and the best/worst subtema pair.\n *\n * Shared by the two modals that show them — the Simulados page modal and the\n * Simulados report modal — so the design is described once. Everything here is\n * presentational: it takes already-fetched numbers and returns markup.\n */\n\n/**\n * The only field these cards read from a subtema, so both the Simulados page\n * shape and the report shape satisfy it.\n */\nexport interface SimulationContentSummary {\n readonly contentName: string;\n}\n\n/** The fields the \"Duração · Nota · Feito em\" line reads from a simulado. */\nexport interface SimulationMetaSource {\n readonly timeSpentSeconds?: number;\n readonly score?: number;\n readonly answeredAt?: string | null;\n}\n\n/**\n * \"Duração · Nota · Feito em\" line of a simulado card. Each segment only joins\n * when its field came in the payload, so an older backend shows a shorter line\n * instead of \"undefined\".\n */\nexport function buildSimulationMeta(simulation: SimulationMetaSource): string {\n const parts: string[] = [];\n if (simulation.timeSpentSeconds !== undefined) {\n parts.push(`Duração: ${formatTimeSpent(simulation.timeSpentSeconds)}`);\n }\n if (simulation.score !== undefined) {\n parts.push(`Nota: ${formatScoreOutOfTen(simulation.score)}`);\n }\n if (simulation.answeredAt) {\n parts.push(`Feito em: ${formatDateToBrazilian(simulation.answeredAt)}`);\n }\n return parts.join(' · ');\n}\n\n/** Section heading, \"Dados de simulados\" and its siblings (14px bold). */\nexport function SectionTitle({ children }: { readonly children: ReactNode }) {\n return (\n <Text as=\"h3\" size=\"sm\" weight=\"bold\" className=\"text-text-950\">\n {children}\n </Text>\n );\n}\n\n/**\n * White card with a centred uppercase label and the value in a rectangular\n * info badge: the \"Dados de simulados\" pair.\n */\nexport function DataCard({\n label,\n value,\n}: {\n readonly label: string;\n readonly value: string;\n}) {\n return (\n <div className=\"flex flex-1 flex-col items-center gap-2 rounded-xl border border-border-50 bg-background px-3 py-4\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className=\"text-center uppercase text-text-800\"\n >\n {label}\n </Text>\n <span className=\"rounded-sm bg-info-background px-2 py-1 text-sm text-info-800\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Tone of a coloured stat card; each maps to one token family. */\ntype StatTone = 'grade' | 'correct' | 'incorrect' | 'blank';\n\nconst STAT_TONE_CLASSES: Record<\n StatTone,\n { card: string; circle: string; icon: string; value: string }\n> = {\n grade: {\n card: 'bg-warning-background',\n circle: 'bg-warning-300',\n icon: 'text-text',\n value: 'text-warning-600',\n },\n correct: {\n card: 'bg-success-200',\n circle: 'bg-indicator-positive',\n icon: 'text-text-950',\n value: 'text-success-700',\n },\n incorrect: {\n card: 'bg-error-100',\n circle: 'bg-error-500',\n icon: 'text-text',\n value: 'text-error-700',\n },\n blank: {\n card: 'bg-info-background',\n circle: 'bg-info-500',\n icon: 'text-text',\n value: 'text-info-700',\n },\n};\n\n/**\n * Coloured stat card, laid out as a centred column: icon in a circle, tiny\n * uppercase label and the large value.\n */\nfunction SimulationStatCard({\n tone,\n icon,\n label,\n value,\n}: {\n readonly tone: StatTone;\n readonly icon: ReactNode;\n readonly label: string;\n readonly value: string;\n}) {\n const classes = STAT_TONE_CLASSES[tone];\n return (\n <div\n className={cn(\n 'flex flex-1 flex-col items-center gap-1 rounded-xl border border-border-50 px-3 py-4',\n classes.card\n )}\n >\n <span\n className={cn(\n 'flex size-8 shrink-0 items-center justify-center rounded-full',\n classes.circle,\n classes.icon\n )}\n >\n {icon}\n </span>\n <Text\n as=\"span\"\n weight=\"bold\"\n className=\"text-center text-[8px] leading-3 uppercase text-text-800\"\n >\n {label}\n </Text>\n <Text\n size=\"xl\"\n weight=\"bold\"\n className={cn('text-center', classes.value)}\n >\n {value}\n </Text>\n </div>\n );\n}\n\n/**\n * The four cards of a cut: grade, correct, incorrect and blank. The grade card\n * is skipped when no score was sent, so an older API still renders the three\n * counts it always had.\n */\nexport function SimulationStatCards({\n score,\n correct,\n incorrect,\n blank,\n}: {\n readonly score: number | undefined;\n readonly correct: number;\n readonly incorrect: number;\n readonly blank: number;\n}) {\n return (\n <div className=\"grid grid-cols-2 gap-2 lg:grid-cols-4\">\n {score !== undefined && (\n <SimulationStatCard\n tone=\"grade\"\n icon={<ExamIcon size={16} weight=\"bold\" />}\n label=\"Nota média\"\n value={formatScoreOutOfTen(score)}\n />\n )}\n <SimulationStatCard\n tone=\"correct\"\n icon={<CheckCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões corretas\"\n value={String(correct)}\n />\n <SimulationStatCard\n tone=\"incorrect\"\n icon={<XCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões incorretas\"\n value={String(incorrect)}\n />\n <SimulationStatCard\n tone=\"blank\"\n icon={<MinusCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões em branco\"\n value={String(blank)}\n />\n </div>\n );\n}\n\n/** One subtema card: centred coloured label over the content name. */\nfunction ContentCard({\n label,\n labelClassName,\n content,\n}: {\n readonly label: string;\n readonly labelClassName: string;\n readonly content: SimulationContentSummary | null;\n}) {\n return (\n <div className=\"flex flex-1 flex-col items-center gap-2 rounded-xl border border-border-50 bg-background p-4\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className={cn('text-center uppercase', labelClassName)}\n >\n {label}\n </Text>\n <Text size=\"md\" className=\"text-center text-text-950\">\n {content?.contentName ?? '—'}\n </Text>\n </div>\n );\n}\n\n/** Best/worst subtema pair; \"—\" when the cut has no answered content. */\nexport function ContentCards({\n best,\n worst,\n}: {\n readonly best: SimulationContentSummary | null;\n readonly worst: SimulationContentSummary | null;\n}) {\n return (\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2\">\n <ContentCard\n label=\"Subtema com melhor resultado\"\n labelClassName=\"text-success-300\"\n content={best}\n />\n <ContentCard\n label=\"Subtema com maior dificuldade\"\n labelClassName=\"text-error-300\"\n content={worst}\n />\n </div>\n );\n}\n\n/**\n * Expandable card of one simulado: title and meta line on top, the hit\n * progress below, and whatever the caller renders inside.\n *\n * The two modals list simulados from different endpoints, with different field\n * names, but the card itself is the same object on screen — so the shape lives\n * here and each caller passes the values it has.\n */\nexport function SimulationCardShell({\n value,\n title,\n meta,\n correct,\n totalQuestions,\n expanded,\n onToggle,\n children,\n}: {\n /** Accordion id, unique within the list */\n readonly value: string;\n readonly title: string;\n /** \"Duração · Nota · Feito em\"; an empty string hides the line */\n readonly meta: string;\n readonly correct: number;\n readonly totalQuestions: number;\n readonly expanded: boolean;\n readonly onToggle: () => void;\n readonly children: ReactNode;\n}) {\n return (\n <CardAccordation\n value={value}\n expanded={expanded}\n onToggleExpanded={onToggle}\n triggerClassName=\"p-4\"\n contentClassName=\"flex flex-col gap-4 pt-0\"\n trigger={\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <div className=\"flex items-center justify-between gap-3\">\n <Text size=\"lg\" weight=\"bold\" className=\"min-w-0 text-text-950\">\n {title}\n </Text>\n {meta && (\n <Text\n size=\"xs\"\n weight=\"semibold\"\n className=\"shrink-0 text-text-600\"\n >\n {meta}\n </Text>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <ProgressBar\n value={correct}\n max={totalQuestions}\n variant=\"green\"\n size=\"small\"\n className=\"flex-1\"\n />\n <Text size=\"xs\" weight=\"medium\" className=\"shrink-0 text-text-950\">\n {`${correct} de ${totalQuestions} corretas`}\n </Text>\n </div>\n </div>\n }\n >\n {children}\n </CardAccordation>\n );\n}\n\n/**\n * Header: avatar, name, an optional badge and \"Escola • Turma • Ano\".\n *\n * `location` takes the parts in display order and drops the empty ones, so a\n * caller whose payload has not arrived yet simply shows the name alone.\n */\nexport function StudentSummaryHeader({\n name,\n location,\n badge,\n}: {\n readonly name: string;\n readonly location: readonly (string | null | undefined)[];\n readonly badge?: ReactNode;\n}) {\n const parts = location.filter((part): part is string => Boolean(part));\n\n return (\n <div className=\"flex flex-col gap-2 border-b border-border-200 pb-4\">\n <div className=\"flex items-center gap-2\">\n <UserIcon size={24} className=\"shrink-0\" />\n <Text size=\"md\" className=\"min-w-0 flex-1 truncate text-text-950\">\n {name}\n </Text>\n {badge}\n </div>\n {parts.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {parts.map((part, index) => (\n <span key={part} className=\"flex items-center gap-2\">\n {index > 0 && (\n <span\n aria-hidden=\"true\"\n className=\"size-1 rounded-full bg-border-600\"\n />\n )}\n <Text size=\"xs\" className=\"text-text-600\">\n {part}\n </Text>\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n","/**\n * Format a 0-100 score as a 0-10 grade with one decimal, in pt-BR.\n *\n * The simulados stack carries scores as percentages, while every screen shows\n * them as a grade out of ten. Keeping the conversion in one place is what stops\n * the modal, the table and the cards from rounding differently.\n *\n * @param percentage - Score in the 0-100 scale\n * @returns The grade as a pt-BR string, e.g. `'7,1'`\n *\n * @example\n * ```typescript\n * formatScoreOutOfTen(71.25); // '7,1'\n * formatScoreOutOfTen(100); // '10,0'\n * ```\n */\nexport function formatScoreOutOfTen(percentage: number): string {\n return (percentage / 10).toLocaleString('pt-BR', {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n });\n}\n","import { formatTimeSpent } from './activityDetailsUtils';\n\n/**\n * Format the time a student spent on a single question, the way the relatórios\n * of simulados present it: `HH:MM:SS` (`00:00:40`, `00:02:30`, `01:05:30`).\n *\n * The clock format is shared with `formatTimeSpent`, which renders the activity\n * and simulado totals — a question row and the \"Tempo total\" card above it read\n * the same way instead of each inventing its own notation.\n *\n * What this adds on top is the \"not measured\" rule: it returns `null` when\n * there is nothing meaningful to show — the value is zero, absent, negative or\n * not finite. Zero is not the same as \"answered instantly\": simulations\n * answered before per-question telemetry existed carry a zero, and so does a\n * question the student never opened. Rendering `00:00:00` there would read as\n * \"the student guessed\", which is the opposite of what the teacher should\n * conclude. Callers drop the whole segment from the row instead.\n *\n * @param seconds - Time spent in seconds\n * @returns Formatted duration in `HH:MM:SS`, or null when there is no measured time\n *\n * @example\n * ```typescript\n * formatQuestionDuration(150); // \"00:02:30\"\n * formatQuestionDuration(0); // null\n * ```\n */\nexport function formatQuestionDuration(\n seconds: number | null | undefined\n): string | null {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return null;\n }\n\n // Truncate before the zero check, not after: a fractional value below one\n // second floors to 0 and must take the same \"not measured\" path as a plain\n // 0, otherwise it would render a misleading `00:00:00`.\n const total = Math.floor(seconds);\n if (total <= 0) {\n return null;\n }\n\n return formatTimeSpent(total);\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { BaseApiClient } from '../types/api';\nimport type { NoteData, SimulationDetailData } from '../types/simulations';\nimport { createUseSimulations } from './useSimulations';\n\n/** Lazily loaded question detail of one simulado. */\nexport interface SimulationDetailState {\n readonly loading: boolean;\n readonly error: string | null;\n readonly data: SimulationDetailData | null;\n}\n\n/**\n * Lazily loaded teacher observation of one simulado.\n *\n * `error` exists so a failed request is never shown as \"this simulado has no\n * observation\": that would offer the teacher an empty field whose save is a\n * create-or-replace, silently overwriting a note they were never shown.\n */\nexport interface SimulationNoteState {\n readonly loading: boolean;\n readonly error: string | null;\n readonly data: NoteData | null;\n}\n\n/** Save handler of one simulado's observation, as the note row expects it. */\nexport type SaveSimulationNote = (\n text: string,\n file: File | null,\n existingAttachment: string | null\n) => Promise<void>;\n\n/**\n * What one simulado card needs from this hook: the two loaded pieces and the\n * three actions on them, already bound to that simulado.\n */\nexport interface SimulationCardHandlers {\n readonly detail: SimulationDetailState | undefined;\n readonly note: SimulationNoteState | undefined;\n /** Load the observation again after a failed request */\n readonly onRetryNote: () => void;\n readonly onSaveNote: SaveSimulationNote;\n readonly onSaveQuestionComment: (\n questionId: string,\n comment: string\n ) => Promise<void>;\n}\n\nexport interface UseSimulationCardDetailsParams {\n /** API client used to load and save the per-simulado data */\n readonly api: BaseApiClient;\n /** Enrollment whose simulados are listed; null while there is no student */\n readonly userInstitutionId: string | null;\n /** Whether the modal is open; reopening starts a fresh session */\n readonly isOpen: boolean;\n}\n\nexport interface UseSimulationCardDetailsReturn {\n readonly expandedId: string | null;\n readonly details: Record<string, SimulationDetailState>;\n readonly notes: Record<string, SimulationNoteState>;\n /** Expand or collapse one simulado, loading its data the first time */\n readonly toggle: (simulationId: string) => void;\n /** Load the observation again after a failed request */\n readonly retryNote: (simulationId: string) => void;\n readonly makeSaveNote: (simulationId: string) => SaveSimulationNote;\n readonly makeSaveQuestionComment: (\n simulationId: string\n ) => (questionId: string, comment: string) => Promise<void>;\n}\n\n/**\n * Per-simulado detail state of a student's simulados modal.\n *\n * Owns what both modals that list simulados need: which card is expanded, the\n * questions and observation of each expanded card, and the two save actions.\n * The list itself stays with the caller, because each modal gets it from a\n * different endpoint.\n *\n * Responses are tagged with a session. The session changes whenever the modal\n * opens, the student changes or the API client changes, and a response from an\n * older session is dropped instead of written into the current one.\n *\n * @param params - API client, the enrollment being shown and the open flag\n * @returns Expanded card, loaded details and notes, and the actions on them\n *\n * @example\n * ```tsx\n * const { expandedId, details, notes, toggle } = useSimulationCardDetails({\n * api,\n * userInstitutionId: student?.userInstitutionId ?? null,\n * isOpen,\n * });\n * ```\n */\nexport function useSimulationCardDetails({\n api,\n userInstitutionId,\n isOpen,\n}: UseSimulationCardDetailsParams): UseSimulationCardDetailsReturn {\n const useSimulations = useMemo(() => createUseSimulations(api), [api]);\n const {\n fetchSimulationDetail,\n fetchNote,\n uploadNoteAttachment,\n saveNote,\n saveQuestionComment,\n } = useSimulations();\n\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const [details, setDetails] = useState<Record<string, SimulationDetailState>>(\n {}\n );\n const [notes, setNotes] = useState<Record<string, SimulationNoteState>>({});\n\n // Guards 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, so a slow response from the previous student\n // (or from a replaced API client) is dropped instead of written into the\n // current one.\n const sessionRef = useRef(0);\n useEffect(() => {\n sessionRef.current += 1;\n setExpandedId(null);\n setDetails({});\n setNotes({});\n }, [isOpen, userInstitutionId, api]);\n\n const isStale = useCallback(\n (session: number) => !mountedRef.current || sessionRef.current !== session,\n []\n );\n\n const loadDetail = useCallback(\n (enrollmentId: string, simulationId: string) => {\n const session = sessionRef.current;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: { loading: true, error: null, data: null },\n }));\n fetchSimulationDetail(enrollmentId, simulationId)\n .then((data) => {\n if (isStale(session)) return;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data },\n }));\n })\n .catch(() => {\n if (isStale(session)) return;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: {\n loading: false,\n error: 'Erro ao carregar o simulado',\n data: null,\n },\n }));\n });\n },\n [fetchSimulationDetail, isStale]\n );\n\n const loadNote = useCallback(\n (enrollmentId: string, simulationId: string) => {\n const session = sessionRef.current;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: true, error: null, data: null },\n }));\n fetchNote(enrollmentId, simulationId)\n .then((data) => {\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data },\n }));\n })\n .catch(() => {\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: {\n loading: false,\n error: 'Erro ao carregar a observação',\n data: null,\n },\n }));\n });\n },\n [fetchNote, isStale]\n );\n\n const toggle = useCallback(\n (simulationId: string) => {\n if (!userInstitutionId) return;\n const next = expandedId === simulationId ? null : simulationId;\n setExpandedId(next);\n if (!next) return;\n\n // The two requests are guarded separately: a loaded detail must not stop\n // the observation from being fetched, and the other way around.\n if (!details[simulationId]) loadDetail(userInstitutionId, simulationId);\n if (!notes[simulationId]) loadNote(userInstitutionId, simulationId);\n },\n [userInstitutionId, expandedId, details, notes, loadDetail, loadNote]\n );\n\n const retryNote = useCallback(\n (simulationId: string) => {\n if (!userInstitutionId) return;\n loadNote(userInstitutionId, simulationId);\n },\n [userInstitutionId, loadNote]\n );\n\n /**\n * Save the observation of one simulado, uploading a newly chosen file first\n * so only its public URL travels with the note.\n */\n const makeSaveNote = useCallback(\n (simulationId: string): SaveSimulationNote =>\n async (text, file, existingAttachment) => {\n if (!userInstitutionId) return;\n const session = sessionRef.current;\n const attachment = file\n ? await uploadNoteAttachment(file)\n : existingAttachment;\n const saved = await saveNote(\n userInstitutionId,\n simulationId,\n text,\n attachment\n );\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data: saved },\n }));\n },\n [userInstitutionId, uploadNoteAttachment, saveNote, isStale]\n );\n\n /**\n * Save a comment on one question and reflect it in the loaded detail, so the\n * field shows what the server now holds without a refetch.\n */\n const makeSaveQuestionComment = useCallback(\n (simulationId: string) => async (questionId: string, comment: string) => {\n if (!userInstitutionId) return;\n const session = sessionRef.current;\n const saved = await saveQuestionComment(\n userInstitutionId,\n simulationId,\n questionId,\n comment\n );\n if (isStale(session)) return;\n setDetails((previous) => {\n const current = previous[simulationId];\n if (!current?.data) return previous;\n return {\n ...previous,\n [simulationId]: {\n ...current,\n data: {\n ...current.data,\n questions: current.data.questions.map((question) =>\n question.questionId === questionId\n ? { ...question, teacherComment: saved?.teacherComment ?? '' }\n : question\n ),\n },\n },\n };\n });\n },\n [userInstitutionId, saveQuestionComment, isStale]\n );\n\n return {\n expandedId,\n details,\n notes,\n toggle,\n retryNote,\n makeSaveNote,\n makeSaveQuestionComment,\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YFIB3YNQ.js","../src/components/SimulationsPage/SimulationsDetailModal.tsx","../src/components/shared/SimulationSummaryCards.tsx","../src/utils/simulatedScore.ts","../src/utils/questionDuration.ts","../src/hooks/useSimulationCardDetails.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YFIB3YNQ.js","sourcesContent":[null,"import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { ChangeEvent } from 'react';\nimport { PaperclipIcon } from '@phosphor-icons/react/dist/csr/Paperclip';\nimport { XIcon } from '@phosphor-icons/react/dist/csr/X';\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 { QuestionCommentField } from '../shared/QuestionCommentField';\nimport {\n TrueFalseStatementList,\n type TrueFalseStatement,\n} from '../shared/TrueFalseStatementList';\nimport { ImageAnswerView } from '../shared/ImageAnswerView';\nimport { DEFAULT_IMAGE_TOLERANCE } from '../../utils/image/imageAnswer.utils';\nimport { AlternativesList } from '../Alternative/Alternative';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nimport { OptionStatus } from '../../enums/Options';\nimport { QUESTION_TYPE } from '../Quiz/useQuizStore';\nimport {\n getQuestionStatusBadgeConfig,\n QUESTION_STATUS,\n type QuestionStatus,\n} from '../../utils/studentActivityCorrection';\nimport {\n buildSimulationMeta,\n ContentCards,\n DataCard,\n SectionTitle,\n SimulationCardShell,\n SimulationStatCards,\n StudentSummaryHeader,\n} from '../shared/SimulationSummaryCards';\nimport { cn } from '../../utils/utils';\nimport { formatQuestionDuration } from '../../utils/questionDuration';\nimport { formatTimeSpent } from '../../utils/activityDetailsUtils';\nimport type { BaseApiClient } from '../../types/api';\nimport { createUseSimulations } from '../../hooks/useSimulations';\nimport {\n useSimulationCardDetails,\n type SimulationCardHandlers,\n type SimulationDetailState,\n type SimulationNoteState,\n} from '../../hooks/useSimulationCardDetails';\nimport type {\n SimulationsListData,\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\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 PENDING: QUESTION_STATUS.PENDENTE,\n};\n\n/** Label of the inner accordion holding the student's answer. */\nfunction getAnswerAccordionTitle(questionType: string): string {\n if (questionType === QUESTION_TYPE.DISSERTATIVA) {\n return 'Resposta do aluno';\n }\n if (questionType === QUESTION_TYPE.VERDADEIRO_FALSO) {\n return 'Afirmações';\n }\n if (questionType === QUESTION_TYPE.IMAGEM) {\n return 'Imagem';\n }\n return 'Alternativas';\n}\n\n// ---------------------------------------------------------------------------\n// Level 2 — Question (reuses the shared alternatives renderer + status badge)\n// ---------------------------------------------------------------------------\n\nexport interface SimulationQuestionItemProps {\n readonly question: SimulationDetailQuestion;\n /** Zero-based position in the simulation; shown as \"Questão N\". */\n readonly index: number;\n /** Persist the teacher comment on this question; an empty string clears it. */\n readonly onSaveComment: (comment: string) => Promise<void>;\n}\n\n/**\n * One question of a student's simulation: an accordion with the status badge\n * in its header and, inside, the statement, the student's answer (alternatives,\n * true/false marks, essay text or image click, by question type) and the\n * teacher comment field.\n *\n * Exported so consumers that list a student's simulations in their own layout\n * (the Desempenho report of the teacher app) render questions exactly as the\n * Simulados page does.\n */\nexport function SimulationQuestionItem({\n question,\n index,\n onSaveComment,\n}: SimulationQuestionItemProps) {\n const badge = getQuestionStatusBadgeConfig(\n QUESTION_STATUS_MAP[question.status]\n );\n\n // The subject only joins the label when the question has one mapped in the\n // knowledge matrix; without it the row falls back to a plain \"Questão N\".\n const label = [`Questão ${index + 1}`, question.subject]\n .filter(Boolean)\n .join(' - ');\n\n // Time spent sits on the right, next to the badge, and not glued to the\n // label: it is a measurement, read down the column against the other\n // questions, not part of the question's name. Null when it was never\n // measured, and then the row shows nothing rather than `00:00:00`.\n const duration = formatQuestionDuration(question.timeSpent);\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 // An essay has no alternatives to show — it has the text the student wrote.\n // Rendering the \"Alternativas\" accordion for it produced an empty box and hid\n // the answer entirely, leaving the teacher to comment on nothing.\n const isEssay = question.questionType === QUESTION_TYPE.DISSERTATIVA;\n\n // True/false never writes `option_id`, so `isSelected` is always false and the\n // student's marks live in each option's `selectedValue`. Sending it through\n // the alternatives branch showed the answer key as if it were the student's\n // answer, and no mark at all.\n const isTrueFalse = question.questionType === QUESTION_TYPE.VERDADEIRO_FALSO;\n const trueFalseStatements: TrueFalseStatement[] = question.options.map(\n (option) => ({\n id: option.id,\n statement: option.option,\n studentMark: option.selectedValue ?? null,\n isTrue: option.isCorrect,\n })\n );\n\n // IMAGEM has no alternatives either: the answer is a point on an image. The\n // alternatives branch rendered the answer key's raw JSON as an option label,\n // painted green, and never showed where the student clicked.\n const isImage = question.questionType === QUESTION_TYPE.IMAGEM;\n\n /**\n * Render the student's answer, shaped by the question type.\n */\n const renderAnswerArea = () => {\n if (isImage) {\n return (\n <ImageAnswerView\n imageUrl={question.additionalContent ?? ''}\n correctPoint={question.correctPoint}\n studentPoint={\n question.imageAnswer\n ? {\n x: question.imageAnswer.coordinateX,\n y: question.imageAnswer.coordinateY,\n }\n : null\n }\n toleranceRadius={question.imageTolerance ?? DEFAULT_IMAGE_TOLERANCE}\n />\n );\n }\n\n if (isEssay) {\n return (\n <div className=\"rounded-lg border border-border-100 bg-background-50 p-3\">\n {question.answer ? (\n <HtmlMathRenderer\n content={question.answer}\n className=\"text-sm text-text-800\"\n />\n ) : (\n <Text size=\"sm\" className=\"text-text-600\">\n Nenhuma resposta fornecida\n </Text>\n )}\n </div>\n );\n }\n\n if (isTrueFalse) {\n return <TrueFalseStatementList statements={trueFalseStatements} />;\n }\n\n return (\n <AlternativesList\n mode=\"readonly\"\n layout=\"compact\"\n name={`question-${question.questionId}`}\n alternatives={alternatives}\n selectedValue={question.selectedOptionId ?? ''}\n />\n );\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=\"min-w-0 text-text-950\">\n {label}\n </Text>\n <div className=\"flex shrink-0 items-center gap-2\">\n {duration && (\n <Text\n size=\"xs\"\n className=\"whitespace-nowrap tabular-nums text-text-600\"\n >\n {duration}\n </Text>\n )}\n <span\n className={cn(\n 'inline-flex shrink-0 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 </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}-answer`}\n trigger={\n <div className=\"flex-1 py-2\">\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-950\">\n {getAnswerAccordionTitle(question.questionType)}\n </Text>\n </div>\n }\n contentClassName=\"px-3 pb-3\"\n >\n {renderAnswerArea()}\n </CardAccordation>\n <QuestionCommentField\n value={question.teacherComment ?? ''}\n onSave={onSaveComment}\n />\n </div>\n </CardAccordation>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Note (\"Observação\")\n// ---------------------------------------------------------------------------\n\nexport interface SimulationNoteRowProps {\n /** Current observation, null when the teacher never wrote one. */\n readonly note: NoteData | null;\n readonly loading: boolean;\n /**\n * Message of a failed load. While it is set the row offers a retry instead\n * of the editor: writing over a note that could not be read would replace\n * it, and this save has no version to protect it.\n */\n readonly loadError?: string | null;\n /** Load the observation again; required whenever `loadError` can be set. */\n readonly onRetry?: () => void;\n /**\n * Persist the observation. `text` is already trimmed and non-empty; `file`\n * is a newly chosen attachment still to be uploaded (null when none), and\n * `existingAttachment` is the URL of the saved file the teacher kept — null\n * when there was none, when it was removed, or when a new file replaces it.\n */\n readonly onSave: (\n text: string,\n file: File | null,\n existingAttachment: string | null\n ) => Promise<void>;\n}\n\n/**\n * Human label of an attachment URL: its file name, or a generic word when the\n * URL carries none.\n */\nfunction getAttachmentLabel(url: string): string {\n const lastSegment = url.split('?')[0].split('/').pop() ?? '';\n try {\n return decodeURIComponent(lastSegment) || 'Anexo';\n } catch {\n return lastSegment || 'Anexo';\n }\n}\n\n/**\n * Grey pill naming an attached file. Links to the file when `href` is given\n * and offers a remove button when `onRemove` is given — the same chip the\n * activity correction modal uses for its observation attachment.\n */\nfunction AttachmentChip({\n label,\n href,\n onRemove,\n}: {\n readonly label: string;\n readonly href?: string;\n readonly onRemove?: () => void;\n}) {\n const content = (\n <>\n <PaperclipIcon size={18} className=\"shrink-0 text-text-800\" />\n <span className=\"truncate text-md font-medium text-text-800\">\n {label}\n </span>\n </>\n );\n\n return (\n <div className=\"flex h-10 min-w-0 max-w-[220px] items-center gap-2 rounded-full bg-secondary-500 px-5\">\n {href ? (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex min-w-0 items-center gap-2 hover:underline\"\n >\n {content}\n </a>\n ) : (\n content\n )}\n {onRemove && (\n <button\n type=\"button\"\n onClick={onRemove}\n className=\"shrink-0 text-text-700 hover:text-text-950\"\n aria-label={`Remover ${label}`}\n >\n <XIcon size={18} />\n </button>\n )}\n </div>\n );\n}\n\n/**\n * The simulation-wide teacher observation: a row with the saved text, the\n * attached file (if any) and an \"Incluir\"/\"Editar\" button that swaps into a\n * textarea with \"Anexar\" and \"Salvar\" — the same flow as the observation of\n * the activity correction modal.\n *\n * Exported for the same reason as {@link SimulationQuestionItem}.\n */\nexport function SimulationNoteRow({\n note,\n loading,\n loadError,\n onRetry,\n onSave,\n}: SimulationNoteRowProps) {\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 /** File chosen in this editing session, not uploaded yet. */\n const [pendingFile, setPendingFile] = useState<File | null>(null);\n /** Saved attachment the teacher is keeping; null once removed. */\n const [keptAttachment, setKeptAttachment] = useState<string | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const savedAttachment = note?.attachment ?? null;\n\n const startEditing = () => {\n setDraft(note?.note ?? '');\n setPendingFile(null);\n setKeptAttachment(savedAttachment);\n setError(null);\n setEditing(true);\n };\n\n const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null;\n if (file) setPendingFile(file);\n // Reset so the same file can be picked again after being removed.\n event.target.value = '';\n };\n\n const handleSave = async () => {\n if (!draft.trim()) return;\n setSaving(true);\n setError(null);\n try {\n await onSave(\n draft.trim(),\n pendingFile,\n pendingFile ? null : keptAttachment\n );\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 /** Left side of the footer: the chosen/kept file, or the \"Anexar\" button. */\n const renderAttachmentControl = () => {\n if (pendingFile) {\n return (\n <AttachmentChip\n label={pendingFile.name}\n onRemove={() => setPendingFile(null)}\n />\n );\n }\n if (keptAttachment) {\n return (\n <AttachmentChip\n label={getAttachmentLabel(keptAttachment)}\n href={keptAttachment}\n onRemove={() => setKeptAttachment(null)}\n />\n );\n }\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"medium\"\n onClick={() => fileInputRef.current?.click()}\n className=\"flex items-center gap-2\"\n >\n <PaperclipIcon size={18} />\n Anexar\n </Button>\n );\n };\n\n if (loading) {\n return <SkeletonCard className=\"h-14\" />;\n }\n\n if (loadError) {\n return (\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-200 bg-background p-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n Observação\n </Text>\n <Text size=\"sm\" className=\"text-error-600\">\n {loadError}\n </Text>\n </div>\n {onRetry && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"medium\"\n onClick={onRetry}\n className=\"shrink-0\"\n >\n Tentar novamente\n </Button>\n )}\n </div>\n );\n }\n\n if (editing) {\n return (\n <div className=\"flex flex-col gap-4 rounded-lg border border-border-200 bg-background p-4\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n Observação\n </Text>\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 {/* Only images: the pre-signed upload endpoint accepts nothing else. */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n accept=\"image/*\"\n onChange={handleFileChange}\n aria-label=\"Selecionar arquivo\"\n />\n <div className=\"flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {renderAttachmentControl()}\n <Button\n type=\"button\"\n variant=\"solid\"\n size=\"medium\"\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-2 rounded-lg border border-border-200 bg-background p-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <Text size=\"md\" 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 <div className=\"flex shrink-0 items-center gap-2\">\n {savedAttachment && (\n <AttachmentChip\n label={getAttachmentLabel(savedAttachment)}\n href={savedAttachment}\n />\n )}\n <Button\n type=\"button\"\n variant=\"solid\"\n size=\"medium\"\n onClick={startEditing}\n >\n {note?.note ? 'Editar' : 'Incluir'}\n </Button>\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Level 1 — Simulation\n// ---------------------------------------------------------------------------\n\n/**\n * Observation and question list of one simulado, loaded when its card is\n * first expanded.\n */\nexport function SimulationAnswers({\n detail,\n note,\n onRetryNote,\n onSaveNote,\n onSaveQuestionComment,\n}: {\n readonly detail: SimulationDetailState | undefined;\n readonly note: SimulationNoteState | undefined;\n /** Load the observation again after a failed request */\n readonly onRetryNote: () => void;\n readonly onSaveNote: SimulationNoteRowProps['onSave'];\n readonly onSaveQuestionComment: (\n questionId: string,\n comment: string\n ) => Promise<void>;\n}) {\n if (!detail || detail.loading) {\n return <SkeletonCard className=\"h-40\" />;\n }\n\n if (detail.error) {\n return (\n <Text size=\"sm\" className=\"text-error-600\">\n {detail.error}\n </Text>\n );\n }\n\n if (!detail.data) return null;\n\n return (\n <>\n <SimulationNoteRow\n note={note?.data ?? null}\n loading={note?.loading ?? false}\n loadError={note?.error ?? null}\n onRetry={onRetryNote}\n onSave={onSaveNote}\n />\n\n <div className=\"flex flex-col gap-2 pt-2\">\n <Text as=\"h4\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Respostas\n </Text>\n {detail.data.questions.map((question, qIndex) => (\n <SimulationQuestionItem\n key={question.questionId}\n question={question}\n index={qIndex}\n onSaveComment={(comment) =>\n onSaveQuestionComment(question.questionId, comment)\n }\n />\n ))}\n </div>\n </>\n );\n}\n\nfunction SimulationItem({\n simulation,\n index,\n expanded,\n onToggle,\n detail,\n note,\n onRetryNote,\n onSaveNote,\n onSaveQuestionComment,\n}: SimulationCardHandlers & {\n readonly simulation: StudentSimulationItem;\n readonly index: number;\n readonly expanded: boolean;\n readonly onToggle: () => void;\n}) {\n const title = simulation.title?.trim()\n ? simulation.title.trim()\n : `Simulado ${index + 1}`;\n const meta = buildSimulationMeta(simulation);\n\n return (\n <SimulationCardShell\n value={simulation.id}\n title={title}\n meta={meta}\n correct={simulation.correctCount}\n totalQuestions={simulation.totalQuestions}\n expanded={expanded}\n onToggle={onToggle}\n >\n <SimulationStatCards\n score={simulation.score}\n correct={simulation.correctCount}\n incorrect={simulation.incorrectCount}\n blank={simulation.blankCount}\n />\n {/* Essays awaiting grading used to be counted as blank. */}\n {detail?.data && detail.data.counts.pending > 0 && (\n <Text size=\"sm\" className=\"text-text-600\">\n {`${detail.data.counts.pending} ${detail.data.counts.pending === 1 ? 'questão dissertativa aguarda' : 'questões dissertativas aguardam'} correção`}\n </Text>\n )}\n <ContentCards\n best={simulation.bestContent ?? null}\n worst={simulation.worstContent ?? null}\n />\n {expanded && (\n <SimulationAnswers\n detail={detail}\n note={note}\n onRetryNote={onRetryNote}\n onSaveNote={onSaveNote}\n onSaveQuestionComment={onSaveQuestionComment}\n />\n )}\n </SimulationCardShell>\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 { fetchStudentSimulations } = 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\n const {\n expandedId,\n details,\n notes,\n toggle,\n retryNote,\n makeSaveNote,\n makeSaveQuestionComment,\n } = useSimulationCardDetails({\n api,\n userInstitutionId: student?.userInstitutionId ?? null,\n isOpen,\n });\n\n useEffect(() => {\n if (!isOpen || !student) return;\n\n setList(null);\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 return (\n <Modal isOpen={isOpen} onClose={onClose} title=\"Simulados\" size=\"xl\">\n {student && (\n <div className=\"flex max-h-[70vh] flex-col gap-6 overflow-y-auto pr-1\">\n <StudentSummaryHeader\n name={student.name}\n location={[\n list?.student.school,\n list?.student.class,\n list?.student.schoolYear,\n ]}\n />\n\n {listLoading && (\n <>\n <SkeletonCard className=\"h-20\" />\n <SkeletonCard className=\"h-40\" />\n </>\n )}\n {listError && (\n <Text size=\"sm\" className=\"text-error-600\">\n {listError}\n </Text>\n )}\n\n {list && (\n <section className=\"flex flex-col gap-3\">\n <SectionTitle>Dados de simulados</SectionTitle>\n <div className=\"flex flex-col gap-2 md:flex-row\">\n <DataCard\n label=\"Simulados realizados\"\n value={String(list.student.simulationsAnswered)}\n />\n {list.student.totalTimeSeconds !== undefined && (\n <DataCard\n label=\"Tempo total\"\n value={formatTimeSpent(list.student.totalTimeSeconds)}\n />\n )}\n </div>\n </section>\n )}\n\n {list && (\n <section className=\"flex flex-col gap-3\">\n <SectionTitle>Simulados realizados</SectionTitle>\n {list.simulations.data.length === 0 ? (\n <div className=\"flex items-center justify-center rounded-xl border border-border-50 bg-background p-6\">\n <Text size=\"sm\" className=\"text-text-600\">\n Este estudante ainda não respondeu nenhum simulado.\n </Text>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {list.simulations.data.map((simulation, index) => (\n <SimulationItem\n // Keyed by the student too: the comment fields below keep\n // a dirty draft through a `value` change so an in-flight\n // save cannot discard it, and a note written for one\n // student must never survive into another. Remounting\n // resets it for free.\n key={`${student.userInstitutionId}-${simulation.id}`}\n simulation={simulation}\n index={index}\n expanded={expandedId === simulation.id}\n onToggle={() => toggle(simulation.id)}\n detail={details[simulation.id]}\n note={notes[simulation.id]}\n onRetryNote={() => retryNote(simulation.id)}\n onSaveNote={makeSaveNote(simulation.id)}\n onSaveQuestionComment={makeSaveQuestionComment(\n simulation.id\n )}\n />\n ))}\n </div>\n )}\n </section>\n )}\n </div>\n )}\n </Modal>\n );\n}\n","import type { ReactNode } from 'react';\nimport { ExamIcon } from '@phosphor-icons/react/dist/csr/Exam';\nimport { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport { MinusCircleIcon } from '@phosphor-icons/react/dist/csr/MinusCircle';\nimport Text from '../Text/Text';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { CardAccordation } from '../Accordation';\nimport { UserIcon } from '../UserIcon/UserIcon';\nimport { cn } from '../../utils/utils';\nimport { formatScoreOutOfTen } from '../../utils/simulatedScore';\nimport {\n formatDateToBrazilian,\n formatTimeSpent,\n} from '../../utils/activityDetailsUtils';\n\n/**\n * Summary blocks of a student's simulados: header, \"Dados de simulados\" cards,\n * the coloured stat cards and the best/worst subtema pair.\n *\n * Shared by the two modals that show them — the Simulados page modal and the\n * Simulados report modal — so the design is described once. Everything here is\n * presentational: it takes already-fetched numbers and returns markup.\n */\n\n/**\n * The only field these cards read from a subtema, so both the Simulados page\n * shape and the report shape satisfy it.\n */\nexport interface SimulationContentSummary {\n readonly contentName: string;\n}\n\n/** The fields the \"Duração · Nota · Feito em\" line reads from a simulado. */\nexport interface SimulationMetaSource {\n readonly timeSpentSeconds?: number;\n readonly score?: number;\n readonly answeredAt?: string | null;\n}\n\n/**\n * \"Duração · Nota · Feito em\" line of a simulado card. Each segment only joins\n * when its field came in the payload, so an older backend shows a shorter line\n * instead of \"undefined\".\n */\nexport function buildSimulationMeta(simulation: SimulationMetaSource): string {\n const parts: string[] = [];\n if (simulation.timeSpentSeconds !== undefined) {\n parts.push(`Duração: ${formatTimeSpent(simulation.timeSpentSeconds)}`);\n }\n if (simulation.score !== undefined) {\n parts.push(`Nota: ${formatScoreOutOfTen(simulation.score)}`);\n }\n if (simulation.answeredAt) {\n parts.push(`Feito em: ${formatDateToBrazilian(simulation.answeredAt)}`);\n }\n return parts.join(' · ');\n}\n\n/** Section heading, \"Dados de simulados\" and its siblings (14px bold). */\nexport function SectionTitle({ children }: { readonly children: ReactNode }) {\n return (\n <Text as=\"h3\" size=\"sm\" weight=\"bold\" className=\"text-text-950\">\n {children}\n </Text>\n );\n}\n\n/**\n * White card with a centred uppercase label and the value in a rectangular\n * info badge: the \"Dados de simulados\" pair.\n */\nexport function DataCard({\n label,\n value,\n}: {\n readonly label: string;\n readonly value: string;\n}) {\n return (\n <div className=\"flex flex-1 flex-col items-center gap-2 rounded-xl border border-border-50 bg-background px-3 py-4\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className=\"text-center uppercase text-text-800\"\n >\n {label}\n </Text>\n <span className=\"rounded-sm bg-info-background px-2 py-1 text-sm text-info-800\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Tone of a coloured stat card; each maps to one token family. */\ntype StatTone = 'grade' | 'correct' | 'incorrect' | 'blank';\n\nconst STAT_TONE_CLASSES: Record<\n StatTone,\n { card: string; circle: string; icon: string; value: string }\n> = {\n grade: {\n card: 'bg-warning-background',\n circle: 'bg-warning-300',\n icon: 'text-text',\n value: 'text-warning-600',\n },\n correct: {\n card: 'bg-success-200',\n circle: 'bg-indicator-positive',\n icon: 'text-text-950',\n value: 'text-success-700',\n },\n incorrect: {\n card: 'bg-error-100',\n circle: 'bg-error-500',\n icon: 'text-text',\n value: 'text-error-700',\n },\n blank: {\n card: 'bg-info-background',\n circle: 'bg-info-500',\n icon: 'text-text',\n value: 'text-info-700',\n },\n};\n\n/**\n * Coloured stat card, laid out as a centred column: icon in a circle, tiny\n * uppercase label and the large value.\n */\nfunction SimulationStatCard({\n tone,\n icon,\n label,\n value,\n}: {\n readonly tone: StatTone;\n readonly icon: ReactNode;\n readonly label: string;\n readonly value: string;\n}) {\n const classes = STAT_TONE_CLASSES[tone];\n return (\n <div\n className={cn(\n 'flex flex-1 flex-col items-center gap-1 rounded-xl border border-border-50 px-3 py-4',\n classes.card\n )}\n >\n <span\n className={cn(\n 'flex size-8 shrink-0 items-center justify-center rounded-full',\n classes.circle,\n classes.icon\n )}\n >\n {icon}\n </span>\n <Text\n as=\"span\"\n weight=\"bold\"\n className=\"text-center text-[8px] leading-3 uppercase text-text-800\"\n >\n {label}\n </Text>\n <Text\n size=\"xl\"\n weight=\"bold\"\n className={cn('text-center', classes.value)}\n >\n {value}\n </Text>\n </div>\n );\n}\n\n/**\n * The four cards of a cut: grade, correct, incorrect and blank. The grade card\n * is skipped when no score was sent, so an older API still renders the three\n * counts it always had.\n */\nexport function SimulationStatCards({\n score,\n correct,\n incorrect,\n blank,\n}: {\n readonly score: number | undefined;\n readonly correct: number;\n readonly incorrect: number;\n readonly blank: number;\n}) {\n return (\n <div className=\"grid grid-cols-2 gap-2 lg:grid-cols-4\">\n {score !== undefined && (\n <SimulationStatCard\n tone=\"grade\"\n icon={<ExamIcon size={16} weight=\"bold\" />}\n label=\"Nota média\"\n value={formatScoreOutOfTen(score)}\n />\n )}\n <SimulationStatCard\n tone=\"correct\"\n icon={<CheckCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões corretas\"\n value={String(correct)}\n />\n <SimulationStatCard\n tone=\"incorrect\"\n icon={<XCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões incorretas\"\n value={String(incorrect)}\n />\n <SimulationStatCard\n tone=\"blank\"\n icon={<MinusCircleIcon size={16} weight=\"bold\" />}\n label=\"Nº de questões em branco\"\n value={String(blank)}\n />\n </div>\n );\n}\n\n/** One subtema card: centred coloured label over the content name. */\nfunction ContentCard({\n label,\n labelClassName,\n content,\n}: {\n readonly label: string;\n readonly labelClassName: string;\n readonly content: SimulationContentSummary | null;\n}) {\n return (\n <div className=\"flex flex-1 flex-col items-center gap-2 rounded-xl border border-border-50 bg-background p-4\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className={cn('text-center uppercase', labelClassName)}\n >\n {label}\n </Text>\n <Text size=\"md\" className=\"text-center text-text-950\">\n {content?.contentName ?? '—'}\n </Text>\n </div>\n );\n}\n\n/** Best/worst subtema pair; \"—\" when the cut has no answered content. */\nexport function ContentCards({\n best,\n worst,\n}: {\n readonly best: SimulationContentSummary | null;\n readonly worst: SimulationContentSummary | null;\n}) {\n return (\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2\">\n <ContentCard\n label=\"Subtema com melhor resultado\"\n labelClassName=\"text-success-300\"\n content={best}\n />\n <ContentCard\n label=\"Subtema com maior dificuldade\"\n labelClassName=\"text-error-300\"\n content={worst}\n />\n </div>\n );\n}\n\n/**\n * Expandable card of one simulado: title and meta line on top, the hit\n * progress below, and whatever the caller renders inside.\n *\n * The two modals list simulados from different endpoints, with different field\n * names, but the card itself is the same object on screen — so the shape lives\n * here and each caller passes the values it has.\n */\nexport function SimulationCardShell({\n value,\n title,\n meta,\n correct,\n totalQuestions,\n expanded,\n onToggle,\n children,\n}: {\n /** Accordion id, unique within the list */\n readonly value: string;\n readonly title: string;\n /** \"Duração · Nota · Feito em\"; an empty string hides the line */\n readonly meta: string;\n readonly correct: number;\n readonly totalQuestions: number;\n readonly expanded: boolean;\n readonly onToggle: () => void;\n readonly children: ReactNode;\n}) {\n return (\n <CardAccordation\n value={value}\n expanded={expanded}\n onToggleExpanded={onToggle}\n triggerClassName=\"p-4\"\n contentClassName=\"flex flex-col gap-4 pt-0\"\n trigger={\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <div className=\"flex items-center justify-between gap-3\">\n <Text size=\"lg\" weight=\"bold\" className=\"min-w-0 text-text-950\">\n {title}\n </Text>\n {meta && (\n <Text\n size=\"xs\"\n weight=\"semibold\"\n className=\"shrink-0 text-text-600\"\n >\n {meta}\n </Text>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <ProgressBar\n value={correct}\n max={totalQuestions}\n variant=\"green\"\n size=\"small\"\n className=\"flex-1\"\n />\n <Text size=\"xs\" weight=\"medium\" className=\"shrink-0 text-text-950\">\n {`${correct} de ${totalQuestions} corretas`}\n </Text>\n </div>\n </div>\n }\n >\n {children}\n </CardAccordation>\n );\n}\n\n/**\n * Header: avatar, name, an optional badge and \"Escola • Turma • Ano\".\n *\n * `location` takes the parts in display order and drops the empty ones, so a\n * caller whose payload has not arrived yet simply shows the name alone.\n */\nexport function StudentSummaryHeader({\n name,\n location,\n badge,\n}: {\n readonly name: string;\n readonly location: readonly (string | null | undefined)[];\n readonly badge?: ReactNode;\n}) {\n const parts = location.filter((part): part is string => Boolean(part));\n\n return (\n <div className=\"flex flex-col gap-2 border-b border-border-200 pb-4\">\n <div className=\"flex items-center gap-2\">\n <UserIcon size={24} className=\"shrink-0\" />\n <Text size=\"md\" className=\"min-w-0 flex-1 truncate text-text-950\">\n {name}\n </Text>\n {badge}\n </div>\n {parts.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {parts.map((part, index) => (\n <span key={part} className=\"flex items-center gap-2\">\n {index > 0 && (\n <span\n aria-hidden=\"true\"\n className=\"size-1 rounded-full bg-border-600\"\n />\n )}\n <Text size=\"xs\" className=\"text-text-600\">\n {part}\n </Text>\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n","/**\n * Format a 0-100 score as a 0-10 grade with one decimal, in pt-BR.\n *\n * The simulados stack carries scores as percentages, while every screen shows\n * them as a grade out of ten. Keeping the conversion in one place is what stops\n * the modal, the table and the cards from rounding differently.\n *\n * @param percentage - Score in the 0-100 scale\n * @returns The grade as a pt-BR string, e.g. `'7,1'`\n *\n * @example\n * ```typescript\n * formatScoreOutOfTen(71.25); // '7,1'\n * formatScoreOutOfTen(100); // '10,0'\n * ```\n */\nexport function formatScoreOutOfTen(percentage: number): string {\n return (percentage / 10).toLocaleString('pt-BR', {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n });\n}\n","import { formatTimeSpent } from './activityDetailsUtils';\n\n/**\n * Format the time a student spent on a single question, the way the relatórios\n * of simulados present it: `HH:MM:SS` (`00:00:40`, `00:02:30`, `01:05:30`).\n *\n * The clock format is shared with `formatTimeSpent`, which renders the activity\n * and simulado totals — a question row and the \"Tempo total\" card above it read\n * the same way instead of each inventing its own notation.\n *\n * What this adds on top is the \"not measured\" rule: it returns `null` when\n * there is nothing meaningful to show — the value is zero, absent, negative or\n * not finite. Zero is not the same as \"answered instantly\": simulations\n * answered before per-question telemetry existed carry a zero, and so does a\n * question the student never opened. Rendering `00:00:00` there would read as\n * \"the student guessed\", which is the opposite of what the teacher should\n * conclude. Callers drop the whole segment from the row instead.\n *\n * @param seconds - Time spent in seconds\n * @returns Formatted duration in `HH:MM:SS`, or null when there is no measured time\n *\n * @example\n * ```typescript\n * formatQuestionDuration(150); // \"00:02:30\"\n * formatQuestionDuration(0); // null\n * ```\n */\nexport function formatQuestionDuration(\n seconds: number | null | undefined\n): string | null {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return null;\n }\n\n // Truncate before the zero check, not after: a fractional value below one\n // second floors to 0 and must take the same \"not measured\" path as a plain\n // 0, otherwise it would render a misleading `00:00:00`.\n const total = Math.floor(seconds);\n if (total <= 0) {\n return null;\n }\n\n return formatTimeSpent(total);\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { BaseApiClient } from '../types/api';\nimport type { NoteData, SimulationDetailData } from '../types/simulations';\nimport { createUseSimulations } from './useSimulations';\n\n/** Lazily loaded question detail of one simulado. */\nexport interface SimulationDetailState {\n readonly loading: boolean;\n readonly error: string | null;\n readonly data: SimulationDetailData | null;\n}\n\n/**\n * Lazily loaded teacher observation of one simulado.\n *\n * `error` exists so a failed request is never shown as \"this simulado has no\n * observation\": that would offer the teacher an empty field whose save is a\n * create-or-replace, silently overwriting a note they were never shown.\n */\nexport interface SimulationNoteState {\n readonly loading: boolean;\n readonly error: string | null;\n readonly data: NoteData | null;\n}\n\n/** Save handler of one simulado's observation, as the note row expects it. */\nexport type SaveSimulationNote = (\n text: string,\n file: File | null,\n existingAttachment: string | null\n) => Promise<void>;\n\n/**\n * What one simulado card needs from this hook: the two loaded pieces and the\n * three actions on them, already bound to that simulado.\n */\nexport interface SimulationCardHandlers {\n readonly detail: SimulationDetailState | undefined;\n readonly note: SimulationNoteState | undefined;\n /** Load the observation again after a failed request */\n readonly onRetryNote: () => void;\n readonly onSaveNote: SaveSimulationNote;\n readonly onSaveQuestionComment: (\n questionId: string,\n comment: string\n ) => Promise<void>;\n}\n\nexport interface UseSimulationCardDetailsParams {\n /** API client used to load and save the per-simulado data */\n readonly api: BaseApiClient;\n /** Enrollment whose simulados are listed; null while there is no student */\n readonly userInstitutionId: string | null;\n /** Whether the modal is open; reopening starts a fresh session */\n readonly isOpen: boolean;\n}\n\nexport interface UseSimulationCardDetailsReturn {\n readonly expandedId: string | null;\n readonly details: Record<string, SimulationDetailState>;\n readonly notes: Record<string, SimulationNoteState>;\n /** Expand or collapse one simulado, loading its data the first time */\n readonly toggle: (simulationId: string) => void;\n /** Load the observation again after a failed request */\n readonly retryNote: (simulationId: string) => void;\n readonly makeSaveNote: (simulationId: string) => SaveSimulationNote;\n readonly makeSaveQuestionComment: (\n simulationId: string\n ) => (questionId: string, comment: string) => Promise<void>;\n}\n\n/**\n * Per-simulado detail state of a student's simulados modal.\n *\n * Owns what both modals that list simulados need: which card is expanded, the\n * questions and observation of each expanded card, and the two save actions.\n * The list itself stays with the caller, because each modal gets it from a\n * different endpoint.\n *\n * Responses are tagged with a session. The session changes whenever the modal\n * opens, the student changes or the API client changes, and a response from an\n * older session is dropped instead of written into the current one.\n *\n * @param params - API client, the enrollment being shown and the open flag\n * @returns Expanded card, loaded details and notes, and the actions on them\n *\n * @example\n * ```tsx\n * const { expandedId, details, notes, toggle } = useSimulationCardDetails({\n * api,\n * userInstitutionId: student?.userInstitutionId ?? null,\n * isOpen,\n * });\n * ```\n */\nexport function useSimulationCardDetails({\n api,\n userInstitutionId,\n isOpen,\n}: UseSimulationCardDetailsParams): UseSimulationCardDetailsReturn {\n const useSimulations = useMemo(() => createUseSimulations(api), [api]);\n const {\n fetchSimulationDetail,\n fetchNote,\n uploadNoteAttachment,\n saveNote,\n saveQuestionComment,\n } = useSimulations();\n\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const [details, setDetails] = useState<Record<string, SimulationDetailState>>(\n {}\n );\n const [notes, setNotes] = useState<Record<string, SimulationNoteState>>({});\n\n // Guards 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, so a slow response from the previous student\n // (or from a replaced API client) is dropped instead of written into the\n // current one.\n const sessionRef = useRef(0);\n useEffect(() => {\n sessionRef.current += 1;\n setExpandedId(null);\n setDetails({});\n setNotes({});\n }, [isOpen, userInstitutionId, api]);\n\n const isStale = useCallback(\n (session: number) => !mountedRef.current || sessionRef.current !== session,\n []\n );\n\n const loadDetail = useCallback(\n (enrollmentId: string, simulationId: string) => {\n const session = sessionRef.current;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: { loading: true, error: null, data: null },\n }));\n fetchSimulationDetail(enrollmentId, simulationId)\n .then((data) => {\n if (isStale(session)) return;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data },\n }));\n })\n .catch(() => {\n if (isStale(session)) return;\n setDetails((previous) => ({\n ...previous,\n [simulationId]: {\n loading: false,\n error: 'Erro ao carregar o simulado',\n data: null,\n },\n }));\n });\n },\n [fetchSimulationDetail, isStale]\n );\n\n const loadNote = useCallback(\n (enrollmentId: string, simulationId: string) => {\n const session = sessionRef.current;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: true, error: null, data: null },\n }));\n fetchNote(enrollmentId, simulationId)\n .then((data) => {\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data },\n }));\n })\n .catch(() => {\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: {\n loading: false,\n error: 'Erro ao carregar a observação',\n data: null,\n },\n }));\n });\n },\n [fetchNote, isStale]\n );\n\n const toggle = useCallback(\n (simulationId: string) => {\n if (!userInstitutionId) return;\n const next = expandedId === simulationId ? null : simulationId;\n setExpandedId(next);\n if (!next) return;\n\n // The two requests are guarded separately: a loaded detail must not stop\n // the observation from being fetched, and the other way around.\n if (!details[simulationId]) loadDetail(userInstitutionId, simulationId);\n if (!notes[simulationId]) loadNote(userInstitutionId, simulationId);\n },\n [userInstitutionId, expandedId, details, notes, loadDetail, loadNote]\n );\n\n const retryNote = useCallback(\n (simulationId: string) => {\n if (!userInstitutionId) return;\n loadNote(userInstitutionId, simulationId);\n },\n [userInstitutionId, loadNote]\n );\n\n /**\n * Save the observation of one simulado, uploading a newly chosen file first\n * so only its public URL travels with the note.\n */\n const makeSaveNote = useCallback(\n (simulationId: string): SaveSimulationNote =>\n async (text, file, existingAttachment) => {\n if (!userInstitutionId) return;\n const session = sessionRef.current;\n const attachment = file\n ? await uploadNoteAttachment(file)\n : existingAttachment;\n const saved = await saveNote(\n userInstitutionId,\n simulationId,\n text,\n attachment\n );\n if (isStale(session)) return;\n setNotes((previous) => ({\n ...previous,\n [simulationId]: { loading: false, error: null, data: saved },\n }));\n },\n [userInstitutionId, uploadNoteAttachment, saveNote, isStale]\n );\n\n /**\n * Save a comment on one question and reflect it in the loaded detail, so the\n * field shows what the server now holds without a refetch.\n */\n const makeSaveQuestionComment = useCallback(\n (simulationId: string) => async (questionId: string, comment: string) => {\n if (!userInstitutionId) return;\n const session = sessionRef.current;\n const saved = await saveQuestionComment(\n userInstitutionId,\n simulationId,\n questionId,\n comment\n );\n if (isStale(session)) return;\n setDetails((previous) => {\n const current = previous[simulationId];\n if (!current?.data) return previous;\n return {\n ...previous,\n [simulationId]: {\n ...current,\n data: {\n ...current.data,\n questions: current.data.questions.map((question) =>\n question.questionId === questionId\n ? { ...question, teacherComment: saved?.teacherComment ?? '' }\n : question\n ),\n },\n },\n };\n });\n },\n [userInstitutionId, saveQuestionComment, isStale]\n );\n\n return {\n expandedId,\n details,\n notes,\n toggle,\n retryNote,\n makeSaveNote,\n makeSaveQuestionComment,\n };\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  QUESTION_TYPE
3
- } from "./chunk-FL4W4S6J.mjs";
3
+ } from "./chunk-MOSP7FR5.mjs";
4
4
 
5
5
  // src/components/ActivityCreate/ActivityCreate.utils.ts
6
6
  function extractLessonsFromResponse(responseData) {
@@ -437,4 +437,4 @@ export {
437
437
  shouldUseCustomAddActivityCallback,
438
438
  shouldAddActivityToLessonDraft
439
439
  };
440
- //# sourceMappingURL=chunk-UCWTF336.mjs.map
440
+ //# sourceMappingURL=chunk-YWGJAB63.mjs.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ENEM_CLASSROOM_LANGUAGE,
3
3
  ENEM_CLASSROOM_LANGUAGE_LABELS
4
- } from "./chunk-ND5M3DZA.mjs";
4
+ } from "./chunk-RAHXO5YU.mjs";
5
5
  import {
6
6
  VideoPlayer_default
7
7
  } from "./chunk-JEHWNNJI.mjs";
@@ -56,12 +56,11 @@ var survey_university_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA
56
56
 
57
57
  // src/components/EnemClassroomStartModal/EnemClassroomStartModal.tsx
58
58
  import { jsx, jsxs } from "react/jsx-runtime";
59
- var ENEM_CLASSROOM_MODAL_TITLE = "Simula\xE7\xE3o do ENEM em sala de aula!";
60
- var INTRO_WARNINGS = [
59
+ var buildIntroWarnings = (texts) => [
61
60
  {
62
61
  icon: warning_classroom_default,
63
- title: "S\xF3 vale em sala de aula",
64
- description: "Essa simula\xE7\xE3o precisa ser feita com seu professor presente. N\xE3o vale fazer em casa!"
62
+ title: texts.introWarningTitle,
63
+ description: texts.introWarningText
65
64
  },
66
65
  {
67
66
  icon: warning_timer_default,
@@ -211,6 +210,16 @@ var EnemClassroomStartModal = ({
211
210
  });
212
211
  }, [exam.languageChoice, language, surveyQuestions, answers, onStart]);
213
212
  const renderIntroStep = () => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", "data-testid": "enem-classroom-intro", children: [
213
+ exam.videoUrl && /* @__PURE__ */ jsx(
214
+ Text_default,
215
+ {
216
+ size: "md",
217
+ weight: "semibold",
218
+ className: "text-text-950",
219
+ "data-testid": "enem-classroom-intro-video-text",
220
+ children: exam.studentTexts.introVideoText
221
+ }
222
+ ),
214
223
  exam.videoUrl && /* @__PURE__ */ jsx(
215
224
  VideoPlayer_default,
216
225
  {
@@ -222,29 +231,34 @@ var EnemClassroomStartModal = ({
222
231
  }
223
232
  ),
224
233
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
225
- /* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: "Ei, l\xEA isso antes de come\xE7ar!" }),
226
- INTRO_WARNINGS.map(({ icon, title, description }) => /* @__PURE__ */ jsxs(
227
- "div",
228
- {
229
- className: cn(CARD_CLASSES, "flex flex-row gap-4 items-start p-4"),
230
- children: [
231
- /* @__PURE__ */ jsx(
232
- "img",
233
- {
234
- src: icon,
235
- alt: "",
236
- "aria-hidden": true,
237
- className: "flex-shrink-0 w-10 h-10"
238
- }
234
+ /* @__PURE__ */ jsx(Text_default, { size: "lg", weight: "bold", className: "text-text-950", children: exam.studentTexts.introHeading }),
235
+ buildIntroWarnings(exam.studentTexts).map(
236
+ ({ icon, title, description }) => /* @__PURE__ */ jsxs(
237
+ "div",
238
+ {
239
+ className: cn(
240
+ CARD_CLASSES,
241
+ "flex flex-row gap-4 items-start p-4"
239
242
  ),
240
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
241
- /* @__PURE__ */ jsx(Text_default, { size: "md", weight: "bold", className: "text-text-950", children: title }),
242
- /* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-text-600", children: description })
243
- ] })
244
- ]
245
- },
246
- title
247
- ))
243
+ children: [
244
+ /* @__PURE__ */ jsx(
245
+ "img",
246
+ {
247
+ src: icon,
248
+ alt: "",
249
+ "aria-hidden": true,
250
+ className: "flex-shrink-0 w-10 h-10"
251
+ }
252
+ ),
253
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
254
+ /* @__PURE__ */ jsx(Text_default, { size: "md", weight: "bold", className: "text-text-950", children: title }),
255
+ /* @__PURE__ */ jsx(Text_default, { size: "sm", className: "text-text-600", children: description })
256
+ ] })
257
+ ]
258
+ },
259
+ title
260
+ )
261
+ )
248
262
  ] }),
249
263
  /* @__PURE__ */ jsx(
250
264
  Button_default,
@@ -469,7 +483,7 @@ var EnemClassroomStartModal = ({
469
483
  {
470
484
  isOpen,
471
485
  onClose,
472
- title: showStepper ? ENEM_CLASSROOM_MODAL_TITLE : /* @__PURE__ */ jsx("span", { className: "sr-only", children: ENEM_CLASSROOM_MODAL_TITLE }),
486
+ title: showStepper ? exam.title : /* @__PURE__ */ jsx("span", { className: "sr-only", children: exam.title }),
473
487
  size: "lg",
474
488
  footer,
475
489
  contentClassName: "flex flex-col gap-6",
@@ -483,7 +497,6 @@ var EnemClassroomStartModal = ({
483
497
  var EnemClassroomStartModal_default = EnemClassroomStartModal;
484
498
 
485
499
  export {
486
- ENEM_CLASSROOM_MODAL_TITLE,
487
500
  EnemClassroomStartModal_default
488
501
  };
489
- //# sourceMappingURL=chunk-4MPXVPBQ.mjs.map
502
+ //# sourceMappingURL=chunk-YYHS2TM6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/EnemClassroomStartModal/EnemClassroomStartModal.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState, ChangeEvent } from 'react';\nimport Modal from '../Modal/Modal';\nimport Stepper from '../Stepper/Stepper';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport CheckBox from '../CheckBox/CheckBox';\nimport Text from '../Text/Text';\nimport VideoPlayer from '../VideoPlayer/VideoPlayer';\nimport { CaretLeftIcon } from '@phosphor-icons/react/dist/csr/CaretLeft';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport { CheckIcon } from '@phosphor-icons/react/dist/csr/Check';\nimport { cn } from '../../utils/utils';\nimport warningClassroomIcon from '../../assets/img/enemClassroom/warning-classroom.png';\nimport warningTimerIcon from '../../assets/img/enemClassroom/warning-timer.png';\nimport warningIrreversibleIcon from '../../assets/img/enemClassroom/warning-irreversible.png';\nimport flagEnglish from '../../assets/img/enemClassroom/flag-english.png';\nimport flagSpanish from '../../assets/img/enemClassroom/flag-spanish.png';\nimport surveyCourseIllustration from '../../assets/img/enemClassroom/survey-course.svg';\nimport surveyUniversityIllustration from '../../assets/img/enemClassroom/survey-university.png';\nimport {\n ENEM_CLASSROOM_LANGUAGE,\n ENEM_CLASSROOM_LANGUAGE_LABELS,\n type EnemClassroomExam,\n type EnemClassroomLanguage,\n type EnemClassroomStartPayload,\n type EnemClassroomStudentTexts,\n type EnemClassroomSurveyQuestion,\n} from '../../types/enemClassroom';\n\n/** Title shared by every step, as in the design. */\n/**\n * The three warnings of the introduction step. The exam only counts when\n * taken in class, so they are shown before anything else. The first one is\n * the backoffice's to write (`studentTexts`); the other two are fixed.\n *\n * @param texts - The exam's texts\n * @returns The warnings in order\n */\nconst buildIntroWarnings = (texts: EnemClassroomStudentTexts) => [\n {\n icon: warningClassroomIcon,\n title: texts.introWarningTitle,\n description: texts.introWarningText,\n },\n {\n icon: warningTimerIcon,\n title: 'O cronômetro liga na hora',\n description:\n 'Assim que clicar em começar, o tempo já corre e não para, mesmo se fechar o navegador.',\n },\n {\n icon: warningIrreversibleIcon,\n title: 'Não tem como desfazer',\n description:\n 'Iniciar fora do horário estabelecido pelo seu professor em sala de aula pode atrapalhar seu resultado!',\n },\n];\n\n/** Flag shown next to each language, as in the design. */\nconst LANGUAGE_FLAGS: Record<EnemClassroomLanguage, string> = {\n [ENEM_CLASSROOM_LANGUAGE.INGLES]: flagEnglish,\n [ENEM_CLASSROOM_LANGUAGE.ESPANHOL]: flagSpanish,\n};\n\n/**\n * Illustration of each survey step, in the order the steps are asked: the\n * course question, then the university one.\n *\n * Fixed here rather than sent by the backoffice, so it is keyed by position —\n * reordering the questions there moves the illustration with the position,\n * not with the question. A question past the end simply gets none.\n */\nconst SURVEY_ILLUSTRATIONS = [\n surveyCourseIllustration,\n surveyUniversityIllustration,\n] as const;\n\n/**\n * The white card the warnings and the options sit on. Same vocabulary as\n * SelectionButton, so the modal reads like the rest of the design system\n * against the modal's `secondary-50` background.\n */\nconst CARD_CLASSES =\n 'rounded-xl border border-border-50 bg-background shadow-soft-shadow-1';\n\n/**\n * Gradiente da identidade da simulação em sala de aula — os mesmos tons do\n * card do painel do aluno. Não sai dos tokens de tema porque é a marca da\n * prova, não a da instituição.\n */\n/*\n * Gradiente literal, e não `bg-gradient-to-r from-… to-…`: o Tailwind v4\n * interpola `in oklab`, o que muda os tons do meio em relação ao sRGB do CSS\n * que veio do design.\n */\nconst GRADIENT_CTA_CLASSES =\n 'bg-[linear-gradient(270deg,#B21FF6_0%,#F95493_100%)] hover:opacity-90 transition-opacity';\n\n/**\n * One selectable option: a card carrying an optional icon and a label.\n *\n * The radio control itself is visually hidden — in the design the card's own\n * border carries the selection. The native input stays in the tree so the\n * options remain a real radio group: arrow keys move between them and screen\n * readers announce \"1 of 2\", which a set of toggle buttons would not.\n */\nconst OptionCard = ({\n name,\n value,\n label,\n iconSrc,\n checked,\n onSelect,\n testId,\n}: {\n name: string;\n value: string;\n label: string;\n iconSrc?: string;\n checked: boolean;\n onSelect: () => void;\n testId?: string;\n}) => (\n <label\n className={cn(\n CARD_CLASSES,\n 'flex flex-row items-center gap-3 px-4 py-3.5',\n 'cursor-pointer transition-colors hover:bg-background-100',\n 'focus-within:ring-2 focus-within:ring-indicator-info',\n checked && 'border-primary-700 ring-1 ring-primary-700'\n )}\n >\n <input\n type=\"radio\"\n name={name}\n value={value}\n checked={checked}\n onChange={onSelect}\n className=\"sr-only\"\n data-testid={testId}\n />\n {iconSrc && (\n <img src={iconSrc} alt=\"\" aria-hidden className=\"w-6 h-6 flex-shrink-0\" />\n )}\n <Text size=\"sm\" weight=\"semibold\" className=\"text-text-900\">\n {label}\n </Text>\n </label>\n);\n\n/**\n * How a survey question was answered. The student either writes an answer or\n * ticks \"ainda estou pensando\" — the tick clears and disables the field, so\n * the two never disagree.\n */\ninterface SurveyAnswerDraft {\n skipped: boolean;\n text: string;\n}\n\n/**\n * One line of the summary: the step's name over what the student answered.\n */\nconst SummaryRow = ({ label, value }: { label: string; value: string }) => (\n <div className=\"flex flex-col rounded-lg bg-primary-50 px-3 py-2\">\n <Text as=\"dt\" size=\"sm\" weight=\"bold\" className=\"text-primary-800\">\n {label}\n </Text>\n <Text as=\"dd\" size=\"sm\" className=\"text-text-600\">\n {value}\n </Text>\n </div>\n);\n\n/** The introduction is always first; the first choice comes right after it. */\nconst INTRO_STEP = 0;\nconst FIRST_CHOICE_STEP = 1;\n\n/** One screen of the flow. Which ones exist depends on the exam. */\ntype FlowStep =\n | { kind: 'intro' }\n | { kind: 'language' }\n | { kind: 'survey'; question: EnemClassroomSurveyQuestion; index: number }\n | { kind: 'summary' };\n\nexport interface EnemClassroomStartModalProps {\n isOpen: boolean;\n onClose: () => void;\n exam: EnemClassroomExam;\n /** Called with everything the start endpoint needs. */\n onStart: (payload: EnemClassroomStartPayload) => void;\n /** Disables the final button while the start request is in flight. */\n isStarting?: boolean;\n}\n\n/**\n * Whether a survey draft is complete: skipped, or answered with some text.\n */\nconst isSurveyAnswerComplete = (draft: SurveyAnswerDraft | undefined) =>\n draft?.skipped === true || (draft?.text.trim().length ?? 0) > 0;\n\n/**\n * The text shown for a survey answer on the summary screen.\n */\nconst describeSurveyAnswer = (\n question: EnemClassroomSurveyQuestion,\n draft: SurveyAnswerDraft | undefined\n) => (draft && !draft.skipped ? draft.text.trim() : question.skipLabel);\n\n/**\n * Start flow of the in-classroom ENEM simulation.\n *\n * Steps, in order: introduction (video + warnings), foreign language — only\n * when the exam offers the choice —, one step per survey question, and a\n * summary the student confirms before the exam opens. The stepper only lists\n * the steps that carry a choice; the summary is a screen of its own, as in\n * the design.\n *\n * Presentational: it collects the choices and hands them to `onStart`; the\n * caller talks to the API.\n *\n * @example\n * ```tsx\n * <EnemClassroomStartModal\n * isOpen={open}\n * onClose={() => setOpen(false)}\n * exam={exam}\n * onStart={(payload) => start(exam.id, payload)}\n * />\n * ```\n */\nconst EnemClassroomStartModal = ({\n isOpen,\n onClose,\n exam,\n onStart,\n isStarting = false,\n}: EnemClassroomStartModalProps) => {\n const surveyQuestions = useMemo(\n () => [...exam.surveyQuestions].sort((a, b) => a.position - b.position),\n [exam.surveyQuestions]\n );\n // The screens this exam has: no language step on an exam without the\n // choice, so the student goes from the warnings straight to the survey.\n const steps = useMemo<FlowStep[]>(\n () => [\n { kind: 'intro' },\n ...(exam.languageChoice ? [{ kind: 'language' } as const] : []),\n ...surveyQuestions.map((question, index) => ({\n kind: 'survey' as const,\n question,\n index,\n })),\n { kind: 'summary' },\n ],\n [exam.languageChoice, surveyQuestions]\n );\n const summaryStep = steps.length - 1;\n\n const [step, setStep] = useState(INTRO_STEP);\n const [language, setLanguage] = useState<EnemClassroomLanguage | null>(null);\n const [answers, setAnswers] = useState<Record<string, SurveyAnswerDraft>>({});\n\n // Every opening starts from the introduction with nothing filled in — and\n // so does a different exam handed in while the modal is open, whose steps\n // and questions are not the ones the answers were given to.\n useEffect(() => {\n if (isOpen) {\n setStep(INTRO_STEP);\n setLanguage(null);\n setAnswers({});\n }\n }, [isOpen, exam.id]);\n\n const stepperSteps = useMemo(\n () =>\n steps\n .filter((flowStep) => flowStep.kind !== 'summary')\n .map((flowStep) => {\n if (flowStep.kind === 'intro') {\n return {\n id: 'intro',\n label: 'Introdução',\n state: 'pending' as const,\n };\n }\n if (flowStep.kind === 'language') {\n return {\n id: 'language',\n label: 'Língua estrangeira',\n state: 'pending' as const,\n };\n }\n return {\n id: flowStep.question.id,\n label: flowStep.question.stepLabel,\n state: 'pending' as const,\n };\n }),\n [steps]\n );\n\n const currentStep = steps[step] ?? steps[INTRO_STEP];\n const currentQuestion =\n currentStep.kind === 'survey' ? currentStep.question : null;\n\n const canGoNext =\n currentStep.kind === 'language'\n ? language !== null\n : currentQuestion !== null &&\n isSurveyAnswerComplete(answers[currentQuestion.id]);\n\n const updateAnswer = useCallback(\n (questionId: string, patch: Partial<SurveyAnswerDraft>) => {\n setAnswers((prev) => {\n const current = prev[questionId] ?? { skipped: false, text: '' };\n return { ...prev, [questionId]: { ...current, ...patch } };\n });\n },\n []\n );\n\n const handleStart = useCallback(() => {\n // The language is only a choice on an exam that offers one.\n if (exam.languageChoice && !language) return;\n onStart({\n language: exam.languageChoice ? language : null,\n surveyAnswers: surveyQuestions.map((question) => {\n const draft = answers[question.id];\n const text = draft && !draft.skipped ? draft.text.trim() : '';\n return { questionId: question.id, answer: text || null };\n }),\n });\n }, [exam.languageChoice, language, surveyQuestions, answers, onStart]);\n\n const renderIntroStep = () => (\n <div className=\"flex flex-col gap-6\" data-testid=\"enem-classroom-intro\">\n {exam.videoUrl && (\n <Text\n size=\"md\"\n weight=\"semibold\"\n className=\"text-text-950\"\n data-testid=\"enem-classroom-intro-video-text\"\n >\n {exam.studentTexts.introVideoText}\n </Text>\n )}\n {exam.videoUrl && (\n <VideoPlayer\n src={exam.videoUrl}\n title={exam.title}\n // O desenho mostra só o vídeo: o nome da prova já está no título do\n // modal. O `title` fica para rotular o player nos leitores de tela.\n hideHeader\n className=\"w-full rounded-xl overflow-hidden\"\n autoSave={false}\n />\n )}\n\n <div className=\"flex flex-col gap-4\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {exam.studentTexts.introHeading}\n </Text>\n {buildIntroWarnings(exam.studentTexts).map(\n ({ icon, title, description }) => (\n <div\n key={title}\n className={cn(\n CARD_CLASSES,\n 'flex flex-row gap-4 items-start p-4'\n )}\n >\n <img\n src={icon}\n alt=\"\"\n aria-hidden\n className=\"flex-shrink-0 w-10 h-10\"\n />\n <div className=\"flex flex-col gap-1\">\n <Text size=\"md\" weight=\"bold\" className=\"text-text-950\">\n {title}\n </Text>\n <Text size=\"sm\" className=\"text-text-600\">\n {description}\n </Text>\n </div>\n </div>\n )\n )}\n </div>\n\n <Button\n size=\"large\"\n variant=\"solid\"\n action=\"primary\"\n className={cn('w-full', GRADIENT_CTA_CLASSES)}\n onClick={() => setStep(FIRST_CHOICE_STEP)}\n data-testid=\"enem-classroom-intro-continue\"\n >\n Estou em sala. Quero começar!\n </Button>\n </div>\n );\n\n const renderLanguageStep = () => (\n <div className=\"flex flex-col gap-4\" data-testid=\"enem-classroom-language\">\n <Text\n id=\"enem-classroom-language-prompt\"\n size=\"lg\"\n weight=\"bold\"\n className=\"text-text-950\"\n >\n Qual língua estrangeira você vai usar na simulação?\n </Text>\n <div\n role=\"radiogroup\"\n aria-labelledby=\"enem-classroom-language-prompt\"\n className=\"flex flex-col gap-4\"\n >\n {Object.values(ENEM_CLASSROOM_LANGUAGE).map((value) => (\n <OptionCard\n key={value}\n name=\"enem-classroom-language\"\n value={value}\n label={ENEM_CLASSROOM_LANGUAGE_LABELS[value]}\n iconSrc={LANGUAGE_FLAGS[value]}\n checked={language === value}\n onSelect={() => setLanguage(value)}\n testId={`enem-classroom-language-${value.toLowerCase()}`}\n />\n ))}\n </div>\n </div>\n );\n\n const renderSurveyStep = (\n question: EnemClassroomSurveyQuestion,\n index: number\n ) => {\n const draft = answers[question.id];\n const illustration = SURVEY_ILLUSTRATIONS[index];\n // The two ways of answering disable each other, so they can never\n // contradict — and either one on its own releases the step.\n const hasText = (draft?.text.trim().length ?? 0) > 0;\n const isSkipped = draft?.skipped === true;\n return (\n <div\n className=\"flex flex-col gap-4\"\n data-testid={`enem-classroom-survey-${question.id}`}\n >\n {illustration && (\n <img\n src={illustration}\n alt=\"\"\n aria-hidden\n className=\"w-[220px] max-w-full self-center\"\n data-testid=\"enem-classroom-survey-illustration\"\n />\n )}\n <Text\n id={`enem-classroom-survey-prompt-${question.id}`}\n size=\"lg\"\n weight=\"bold\"\n className=\"text-text-950\"\n >\n {question.prompt}\n </Text>\n <div className=\"flex flex-col gap-2\">\n <Text\n as=\"label\"\n htmlFor={`enem-classroom-survey-input-${question.id}`}\n size=\"sm\"\n className=\"text-text-600\"\n >\n {question.inputLabel}\n </Text>\n <Input\n id={`enem-classroom-survey-input-${question.id}`}\n variant=\"rounded\"\n placeholder={question.inputPlaceholder}\n value={draft?.text ?? ''}\n maxLength={255}\n disabled={isSkipped}\n onChange={(event: ChangeEvent<HTMLInputElement>) =>\n updateAnswer(question.id, { text: event.target.value })\n }\n data-testid=\"enem-classroom-survey-input\"\n />\n </div>\n\n <CheckBox\n label={question.skipLabel}\n checked={isSkipped}\n disabled={hasText}\n onChange={(event: ChangeEvent<HTMLInputElement>) =>\n updateAnswer(question.id, { skipped: event.target.checked })\n }\n data-testid=\"enem-classroom-survey-skip\"\n />\n </div>\n );\n };\n\n const renderSummaryStep = () => (\n <div\n className=\"flex flex-col items-center gap-6 text-center\"\n data-testid=\"enem-classroom-summary\"\n >\n <span className=\"flex items-center justify-center w-14 h-14 rounded-full bg-success-200 text-success-950\">\n <CheckIcon size={24} aria-hidden />\n </span>\n <div className=\"flex flex-col gap-2\">\n <Text size=\"xl\" weight=\"bold\" className=\"text-text-950\">\n Boa sorte!\n </Text>\n <Text size=\"sm\" className=\"text-text-600\">\n Sua simulação do ENEM já está para começar e o cronômetro vai iniciar.\n Respira fundo e dá o seu melhor!\n </Text>\n </div>\n\n <div\n className={cn(CARD_CLASSES, 'flex flex-col gap-3 w-full text-left p-4')}\n >\n <Text size=\"sm\" weight=\"bold\" className=\"text-text-950\">\n Suas respostas\n </Text>\n <dl className=\"flex flex-col gap-2\">\n {exam.languageChoice && (\n <SummaryRow\n label=\"Idioma\"\n value={language ? ENEM_CLASSROOM_LANGUAGE_LABELS[language] : '—'}\n />\n )}\n {surveyQuestions.map((question) => (\n <SummaryRow\n key={question.id}\n label={question.stepLabel}\n value={describeSurveyAnswer(question, answers[question.id])}\n />\n ))}\n </dl>\n <Button\n size=\"medium\"\n variant=\"outline\"\n action=\"primary\"\n className=\"self-center\"\n disabled={isStarting}\n onClick={() => setStep(FIRST_CHOICE_STEP)}\n data-testid=\"enem-classroom-review\"\n >\n Revisar minhas respostas\n </Button>\n </div>\n\n <Button\n size=\"large\"\n variant=\"solid\"\n action=\"primary\"\n className={cn('w-full', GRADIENT_CTA_CLASSES)}\n disabled={isStarting}\n onClick={handleStart}\n data-testid=\"enem-classroom-start\"\n >\n Iniciar a prova!\n </Button>\n </div>\n );\n\n const renderStepContent = () => {\n if (currentStep.kind === 'intro') return renderIntroStep();\n if (currentStep.kind === 'language') return renderLanguageStep();\n if (currentStep.kind === 'survey')\n return renderSurveyStep(currentStep.question, currentStep.index);\n return renderSummaryStep();\n };\n\n const showNavigation = step >= FIRST_CHOICE_STEP && step < summaryStep;\n const showStepper = step < summaryStep;\n\n const footer = showNavigation ? (\n <div className=\"flex flex-row justify-end gap-2 w-full\">\n <Button\n size=\"medium\"\n variant=\"outline\"\n action=\"primary\"\n iconLeft={<CaretLeftIcon size={16} aria-hidden />}\n onClick={() => setStep((current) => current - 1)}\n data-testid=\"enem-classroom-previous\"\n >\n Anterior\n </Button>\n <Button\n size=\"medium\"\n variant=\"solid\"\n action=\"primary\"\n iconRight={<CaretRightIcon size={16} aria-hidden />}\n disabled={!canGoNext}\n onClick={() => setStep((current) => current + 1)}\n data-testid=\"enem-classroom-next\"\n >\n Próximo\n </Button>\n </div>\n ) : undefined;\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n /*\n * A tela de resumo abre com o header vazio, só o X — quem dá o título\n * ali é o \"Boa sorte!\". O texto continua no <h2>, apenas oculto: o\n * `aria-labelledby` do Modal aponta pra ele, e um h2 vazio deixaria o\n * dialog sem nome acessível.\n */\n title={\n showStepper ? exam.title : <span className=\"sr-only\">{exam.title}</span>\n }\n size=\"lg\"\n footer={footer}\n contentClassName=\"flex flex-col gap-6\"\n >\n {showStepper && (\n <Stepper steps={stepperSteps} currentStep={step} size=\"small\" />\n )}\n {renderStepContent()}\n </Modal>\n );\n};\n\nexport default EnemClassroomStartModal;\n"],"mappings":"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","names":[]}
@@ -1,4 +1,12 @@
1
1
  import { type QuestionAnswerResult, type TrueOrFalseOptionState } from '../../Quiz/Quiz.types/index';
2
+ import { type QuizInterface } from '../../Quiz/useQuizStore/index';
3
+ /**
4
+ * Whether the banca/year line goes on the questions of a quiz: always on a
5
+ * SIMULADO, and on any quiz that asks for it (`showExamInfo`).
6
+ * @param quiz - The quiz, or null before one is loaded
7
+ * @returns true when the exam info is shown
8
+ */
9
+ export declare const shouldShowExamInfo: (quiz: Pick<QuizInterface, "type" | "showExamInfo"> | null) => boolean;
2
10
  /**
3
11
  * Helper function to format exam info (bank - year)
4
12
  * @param examBoard - The exam board/bank name
@@ -1 +1 @@
1
- {"version":3,"file":"Quiz.utils.d.ts","sourceRoot":"","sources":["../../../src/components/Quiz/Quiz.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAItB;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,UAAU,MAAM,GAAG,IAAI,GAAG,SAAS,KAClC,MAWF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,MAAM,KAAG,CAAC,EAmB9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,MAKlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAU,MAAM,EAChB,SAAS,MAAM,EACf,uBAAuB,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC9D,cAAc,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACnC,sBA0DF,CAAC"}
1
+ {"version":3,"file":"Quiz.utils.d.ts","sourceRoot":"","sources":["../../../src/components/Quiz/Quiz.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,CAAC,GAAG,IAAI,KACxD,OAA2E,CAAC;AAE/E;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,UAAU,MAAM,GAAG,IAAI,GAAG,SAAS,KAClC,MAWF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,MAAM,KAAG,CAAC,EAmB9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,MAKlE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAU,MAAM,EAChB,SAAS,MAAM,EACf,uBAAuB,oBAAoB,GAAG,IAAI,GAAG,SAAS,EAC9D,cAAc,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACnC,sBA0DF,CAAC"}