analytica-frontend-lib 1.7.21 → 1.7.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/dist/AccessReportModal/index.js +3 -3
  2. package/dist/AccessReportModal/index.mjs +2 -2
  3. package/dist/ActivitiesHistory/index.js +49 -49
  4. package/dist/ActivitiesHistory/index.mjs +48 -48
  5. package/dist/ActivityCardQuestionBanks/index.js +49 -49
  6. package/dist/ActivityCardQuestionBanks/index.mjs +48 -48
  7. package/dist/ActivityCardQuestionPreview/index.js +49 -49
  8. package/dist/ActivityCardQuestionPreview/index.mjs +48 -48
  9. package/dist/ActivityCreate/ActivityCreateContent/index.js +49 -49
  10. package/dist/ActivityCreate/ActivityCreateContent/index.mjs +48 -48
  11. package/dist/ActivityCreate/index.js +49 -49
  12. package/dist/ActivityCreate/index.mjs +48 -48
  13. package/dist/ActivityDetails/index.js +49 -49
  14. package/dist/ActivityDetails/index.mjs +48 -48
  15. package/dist/ActivityFilters/index.js +49 -49
  16. package/dist/ActivityFilters/index.mjs +48 -48
  17. package/dist/ActivityPageLayout/index.js +49 -49
  18. package/dist/ActivityPageLayout/index.mjs +48 -48
  19. package/dist/ActivityPreview/index.js +49 -49
  20. package/dist/ActivityPreview/index.mjs +48 -48
  21. package/dist/AlertManager/index.js +49 -49
  22. package/dist/AlertManager/index.mjs +48 -48
  23. package/dist/AlertManagerView/index.js +3 -3
  24. package/dist/AlertManagerView/index.mjs +2 -2
  25. package/dist/AnnouncementModal/index.js +3 -3
  26. package/dist/AnnouncementModal/index.mjs +2 -2
  27. package/dist/AnnouncementsGate/index.js +4 -4
  28. package/dist/AnnouncementsGate/index.mjs +3 -3
  29. package/dist/AnswerKeyModal/index.js +3 -3
  30. package/dist/AnswerKeyModal/index.mjs +2 -2
  31. package/dist/AppHeader/index.js +6 -6
  32. package/dist/AppHeader/index.mjs +5 -5
  33. package/dist/CalendarCard/index.js +4 -4
  34. package/dist/CalendarCard/index.mjs +3 -3
  35. package/dist/Chat/index.js +3 -3
  36. package/dist/Chat/index.mjs +2 -2
  37. package/dist/CheckBox/index.d.ts.map +1 -1
  38. package/dist/CheckBox/index.js +2 -2
  39. package/dist/CheckBox/index.mjs +1 -1
  40. package/dist/CheckBoxGroup/index.js +49 -49
  41. package/dist/CheckBoxGroup/index.mjs +48 -48
  42. package/dist/ComparatorChart/ComparatorView/index.js +3 -3
  43. package/dist/ComparatorChart/ComparatorView/index.mjs +2 -2
  44. package/dist/CorrectActivityModal/index.js +7 -7
  45. package/dist/CorrectActivityModal/index.mjs +6 -6
  46. package/dist/DownloadButton/index.js +4 -4
  47. package/dist/DownloadButton/index.mjs +3 -3
  48. package/dist/DownloadModal/index.js +3 -3
  49. package/dist/DownloadModal/index.mjs +2 -2
  50. package/dist/DropdownMenu/index.js +3 -3
  51. package/dist/DropdownMenu/index.mjs +2 -2
  52. package/dist/EnemClassroomStartModal/index.d.ts.map +1 -1
  53. package/dist/EnemClassroomStartModal/index.js +7 -7
  54. package/dist/EnemClassroomStartModal/index.mjs +6 -6
  55. package/dist/EnemMoment/index.js +57 -57
  56. package/dist/EnemMoment/index.mjs +51 -51
  57. package/dist/EnemMomentStudentModal/index.js +50 -50
  58. package/dist/EnemMomentStudentModal/index.mjs +48 -48
  59. package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +49 -49
  60. package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +48 -48
  61. package/dist/ExamPageLayout/index.js +49 -49
  62. package/dist/ExamPageLayout/index.mjs +48 -48
  63. package/dist/Forum/index.js +5 -5
  64. package/dist/Forum/index.mjs +4 -4
  65. package/dist/KnowledgePerformanceCard/index.js +51 -51
  66. package/dist/KnowledgePerformanceCard/index.mjs +50 -50
  67. package/dist/LessonCardPreview/index.js +49 -49
  68. package/dist/LessonCardPreview/index.mjs +48 -48
  69. package/dist/LessonMediaSections/index.js +49 -49
  70. package/dist/LessonMediaSections/index.mjs +48 -48
  71. package/dist/LessonsCatalog/index.js +49 -49
  72. package/dist/LessonsCatalog/index.mjs +48 -48
  73. package/dist/Modal/index.d.ts.map +1 -1
  74. package/dist/Modal/index.js +2 -2
  75. package/dist/Modal/index.mjs +1 -1
  76. package/dist/MultiCheckSelect/index.js +5 -5
  77. package/dist/MultiCheckSelect/index.mjs +4 -4
  78. package/dist/MultiSearchSelect/index.js +3 -3
  79. package/dist/MultiSearchSelect/index.mjs +2 -2
  80. package/dist/MultipleChoice/index.js +3 -3
  81. package/dist/MultipleChoice/index.mjs +2 -2
  82. package/dist/NotificationCard/index.js +4 -4
  83. package/dist/NotificationCard/index.mjs +3 -3
  84. package/dist/PerformanceReportModal/index.js +3 -3
  85. package/dist/PerformanceReportModal/index.mjs +2 -2
  86. package/dist/PrintableUsersTable/index.js +49 -49
  87. package/dist/PrintableUsersTable/index.mjs +48 -48
  88. package/dist/ProgressModal/index.js +3 -3
  89. package/dist/ProgressModal/index.mjs +2 -2
  90. package/dist/QuestionsPerformanceCard/index.js +50 -50
  91. package/dist/QuestionsPerformanceCard/index.mjs +49 -49
  92. package/dist/Quiz/index.js +6 -6
  93. package/dist/Quiz/index.mjs +5 -5
  94. package/dist/RecommendedLessonCreate/index.js +49 -49
  95. package/dist/RecommendedLessonCreate/index.mjs +48 -48
  96. package/dist/RecommendedLessonDetails/index.js +7 -7
  97. package/dist/RecommendedLessonDetails/index.mjs +6 -6
  98. package/dist/RecommendedLessonsHistory/index.js +49 -49
  99. package/dist/RecommendedLessonsHistory/index.mjs +48 -48
  100. package/dist/ReportDetailModal/index.js +4 -4
  101. package/dist/ReportDetailModal/index.mjs +3 -3
  102. package/dist/RichEditor/index.js +5 -5
  103. package/dist/RichEditor/index.mjs +4 -4
  104. package/dist/{RichEditorCore-6MTYSSKS.mjs → RichEditorCore-2S5YUAOI.mjs} +5 -5
  105. package/dist/{RichEditorCore-KDARBEK4.js → RichEditorCore-N7A2445B.js} +16 -16
  106. package/dist/{RichEditorCore-KDARBEK4.js.map → RichEditorCore-N7A2445B.js.map} +1 -1
  107. package/dist/Search/index.js +4 -4
  108. package/dist/Search/index.mjs +3 -3
  109. package/dist/SendActivityModal/SendActivityModal.js +49 -49
  110. package/dist/SendActivityModal/SendActivityModal.mjs +48 -48
  111. package/dist/SendActivityModal/index.js +49 -49
  112. package/dist/SendActivityModal/index.mjs +48 -48
  113. package/dist/SendLessonModal/index.js +49 -49
  114. package/dist/SendLessonModal/index.mjs +48 -48
  115. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +49 -49
  116. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +48 -48
  117. package/dist/SimulatedPerformance/useSimulatedPerformance/index.js +5 -5
  118. package/dist/SimulatedPerformance/useSimulatedPerformance/index.mjs +4 -4
  119. package/dist/SimulatedStudentSimulationsModal/index.js +6 -6
  120. package/dist/SimulatedStudentSimulationsModal/index.mjs +5 -5
  121. package/dist/SimulationsPage/index.js +49 -49
  122. package/dist/SimulationsPage/index.mjs +48 -48
  123. package/dist/StudentLessonProgressModal/index.js +3 -3
  124. package/dist/StudentLessonProgressModal/index.mjs +2 -2
  125. package/dist/StudentPerformanceDetailsModal/index.js +5 -5
  126. package/dist/StudentPerformanceDetailsModal/index.mjs +4 -4
  127. package/dist/Support/TicketModal/index.js +3 -3
  128. package/dist/Support/TicketModal/index.mjs +2 -2
  129. package/dist/Support/index.js +4 -4
  130. package/dist/Support/index.mjs +3 -3
  131. package/dist/SupportChatComposer/index.js +3 -3
  132. package/dist/SupportChatComposer/index.mjs +2 -2
  133. package/dist/SupportChatEvent/index.js +3 -3
  134. package/dist/SupportChatEvent/index.mjs +2 -2
  135. package/dist/SupportChatMessage/index.js +3 -3
  136. package/dist/SupportChatMessage/index.mjs +2 -2
  137. package/dist/SupportChatWidget/index.js +3 -3
  138. package/dist/SupportChatWidget/index.mjs +2 -2
  139. package/dist/SupportConcludeModal/index.js +4 -4
  140. package/dist/SupportConcludeModal/index.mjs +3 -3
  141. package/dist/SupportConversationHeader/index.js +4 -4
  142. package/dist/SupportConversationHeader/index.mjs +3 -3
  143. package/dist/SupportConversationMessages/index.js +5 -5
  144. package/dist/SupportConversationMessages/index.mjs +4 -4
  145. package/dist/SupportTicketConversation/index.js +12 -12
  146. package/dist/SupportTicketConversation/index.mjs +11 -11
  147. package/dist/TableProvider/index.js +49 -49
  148. package/dist/TableProvider/index.mjs +48 -48
  149. package/dist/UnifiedDraftModelPage/index.js +49 -49
  150. package/dist/UnifiedDraftModelPage/index.mjs +48 -48
  151. package/dist/UnifiedHistoryPage/index.js +49 -49
  152. package/dist/UnifiedHistoryPage/index.mjs +48 -48
  153. package/dist/VideoPlayer/index.js +5 -5
  154. package/dist/VideoPlayer/index.mjs +4 -4
  155. package/dist/Whiteboard/index.js +3 -3
  156. package/dist/Whiteboard/index.mjs +2 -2
  157. package/dist/{chunk-B6QOBHZM.mjs → chunk-24BEZK43.mjs} +2 -2
  158. package/dist/{chunk-CYSODZGX.js → chunk-27Y6V3GM.js} +169 -66
  159. package/dist/chunk-27Y6V3GM.js.map +1 -0
  160. package/dist/{chunk-OQTXTDSK.mjs → chunk-2OJ3QSLU.mjs} +3 -3
  161. package/dist/{chunk-VDUO65VK.mjs → chunk-3EGJJDIM.mjs} +2 -2
  162. package/dist/{chunk-VDUO65VK.mjs.map → chunk-3EGJJDIM.mjs.map} +1 -1
  163. package/dist/{chunk-4OUBPEVS.mjs → chunk-3HKLGNUE.mjs} +3 -3
  164. package/dist/{chunk-YA7XLH7D.js → chunk-3MFACEZ2.js} +3 -3
  165. package/dist/{chunk-YA7XLH7D.js.map → chunk-3MFACEZ2.js.map} +1 -1
  166. package/dist/{chunk-IHBWJM4C.mjs → chunk-3Q7RJKUP.mjs} +2 -2
  167. package/dist/{chunk-PY6WD6SB.mjs → chunk-3WRB4WCD.mjs} +4 -4
  168. package/dist/{chunk-4RHCBMMP.js → chunk-46VHAL4S.js} +3 -3
  169. package/dist/{chunk-4RHCBMMP.js.map → chunk-46VHAL4S.js.map} +1 -1
  170. package/dist/{chunk-POT3ALY3.js → chunk-5DINXU6W.js} +1 -1
  171. package/dist/{chunk-POT3ALY3.js.map → chunk-5DINXU6W.js.map} +1 -1
  172. package/dist/{chunk-L52NFNFK.mjs → chunk-5VC4BK65.mjs} +3 -3
  173. package/dist/{chunk-B67RLIUM.js → chunk-72D7G6YQ.js} +3 -3
  174. package/dist/{chunk-B67RLIUM.js.map → chunk-72D7G6YQ.js.map} +1 -1
  175. package/dist/{chunk-C65DG2RI.mjs → chunk-7FSBWUOM.mjs} +2 -2
  176. package/dist/{chunk-QWMFWYII.js → chunk-7IZNLARX.js} +3 -3
  177. package/dist/{chunk-QWMFWYII.js.map → chunk-7IZNLARX.js.map} +1 -1
  178. package/dist/{chunk-ZCBT2BUZ.js → chunk-7RFHXFFV.js} +13 -13
  179. package/dist/{chunk-ZCBT2BUZ.js.map → chunk-7RFHXFFV.js.map} +1 -1
  180. package/dist/{chunk-EMWX6IKP.js → chunk-7VJP5D26.js} +13 -13
  181. package/dist/{chunk-EMWX6IKP.js.map → chunk-7VJP5D26.js.map} +1 -1
  182. package/dist/{chunk-HCQJR5CE.mjs → chunk-7ZWEBNFB.mjs} +2 -2
  183. package/dist/{chunk-3RHNJUXU.mjs → chunk-ACSEJOW6.mjs} +2 -2
  184. package/dist/{chunk-WQZHUW7C.mjs → chunk-ANFHLSDB.mjs} +3 -3
  185. package/dist/{chunk-H6NQOLQY.mjs → chunk-ANFLWCVD.mjs} +2 -2
  186. package/dist/{chunk-BKBDQXZX.js → chunk-AOYPUZRO.js} +3 -3
  187. package/dist/{chunk-BKBDQXZX.js.map → chunk-AOYPUZRO.js.map} +1 -1
  188. package/dist/{chunk-5WS6IENQ.mjs → chunk-AU2HLQDJ.mjs} +2 -2
  189. package/dist/{chunk-P6FX3NT6.mjs → chunk-AZGDHAP5.mjs} +2 -2
  190. package/dist/{chunk-NCGO7FPB.js → chunk-AZKNYREH.js} +8 -8
  191. package/dist/{chunk-NCGO7FPB.js.map → chunk-AZKNYREH.js.map} +1 -1
  192. package/dist/{chunk-R6MTPYYW.js → chunk-AZTBNERQ.js} +3 -3
  193. package/dist/{chunk-R6MTPYYW.js.map → chunk-AZTBNERQ.js.map} +1 -1
  194. package/dist/{chunk-NPB2JTYC.mjs → chunk-BO3QVQ37.mjs} +4 -4
  195. package/dist/{chunk-2WI4Q2I2.mjs → chunk-BPMWRFSI.mjs} +4 -4
  196. package/dist/{chunk-TEW33GLR.mjs → chunk-BXN3VO6I.mjs} +2 -2
  197. package/dist/{chunk-7ZB3YHF2.mjs → chunk-C6LC5LD5.mjs} +1 -1
  198. package/dist/{chunk-7ZB3YHF2.mjs.map → chunk-C6LC5LD5.mjs.map} +1 -1
  199. package/dist/{chunk-5HONWYQA.js → chunk-C7ILTLT5.js} +3 -3
  200. package/dist/{chunk-5HONWYQA.js.map → chunk-C7ILTLT5.js.map} +1 -1
  201. package/dist/{chunk-OES54VU6.mjs → chunk-CFV2OZ3P.mjs} +116 -13
  202. package/dist/chunk-CFV2OZ3P.mjs.map +1 -0
  203. package/dist/{chunk-4OVIHZ3I.js → chunk-CGXWWPEA.js} +6 -6
  204. package/dist/{chunk-4OVIHZ3I.js.map → chunk-CGXWWPEA.js.map} +1 -1
  205. package/dist/{chunk-MNU45FM3.js → chunk-COT4KW3H.js} +2 -2
  206. package/dist/{chunk-MNU45FM3.js.map → chunk-COT4KW3H.js.map} +1 -1
  207. package/dist/{chunk-JDQUXG75.js → chunk-CVDNCU6L.js} +3 -3
  208. package/dist/{chunk-JDQUXG75.js.map → chunk-CVDNCU6L.js.map} +1 -1
  209. package/dist/{chunk-HBZHWSMZ.mjs → chunk-D46PJSQ7.mjs} +13 -13
  210. package/dist/{chunk-ARYIJ4RU.js → chunk-DDORKDQO.js} +3 -3
  211. package/dist/{chunk-ARYIJ4RU.js.map → chunk-DDORKDQO.js.map} +1 -1
  212. package/dist/{chunk-7YQTDJFA.js → chunk-DKXSR2V3.js} +3 -3
  213. package/dist/{chunk-7YQTDJFA.js.map → chunk-DKXSR2V3.js.map} +1 -1
  214. package/dist/{chunk-I6EAPPBL.mjs → chunk-DZMYIOKZ.mjs} +5 -5
  215. package/dist/{chunk-SKM7NFPY.js → chunk-EPXAFJVW.js} +6 -6
  216. package/dist/{chunk-SKM7NFPY.js.map → chunk-EPXAFJVW.js.map} +1 -1
  217. package/dist/{chunk-AYYMS23L.js → chunk-ESIQ2GB4.js} +7 -7
  218. package/dist/{chunk-AYYMS23L.js.map → chunk-ESIQ2GB4.js.map} +1 -1
  219. package/dist/{chunk-NU2WNYOD.js → chunk-EW7HGUFV.js} +87 -87
  220. package/dist/{chunk-NU2WNYOD.js.map → chunk-EW7HGUFV.js.map} +1 -1
  221. package/dist/{chunk-I4QGQMXI.mjs → chunk-EXYXZFNK.mjs} +2 -2
  222. package/dist/{chunk-RBJAO57A.js → chunk-FAAR4XC5.js} +3 -3
  223. package/dist/{chunk-RBJAO57A.js.map → chunk-FAAR4XC5.js.map} +1 -1
  224. package/dist/{chunk-N2VNE3ON.js → chunk-FDTDJDZI.js} +13 -13
  225. package/dist/{chunk-N2VNE3ON.js.map → chunk-FDTDJDZI.js.map} +1 -1
  226. package/dist/{chunk-SU5Q64TW.mjs → chunk-FVCTKS33.mjs} +6 -5
  227. package/dist/chunk-FVCTKS33.mjs.map +1 -0
  228. package/dist/{chunk-AVHZHWI3.js → chunk-GBIFSFWJ.js} +15 -15
  229. package/dist/{chunk-AVHZHWI3.js.map → chunk-GBIFSFWJ.js.map} +1 -1
  230. package/dist/{chunk-KSUPPAKH.js → chunk-GIQSHTB5.js} +9 -9
  231. package/dist/{chunk-KSUPPAKH.js.map → chunk-GIQSHTB5.js.map} +1 -1
  232. package/dist/{chunk-4QBKD4JN.js → chunk-GUUJYGXF.js} +3 -3
  233. package/dist/{chunk-4QBKD4JN.js.map → chunk-GUUJYGXF.js.map} +1 -1
  234. package/dist/{chunk-M4O46VDY.mjs → chunk-GX2PKLN2.mjs} +2 -2
  235. package/dist/{chunk-5BUTINIM.mjs → chunk-HLVLSWKD.mjs} +3 -3
  236. package/dist/{chunk-FU23WOTY.js → chunk-HSV5VZ4Z.js} +6 -6
  237. package/dist/{chunk-FU23WOTY.js.map → chunk-HSV5VZ4Z.js.map} +1 -1
  238. package/dist/{chunk-MMS2ELDK.mjs → chunk-IIQCS3A2.mjs} +2 -2
  239. package/dist/{chunk-NKWLCZUZ.mjs → chunk-JGGUMHM6.mjs} +2 -2
  240. package/dist/{chunk-HT2GREFH.js → chunk-JLD46DL7.js} +11 -11
  241. package/dist/{chunk-HT2GREFH.js.map → chunk-JLD46DL7.js.map} +1 -1
  242. package/dist/{chunk-NVL5TKWD.mjs → chunk-JYFLOQYT.mjs} +2 -2
  243. package/dist/{chunk-DWGDEUEW.mjs → chunk-KG563P7F.mjs} +5 -5
  244. package/dist/{chunk-TCIC5556.mjs → chunk-KH4TEDGD.mjs} +2 -2
  245. package/dist/{chunk-23PAYKWB.js → chunk-KM27ZDHZ.js} +2 -2
  246. package/dist/{chunk-23PAYKWB.js.map → chunk-KM27ZDHZ.js.map} +1 -1
  247. package/dist/{chunk-XZUMD3CB.mjs → chunk-LODDYDTP.mjs} +2 -2
  248. package/dist/{chunk-XZUMD3CB.mjs.map → chunk-LODDYDTP.mjs.map} +1 -1
  249. package/dist/{chunk-E6CGJBID.mjs → chunk-LUHKMY56.mjs} +2 -2
  250. package/dist/{chunk-TVLEUNKE.mjs → chunk-LZP5VZCO.mjs} +2 -2
  251. package/dist/{chunk-EHNEBXS7.mjs → chunk-M6DXZ2FM.mjs} +2 -2
  252. package/dist/{chunk-B4C5DWAP.mjs → chunk-MMMJDO75.mjs} +2 -2
  253. package/dist/{chunk-AFCWMJQO.js → chunk-MOUPNP53.js} +3 -3
  254. package/dist/{chunk-AFCWMJQO.js.map → chunk-MOUPNP53.js.map} +1 -1
  255. package/dist/{chunk-36KFIZ43.js → chunk-MYPUZ6EG.js} +3 -3
  256. package/dist/{chunk-36KFIZ43.js.map → chunk-MYPUZ6EG.js.map} +1 -1
  257. package/dist/{chunk-6JHIKIPA.js → chunk-NUGLTWEA.js} +23 -23
  258. package/dist/{chunk-6JHIKIPA.js.map → chunk-NUGLTWEA.js.map} +1 -1
  259. package/dist/{chunk-KWXOYHYK.mjs → chunk-P7MORXSE.mjs} +2 -2
  260. package/dist/{chunk-YG4IZ5Q3.mjs → chunk-Q2OSEROT.mjs} +2 -2
  261. package/dist/{chunk-L5SXG6T6.mjs → chunk-QQKOHKO2.mjs} +3 -3
  262. package/dist/{chunk-SADSJ2MV.js → chunk-QVGUYPXO.js} +3 -3
  263. package/dist/{chunk-SADSJ2MV.js.map → chunk-QVGUYPXO.js.map} +1 -1
  264. package/dist/{chunk-K45PTE7F.mjs → chunk-RBKQXT73.mjs} +4 -4
  265. package/dist/{chunk-JZFISWTP.js → chunk-RIN2IN7E.js} +2 -2
  266. package/dist/{chunk-JZFISWTP.js.map → chunk-RIN2IN7E.js.map} +1 -1
  267. package/dist/{chunk-BXLAOYAG.mjs → chunk-RIUVPTG7.mjs} +2 -2
  268. package/dist/{chunk-QVB54LBS.mjs → chunk-RNZGX6MX.mjs} +3 -3
  269. package/dist/{chunk-VSVLIRO2.js → chunk-RXEU77VN.js} +15 -15
  270. package/dist/{chunk-VSVLIRO2.js.map → chunk-RXEU77VN.js.map} +1 -1
  271. package/dist/{chunk-ZRIYCU2V.js → chunk-S2BZPPAI.js} +5 -5
  272. package/dist/{chunk-ZRIYCU2V.js.map → chunk-S2BZPPAI.js.map} +1 -1
  273. package/dist/{chunk-MES2BP4T.mjs → chunk-SGL5IUCV.mjs} +2 -2
  274. package/dist/{chunk-7LS5UQCQ.mjs → chunk-STKUPDHG.mjs} +2 -2
  275. package/dist/{chunk-PPYCCT2K.js → chunk-SWDVSCQJ.js} +9 -8
  276. package/dist/chunk-SWDVSCQJ.js.map +1 -0
  277. package/dist/{chunk-JCA76TCX.mjs → chunk-SZKLJOTN.mjs} +2 -2
  278. package/dist/{chunk-F5WR6XZZ.js → chunk-T73HPYF2.js} +3 -3
  279. package/dist/{chunk-F5WR6XZZ.js.map → chunk-T73HPYF2.js.map} +1 -1
  280. package/dist/{chunk-ZB6HDDE4.js → chunk-TQO37PVW.js} +14 -14
  281. package/dist/{chunk-ZB6HDDE4.js.map → chunk-TQO37PVW.js.map} +1 -1
  282. package/dist/{chunk-VKQ5AJDT.mjs → chunk-UENZQF2Y.mjs} +2 -2
  283. package/dist/{chunk-DCG7EUBC.js → chunk-UWLW5YVB.js} +6 -6
  284. package/dist/{chunk-DCG7EUBC.js.map → chunk-UWLW5YVB.js.map} +1 -1
  285. package/dist/{chunk-37AJ2GVO.mjs → chunk-UXJIZDXQ.mjs} +2 -2
  286. package/dist/{chunk-PLLPFCN3.js → chunk-UXMDPRAI.js} +14 -14
  287. package/dist/{chunk-PLLPFCN3.js.map → chunk-UXMDPRAI.js.map} +1 -1
  288. package/dist/{chunk-7BOTKRY6.mjs → chunk-V55DG5B4.mjs} +2 -2
  289. package/dist/{chunk-YRTIDZTN.js → chunk-VHEDMUAW.js} +4 -4
  290. package/dist/{chunk-YRTIDZTN.js.map → chunk-VHEDMUAW.js.map} +1 -1
  291. package/dist/{chunk-4RD55E4R.js → chunk-VWLWSO4J.js} +3 -3
  292. package/dist/{chunk-4RD55E4R.js.map → chunk-VWLWSO4J.js.map} +1 -1
  293. package/dist/{chunk-VJQIKFMG.mjs → chunk-W3CWEZDB.mjs} +3 -3
  294. package/dist/{chunk-JM6SLU7D.js → chunk-W7NNH4VY.js} +6 -6
  295. package/dist/{chunk-JM6SLU7D.js.map → chunk-W7NNH4VY.js.map} +1 -1
  296. package/dist/{chunk-VQ7MSYIH.js → chunk-WUN5NHZE.js} +4 -4
  297. package/dist/{chunk-VQ7MSYIH.js.map → chunk-WUN5NHZE.js.map} +1 -1
  298. package/dist/{chunk-AAU7ABJN.js → chunk-WWTRSQ3X.js} +3 -3
  299. package/dist/{chunk-AAU7ABJN.js.map → chunk-WWTRSQ3X.js.map} +1 -1
  300. package/dist/{chunk-BR6D2TGF.js → chunk-WZOA2PVJ.js} +3 -3
  301. package/dist/{chunk-BR6D2TGF.js.map → chunk-WZOA2PVJ.js.map} +1 -1
  302. package/dist/{chunk-TZKR5Z4J.mjs → chunk-XJF2F36N.mjs} +2 -2
  303. package/dist/{chunk-P6ACVNQK.mjs → chunk-YD2XBEKO.mjs} +2 -2
  304. package/dist/{chunk-LMXWIPGR.js → chunk-YWPLHFAK.js} +3 -3
  305. package/dist/{chunk-LMXWIPGR.js.map → chunk-YWPLHFAK.js.map} +1 -1
  306. package/dist/{chunk-DCEWWHV5.mjs → chunk-Z3HGYPH7.mjs} +2 -2
  307. package/dist/{chunk-Q3PKW6TH.js → chunk-ZCV3HWI2.js} +6 -6
  308. package/dist/{chunk-Q3PKW6TH.js.map → chunk-ZCV3HWI2.js.map} +1 -1
  309. package/dist/{chunk-OSA3YVDZ.mjs → chunk-ZF46DCAC.mjs} +2 -2
  310. package/dist/{chunk-5T6IDXSB.js → chunk-ZO2GA23I.js} +3 -3
  311. package/dist/{chunk-5T6IDXSB.js.map → chunk-ZO2GA23I.js.map} +1 -1
  312. package/dist/{chunk-ZN3763VZ.js → chunk-ZQLW4EJB.js} +9 -9
  313. package/dist/{chunk-ZN3763VZ.js.map → chunk-ZQLW4EJB.js.map} +1 -1
  314. package/dist/{chunk-CM5R4SPF.js → chunk-ZQQJTO6T.js} +3 -3
  315. package/dist/{chunk-CM5R4SPF.js.map → chunk-ZQQJTO6T.js.map} +1 -1
  316. package/dist/{chunk-SEL4QDCO.js → chunk-ZSXSAE3D.js} +3 -3
  317. package/dist/{chunk-SEL4QDCO.js.map → chunk-ZSXSAE3D.js.map} +1 -1
  318. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts +6 -1
  319. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts.map +1 -1
  320. package/dist/hooks/useAppContent/index.js +49 -49
  321. package/dist/hooks/useAppContent/index.mjs +48 -48
  322. package/dist/hooks/useSupportChat.d.ts +2 -0
  323. package/dist/hooks/useSupportChat.d.ts.map +1 -1
  324. package/dist/index.js +49 -49
  325. package/dist/index.mjs +48 -48
  326. package/dist/types/supportChat/index.d.ts +14 -0
  327. package/dist/types/supportChat/index.d.ts.map +1 -1
  328. package/dist/types/supportChat/index.js +2 -2
  329. package/dist/types/supportChat/index.mjs +1 -1
  330. package/dist/types/supportChat.d.ts +14 -0
  331. package/dist/types/supportChat.d.ts.map +1 -1
  332. package/package.json +1 -1
  333. package/dist/chunk-CYSODZGX.js.map +0 -1
  334. package/dist/chunk-OES54VU6.mjs.map +0 -1
  335. package/dist/chunk-PPYCCT2K.js.map +0 -1
  336. package/dist/chunk-SU5Q64TW.mjs.map +0 -1
  337. /package/dist/{RichEditorCore-6MTYSSKS.mjs.map → RichEditorCore-2S5YUAOI.mjs.map} +0 -0
  338. /package/dist/{chunk-B6QOBHZM.mjs.map → chunk-24BEZK43.mjs.map} +0 -0
  339. /package/dist/{chunk-OQTXTDSK.mjs.map → chunk-2OJ3QSLU.mjs.map} +0 -0
  340. /package/dist/{chunk-4OUBPEVS.mjs.map → chunk-3HKLGNUE.mjs.map} +0 -0
  341. /package/dist/{chunk-IHBWJM4C.mjs.map → chunk-3Q7RJKUP.mjs.map} +0 -0
  342. /package/dist/{chunk-PY6WD6SB.mjs.map → chunk-3WRB4WCD.mjs.map} +0 -0
  343. /package/dist/{chunk-L52NFNFK.mjs.map → chunk-5VC4BK65.mjs.map} +0 -0
  344. /package/dist/{chunk-C65DG2RI.mjs.map → chunk-7FSBWUOM.mjs.map} +0 -0
  345. /package/dist/{chunk-HCQJR5CE.mjs.map → chunk-7ZWEBNFB.mjs.map} +0 -0
  346. /package/dist/{chunk-3RHNJUXU.mjs.map → chunk-ACSEJOW6.mjs.map} +0 -0
  347. /package/dist/{chunk-WQZHUW7C.mjs.map → chunk-ANFHLSDB.mjs.map} +0 -0
  348. /package/dist/{chunk-H6NQOLQY.mjs.map → chunk-ANFLWCVD.mjs.map} +0 -0
  349. /package/dist/{chunk-5WS6IENQ.mjs.map → chunk-AU2HLQDJ.mjs.map} +0 -0
  350. /package/dist/{chunk-P6FX3NT6.mjs.map → chunk-AZGDHAP5.mjs.map} +0 -0
  351. /package/dist/{chunk-NPB2JTYC.mjs.map → chunk-BO3QVQ37.mjs.map} +0 -0
  352. /package/dist/{chunk-2WI4Q2I2.mjs.map → chunk-BPMWRFSI.mjs.map} +0 -0
  353. /package/dist/{chunk-TEW33GLR.mjs.map → chunk-BXN3VO6I.mjs.map} +0 -0
  354. /package/dist/{chunk-HBZHWSMZ.mjs.map → chunk-D46PJSQ7.mjs.map} +0 -0
  355. /package/dist/{chunk-I6EAPPBL.mjs.map → chunk-DZMYIOKZ.mjs.map} +0 -0
  356. /package/dist/{chunk-I4QGQMXI.mjs.map → chunk-EXYXZFNK.mjs.map} +0 -0
  357. /package/dist/{chunk-M4O46VDY.mjs.map → chunk-GX2PKLN2.mjs.map} +0 -0
  358. /package/dist/{chunk-5BUTINIM.mjs.map → chunk-HLVLSWKD.mjs.map} +0 -0
  359. /package/dist/{chunk-MMS2ELDK.mjs.map → chunk-IIQCS3A2.mjs.map} +0 -0
  360. /package/dist/{chunk-NKWLCZUZ.mjs.map → chunk-JGGUMHM6.mjs.map} +0 -0
  361. /package/dist/{chunk-NVL5TKWD.mjs.map → chunk-JYFLOQYT.mjs.map} +0 -0
  362. /package/dist/{chunk-DWGDEUEW.mjs.map → chunk-KG563P7F.mjs.map} +0 -0
  363. /package/dist/{chunk-TCIC5556.mjs.map → chunk-KH4TEDGD.mjs.map} +0 -0
  364. /package/dist/{chunk-E6CGJBID.mjs.map → chunk-LUHKMY56.mjs.map} +0 -0
  365. /package/dist/{chunk-TVLEUNKE.mjs.map → chunk-LZP5VZCO.mjs.map} +0 -0
  366. /package/dist/{chunk-EHNEBXS7.mjs.map → chunk-M6DXZ2FM.mjs.map} +0 -0
  367. /package/dist/{chunk-B4C5DWAP.mjs.map → chunk-MMMJDO75.mjs.map} +0 -0
  368. /package/dist/{chunk-KWXOYHYK.mjs.map → chunk-P7MORXSE.mjs.map} +0 -0
  369. /package/dist/{chunk-YG4IZ5Q3.mjs.map → chunk-Q2OSEROT.mjs.map} +0 -0
  370. /package/dist/{chunk-L5SXG6T6.mjs.map → chunk-QQKOHKO2.mjs.map} +0 -0
  371. /package/dist/{chunk-K45PTE7F.mjs.map → chunk-RBKQXT73.mjs.map} +0 -0
  372. /package/dist/{chunk-BXLAOYAG.mjs.map → chunk-RIUVPTG7.mjs.map} +0 -0
  373. /package/dist/{chunk-QVB54LBS.mjs.map → chunk-RNZGX6MX.mjs.map} +0 -0
  374. /package/dist/{chunk-MES2BP4T.mjs.map → chunk-SGL5IUCV.mjs.map} +0 -0
  375. /package/dist/{chunk-7LS5UQCQ.mjs.map → chunk-STKUPDHG.mjs.map} +0 -0
  376. /package/dist/{chunk-JCA76TCX.mjs.map → chunk-SZKLJOTN.mjs.map} +0 -0
  377. /package/dist/{chunk-VKQ5AJDT.mjs.map → chunk-UENZQF2Y.mjs.map} +0 -0
  378. /package/dist/{chunk-37AJ2GVO.mjs.map → chunk-UXJIZDXQ.mjs.map} +0 -0
  379. /package/dist/{chunk-7BOTKRY6.mjs.map → chunk-V55DG5B4.mjs.map} +0 -0
  380. /package/dist/{chunk-VJQIKFMG.mjs.map → chunk-W3CWEZDB.mjs.map} +0 -0
  381. /package/dist/{chunk-TZKR5Z4J.mjs.map → chunk-XJF2F36N.mjs.map} +0 -0
  382. /package/dist/{chunk-P6ACVNQK.mjs.map → chunk-YD2XBEKO.mjs.map} +0 -0
  383. /package/dist/{chunk-DCEWWHV5.mjs.map → chunk-Z3HGYPH7.mjs.map} +0 -0
  384. /package/dist/{chunk-OSA3YVDZ.mjs.map → chunk-ZF46DCAC.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-LMXWIPGR.js","../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;ACVA,8BAAsC;AACtC,+EAAmC;AAyc/B,+CAAA;AAvXJ,IAAM,wBAAA,EAA0B,CAAA,EAAA,GAC9B,OAAQ,UAAA,CACL,oBAAA,IAAwB,UAAA;AAO7B,IAAM,iBAAA,EAAmB,CAAC,GAAA,EAAA,GAAwB;AAChD,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,EAAI,IAAI,GAAA,CAAI,GAAA,kBAAK,UAAA,mBAAW,QAAA,6BAAU,SAAA,GAAU,kBAAkB,CAAA;AACxE,IAAA,IAAA,EAAM,CAAA,CAAE,QAAA;AAAA,EACV,EAAA,UAAQ;AAAA,EAER;AACA,EAAA,MAAM,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA,CAAE,CAAC,CAAA;AAChC,EAAA,MAAM,IAAA,EAAM,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA;AAChC,EAAA,OAAO,IAAA,EAAM,CAAA,EAAA,EAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,CAAC,CAAA,CAAE,WAAA,CAAY,EAAA,EAAI,MAAA;AACxD,CAAA;AAOA,IAAM,aAAA,EAAe,CAAC,WAAA,EAAA,GACpB,WAAA,CACG,WAAA,CAAY,CAAA,CACZ,UAAA,CAAW,cAAA,EAAgB,EAAE,CAAA,CAC7B,UAAA,CAAW,MAAA,EAAQ,GAAG,CAAA,CACtB,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AASpB,IAAM,iBAAA,EAAmB,CACvB,WAAA,EACA,GAAA,EACA,YAAA,EAAsB,MAAA,EAAA,GAEtB,CAAA,EAAA;AAagE;AAC5D,EAAA;AACwD,IAAA;AACjC,IAAA;AACgC,IAAA;AACnD,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAoBoB;AACgC,EAAA;AAEhC,EAAA;AACN,IAAA;AAC6C,MAAA;AACvD,IAAA;AACF,EAAA;AACoB,EAAA;AAC6B,IAAA;AACjD,EAAA;AAE6D,EAAA;AACZ,EAAA;AAE7C,EAAA;AACqC,IAAA;AAE9B,IAAA;AACmC,MAAA;AAChC,MAAA;AACgB,MAAA;AACN,MAAA;AACtB,IAAA;AAEqB,IAAA;AACP,EAAA;AACS,IAAA;AACjB,IAAA;AACR,EAAA;AACF;AAmBwE;AAC/B,EAAA;AAC3B,EAAA;AACI,EAAA;AACL,EAAA;AAEmB,EAAA;AACnB,EAAA;AACC,EAAA;AACd;AAawB;AACtB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACc,EAAA;AACH,EAAA;AACc;AAC+B,EAAA;AACd,EAAA;AAOgB,EAAA;AACjD,IAAA;AACsD,MAAA;AAC7D,IAAA;AACG,EAAA;AAMyD,EAAA;AACzB,IAAA;AAED,IAAA;AAC+B,MAAA;AACjE,IAAA;AAEoC,IAAA;AACnB,MAAA;AACP,QAAA;AACO,QAAA;AACN,QAAA;AACR,MAAA;AACH,IAAA;AAEyC,IAAA;AACxB,MAAA;AACP,QAAA;AACO,QAAA;AACN,QAAA;AACR,MAAA;AACH,IAAA;AAEuC,IAAA;AACtB,MAAA;AACP,QAAA;AACO,QAAA;AACN,QAAA;AACR,MAAA;AACH,IAAA;AAEO,IAAA;AACe,EAAA;AAQC,EAAA;AACoD,IAAA;AAC7C,MAAA;AACqB,QAAA;AACjD,MAAA;AAC4D,MAAA;AAEzB,MAAA;AAEb,MAAA;AACN,MAAA;AAE6B,MAAA;AACvC,QAAA;AACyB,0BAAA;AAErB,UAAA;AACC,YAAA;AACL,YAAA;AACiD,YAAA;AAClC,YAAA;AACM,cAAA;AACD,cAAA;AAChB,gBAAA;AAC6C,kBAAA;AAC7C,gBAAA;AACF,cAAA;AACF,YAAA;AACF,UAAA;AAEA,UAAA;AAC8B,0BAAA;AAChB,QAAA;AACd,0BAAA;AACO,YAAA;AAGS,YAAA;AAChB,UAAA;AACA,QAAA;AACiB,UAAA;AACf,YAAA;AACyD,cAAA;AACzD,YAAA;AACF,UAAA;AACF,QAAA;AACF,MAAA;AAEqB,MAAA;AACH,QAAA;AAClB,MAAA;AAEe,MAAA;AACjB,IAAA;AACmD,IAAA;AACrD,EAAA;AAU2B,EAAA;AACQ,IAAA;AACc,MAAA;AAChB,wBAAA;AAE3B,QAAA;AACO,UAAA;AAC4C,UAAA;AACnD,QAAA;AAE8B,wBAAA;AAC4B,QAAA;AAE5B,QAAA;AAC2B,UAAA;AACzD,QAAA;AACF,MAAA;AACF,IAAA;AACiD,IAAA;AACnD,EAAA;AAS+C,EAAA;AACd,IAAA;AAEc,IAAA;AACV,IAAA;AAEmB,IAAA;AAEvB,IAAA;AACzB,MAAA;AAEC,QAAA;AACwD,QAAA;AACrD,MAAA;AAEN,QAAA;AACF,MAAA;AACF,IAAA;AAEqB,IAAA;AACR,IAAA;AAET,IAAA;AACa,MAAA;AACqC,QAAA;AAC7C,MAAA;AACoC,QAAA;AAC3C,MAAA;AACc,IAAA;AACd,sBAAA;AACE,QAAA;AAGc,QAAA;AAChB,MAAA;AACA,IAAA;AACsB,MAAA;AACT,MAAA;AACf,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAG4C,EAAA;AAEnB,EAAA;AACjB,IAAA;AACT,EAAA;AAE0C,EAAA;AAGrB,EAAA;AACjB,oBAAA;AAAC,MAAA;AAAA,MAAA;AACqC,QAAA;AAC3B,QAAA;AACa,QAAA;AAIhB,QAAA;AAEK,QAAA;AACT,UAAA;AACiB,UAAA;AACnB,QAAA;AAAA,MAAA;AACF,IAAA;AAEA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACS,QAAA;AACO,QAAA;AAAC,QAAA;AACR,QAAA;AACR,QAAA;AAAA,MAAA;AACF,IAAA;AACF,EAAA;AAEJ;AAEe;AD3OqD;AACA;AACA;AACA","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-LMXWIPGR.js","sourcesContent":[null,"import { useCallback, useState } from 'react';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport IconButton from '../IconButton/IconButton';\nimport ProgressModal from '../ProgressModal/ProgressModal';\nimport { cn } from '../../utils/utils';\n\n/**\n * Download content interface for lesson materials\n */\nexport interface DownloadContent {\n /** Initial frame image URL */\n urlInitialFrame?: string;\n /** Final frame image URL */\n urlFinalFrame?: string;\n /** Podcast audio URL */\n urlPodcast?: string;\n /** Video URL */\n urlVideo?: string;\n}\n\n/**\n * Props for DownloadButton component\n */\nexport interface DownloadButtonProps {\n /** Content URLs to download */\n content: DownloadContent;\n /** Additional CSS classes */\n className?: string;\n /** Callback fired when download starts */\n onDownloadStart?: (contentType: string) => void;\n /** Callback fired when download completes */\n onDownloadComplete?: (contentType: string) => void;\n /** Callback fired when download fails */\n onDownloadError?: (contentType: string, error: Error) => void;\n /** Lesson title for download file naming */\n lessonTitle?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\n/** A single downloadable asset resolved from the lesson content */\ninterface DownloadItem {\n type: string;\n url: string;\n label: string;\n}\n\n/**\n * Minimal shape of the File System Access API we rely on.\n *\n * Typed locally because `showDirectoryPicker` is not in the DOM lib yet, and\n * because only Chromium ships it — every use goes through `supportsDirectoryPicker`.\n */\ninterface FileSystemWritable {\n write: (chunk: Uint8Array) => Promise<void>;\n close: () => Promise<void>;\n abort?: () => Promise<void>;\n}\n\ninterface FileSystemFileHandleLike {\n createWritable: () => Promise<FileSystemWritable>;\n}\n\ninterface FileSystemDirectoryHandleLike {\n getFileHandle: (\n name: string,\n options?: { create?: boolean }\n ) => Promise<FileSystemFileHandleLike>;\n}\n\ntype DirectoryPicker = (options?: {\n mode?: string;\n id?: string;\n}) => Promise<FileSystemDirectoryHandleLike>;\n\n/**\n * Whether the browser can stream downloads straight to a folder on disk.\n *\n * Chromium-based browsers ship the File System Access API; Safari and Firefox\n * do not, and fall back to plain per-file downloads.\n *\n * @returns Whether `showDirectoryPicker` is available\n */\nconst supportsDirectoryPicker = (): boolean =>\n typeof (globalThis as { showDirectoryPicker?: DirectoryPicker })\n .showDirectoryPicker === 'function';\n\n/**\n * Get file extension from URL\n * @param url - URL to extract extension from\n * @returns File extension or default\n */\nconst getFileExtension = (url: string): string => {\n try {\n const u = new URL(url, globalThis.location?.origin || 'http://localhost');\n url = u.pathname;\n } catch {\n // keep original url (likely relative)\n }\n const path = url.split(/[?#]/)[0];\n const dot = path.lastIndexOf('.');\n return dot > -1 ? path.slice(dot + 1).toLowerCase() : 'file';\n};\n\n/**\n * Slugify a lesson title for safe use in filenames\n * @param lessonTitle - Title of the lesson\n * @returns Sanitized slug\n */\nconst slugifyTitle = (lessonTitle: string): string =>\n lessonTitle\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s]/g, '')\n .replaceAll(/\\s+/g, '-')\n .substring(0, 50);\n\n/**\n * Generate the filename an asset is saved as\n * @param contentType - Type of content being downloaded\n * @param url - URL to get extension from\n * @param lessonTitle - Title of the lesson\n * @returns Generated filename\n */\nconst generateFilename = (\n contentType: string,\n url: string,\n lessonTitle: string = 'aula'\n): string =>\n `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;\n\n/**\n * Read the byte size of a remote file without downloading it.\n *\n * Weights per-file progress against the real payload — a lesson video can be\n * hundreds of MB while the board frames are ~1MB, so counting files would make\n * the bar jump. Returns 0 when the server omits `content-length` or the request\n * fails, and callers then fall back to counting files.\n *\n * @param url - URL to probe\n * @returns Size in bytes, or 0 when unknown\n */\nconst getRemoteFileSize = async (url: string): Promise<number> => {\n try {\n const response = await fetch(url, { method: 'HEAD', mode: 'cors' });\n if (!response.ok) return 0;\n return Number(response.headers.get('content-length')) || 0;\n } catch {\n return 0;\n }\n};\n\n/**\n * Stream a remote file straight into a file on disk.\n *\n * Nothing is buffered in memory: each chunk is written to the file handle as it\n * arrives. This is what makes a ~900MB lesson video downloadable — holding it\n * in a Blob (and zipping it) exhausts the tab's memory and silently drops the\n * assets queued behind it.\n *\n * @param url - URL to download\n * @param dirHandle - Directory the user granted write access to\n * @param filename - Name to save the file as\n * @param onBytes - Called with the byte length of each chunk written\n */\nconst streamToDisk = async (\n url: string,\n dirHandle: FileSystemDirectoryHandleLike,\n filename: string,\n onBytes: (chunkSize: number) => void\n): Promise<void> => {\n const response = await fetch(url, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`\n );\n }\n if (!response.body) {\n throw new Error('Response has no readable body');\n }\n\n const fileHandle = await dirHandle.getFileHandle(filename, { create: true });\n const writable = await fileHandle.createWritable();\n\n try {\n const reader = response.body.getReader();\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n await writable.write(value);\n onBytes(value.length);\n }\n\n await writable.close();\n } catch (error) {\n await writable.abort?.();\n throw error;\n }\n};\n\n/**\n * Save a file through a plain anchor click, letting the browser download it.\n *\n * Fallback for browsers without the File System Access API (Safari, Firefox).\n * Note the `download` attribute is ignored cross-origin, and the CDN does not\n * send `Content-Disposition: attachment`, so these browsers may open the asset\n * in a tab rather than save it. Fixing that properly means setting that header\n * on the CDN — it cannot be forced from the client.\n *\n * The browser streams the response to its own downloads folder, so memory is\n * not a concern — but it\n * cannot report progress, and firing several in a row is unreliable, which is\n * why the caller spaces them out.\n *\n * @param url - URL to download\n * @param filename - Suggested filename\n */\nconst triggerBrowserDownload = (url: string, filename: string): void => {\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n\n document.body.appendChild(link);\n link.click();\n link.remove();\n};\n\n/**\n * DownloadButton component for downloading lesson content\n *\n * Downloads every asset of a lesson (video, podcast and board frames), matching\n * the mobile app's \"baixar aula completa\". Where the browser supports it, the\n * user picks a folder and each file is streamed straight to disk with real\n * progress; elsewhere the browser downloads them one by one.\n *\n * @param props - DownloadButton component props\n * @returns Download button element\n */\nconst DownloadButton = ({\n content,\n className,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n lessonTitle = 'aula',\n disabled = false,\n}: DownloadButtonProps) => {\n const [isDownloading, setIsDownloading] = useState(false);\n const [progress, setProgress] = useState(0);\n\n /**\n * Check if URL is valid and not empty\n * @param url - URL to validate\n * @returns Whether URL is valid\n */\n const isValidUrl = useCallback((url?: string): boolean => {\n return Boolean(\n url && url.trim() !== '' && url !== 'undefined' && url !== 'null'\n );\n }, []);\n\n /**\n * Get available download content\n * @returns Array of available download items\n */\n const getAvailableContent = useCallback((): DownloadItem[] => {\n const downloads: DownloadItem[] = [];\n\n if (isValidUrl(content.urlVideo)) {\n downloads.push({ type: 'video', url: content.urlVideo!, label: 'Vídeo' });\n }\n\n if (isValidUrl(content.urlPodcast)) {\n downloads.push({\n type: 'podcast',\n url: content.urlPodcast!,\n label: 'Podcast',\n });\n }\n\n if (isValidUrl(content.urlInitialFrame)) {\n downloads.push({\n type: 'quadro-inicial',\n url: content.urlInitialFrame!,\n label: 'Quadro Inicial',\n });\n }\n\n if (isValidUrl(content.urlFinalFrame)) {\n downloads.push({\n type: 'quadro-final',\n url: content.urlFinalFrame!,\n label: 'Quadro Final',\n });\n }\n\n return downloads;\n }, [content, isValidUrl]);\n\n /**\n * Download every asset by streaming it into the folder the user picked.\n *\n * @param items - Assets to download\n * @param dirHandle - Directory the user granted write access to\n */\n const downloadToFolder = useCallback(\n async (items: DownloadItem[], dirHandle: FileSystemDirectoryHandleLike) => {\n const sizes = await Promise.all(\n items.map((item) => getRemoteFileSize(item.url))\n );\n const totalBytes = sizes.reduce((sum, size) => sum + size, 0);\n // No content-length anywhere: weight each file equally instead of by bytes.\n const weighByFile = totalBytes === 0;\n\n let downloadedBytes = 0;\n let succeeded = 0;\n\n for (const [index, item] of items.entries()) {\n try {\n onDownloadStart?.(item.type);\n\n await streamToDisk(\n item.url,\n dirHandle,\n generateFilename(item.type, item.url, lessonTitle),\n (chunkSize) => {\n downloadedBytes += chunkSize;\n if (!weighByFile) {\n setProgress(\n Math.min(99, Math.round((downloadedBytes / totalBytes) * 100))\n );\n }\n }\n );\n\n succeeded++;\n onDownloadComplete?.(item.type);\n } catch (error) {\n onDownloadError?.(\n item.type,\n error instanceof Error\n ? error\n : new Error(`Falha ao baixar ${item.label}`)\n );\n } finally {\n if (weighByFile) {\n setProgress(\n Math.min(99, Math.round(((index + 1) / items.length) * 100))\n );\n }\n }\n }\n\n if (succeeded === 0) {\n throw new Error('Nenhum conteúdo da aula pôde ser baixado');\n }\n\n setProgress(100);\n },\n [lessonTitle, onDownloadStart, onDownloadComplete, onDownloadError]\n );\n\n /**\n * Hand each asset to the browser's own downloader, one at a time.\n *\n * Used where the File System Access API is missing. Downloads are spaced out\n * because browsers throttle several programmatic downloads fired at once.\n *\n * @param items - Assets to download\n */\n const downloadViaBrowser = useCallback(\n async (items: DownloadItem[]) => {\n for (const [index, item] of items.entries()) {\n onDownloadStart?.(item.type);\n\n triggerBrowserDownload(\n item.url,\n generateFilename(item.type, item.url, lessonTitle)\n );\n\n onDownloadComplete?.(item.type);\n setProgress(Math.round(((index + 1) / items.length) * 100));\n\n if (index < items.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 800));\n }\n }\n },\n [lessonTitle, onDownloadStart, onDownloadComplete]\n );\n\n /**\n * Download every asset of the lesson.\n *\n * Prefers streaming to a user-picked folder, which is the only approach that\n * survives a large lesson video. Falls back to the browser's downloader when\n * the File System Access API is unavailable.\n */\n const handleDownload = useCallback(async () => {\n if (disabled || isDownloading) return;\n\n const availableContent = getAvailableContent();\n if (availableContent.length === 0) return;\n\n let dirHandle: FileSystemDirectoryHandleLike | null = null;\n\n if (supportsDirectoryPicker()) {\n try {\n const picker = (globalThis as { showDirectoryPicker?: DirectoryPicker })\n .showDirectoryPicker!;\n dirHandle = await picker({ mode: 'readwrite', id: 'aulas' });\n } catch {\n // The user dismissed the folder picker: abort quietly, nothing to report.\n return;\n }\n }\n\n setIsDownloading(true);\n setProgress(0);\n\n try {\n if (dirHandle) {\n await downloadToFolder(availableContent, dirHandle);\n } else {\n await downloadViaBrowser(availableContent);\n }\n } catch (error) {\n onDownloadError?.(\n 'aula',\n error instanceof Error\n ? error\n : new Error('Falha ao baixar o conteúdo da aula')\n );\n } finally {\n setIsDownloading(false);\n setProgress(0);\n }\n }, [\n disabled,\n isDownloading,\n getAvailableContent,\n downloadToFolder,\n downloadViaBrowser,\n onDownloadError,\n ]);\n\n // Don't render if no content is available\n const availableCount = getAvailableContent().length;\n\n if (availableCount === 0) {\n return null;\n }\n\n const suffix = availableCount > 1 ? 's' : '';\n\n return (\n <div className={cn('flex items-center', className)}>\n <IconButton\n icon={<DownloadSimpleIcon size={24} />}\n onClick={handleDownload}\n disabled={disabled || isDownloading}\n aria-label={\n isDownloading\n ? 'Baixando conteúdo...'\n : `Baixar conteúdo da aula (${availableCount} arquivo${suffix})`\n }\n className={cn(\n '!bg-transparent hover:!bg-black/10 transition-colors',\n isDownloading && 'opacity-60 cursor-not-allowed'\n )}\n />\n\n <ProgressModal\n isOpen={isDownloading}\n onClose={() => {}}\n message=\"Baixando conteúdo da aula...\"\n progress={progress}\n />\n </div>\n );\n};\n\nexport default DownloadButton;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YWPLHFAK.js","../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YWPLHFAK.js","sourcesContent":[null,"import { useCallback, useState } from 'react';\nimport { DownloadSimpleIcon } from '@phosphor-icons/react/dist/csr/DownloadSimple';\nimport IconButton from '../IconButton/IconButton';\nimport ProgressModal from '../ProgressModal/ProgressModal';\nimport { cn } from '../../utils/utils';\n\n/**\n * Download content interface for lesson materials\n */\nexport interface DownloadContent {\n /** Initial frame image URL */\n urlInitialFrame?: string;\n /** Final frame image URL */\n urlFinalFrame?: string;\n /** Podcast audio URL */\n urlPodcast?: string;\n /** Video URL */\n urlVideo?: string;\n}\n\n/**\n * Props for DownloadButton component\n */\nexport interface DownloadButtonProps {\n /** Content URLs to download */\n content: DownloadContent;\n /** Additional CSS classes */\n className?: string;\n /** Callback fired when download starts */\n onDownloadStart?: (contentType: string) => void;\n /** Callback fired when download completes */\n onDownloadComplete?: (contentType: string) => void;\n /** Callback fired when download fails */\n onDownloadError?: (contentType: string, error: Error) => void;\n /** Lesson title for download file naming */\n lessonTitle?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\n/** A single downloadable asset resolved from the lesson content */\ninterface DownloadItem {\n type: string;\n url: string;\n label: string;\n}\n\n/**\n * Minimal shape of the File System Access API we rely on.\n *\n * Typed locally because `showDirectoryPicker` is not in the DOM lib yet, and\n * because only Chromium ships it — every use goes through `supportsDirectoryPicker`.\n */\ninterface FileSystemWritable {\n write: (chunk: Uint8Array) => Promise<void>;\n close: () => Promise<void>;\n abort?: () => Promise<void>;\n}\n\ninterface FileSystemFileHandleLike {\n createWritable: () => Promise<FileSystemWritable>;\n}\n\ninterface FileSystemDirectoryHandleLike {\n getFileHandle: (\n name: string,\n options?: { create?: boolean }\n ) => Promise<FileSystemFileHandleLike>;\n}\n\ntype DirectoryPicker = (options?: {\n mode?: string;\n id?: string;\n}) => Promise<FileSystemDirectoryHandleLike>;\n\n/**\n * Whether the browser can stream downloads straight to a folder on disk.\n *\n * Chromium-based browsers ship the File System Access API; Safari and Firefox\n * do not, and fall back to plain per-file downloads.\n *\n * @returns Whether `showDirectoryPicker` is available\n */\nconst supportsDirectoryPicker = (): boolean =>\n typeof (globalThis as { showDirectoryPicker?: DirectoryPicker })\n .showDirectoryPicker === 'function';\n\n/**\n * Get file extension from URL\n * @param url - URL to extract extension from\n * @returns File extension or default\n */\nconst getFileExtension = (url: string): string => {\n try {\n const u = new URL(url, globalThis.location?.origin || 'http://localhost');\n url = u.pathname;\n } catch {\n // keep original url (likely relative)\n }\n const path = url.split(/[?#]/)[0];\n const dot = path.lastIndexOf('.');\n return dot > -1 ? path.slice(dot + 1).toLowerCase() : 'file';\n};\n\n/**\n * Slugify a lesson title for safe use in filenames\n * @param lessonTitle - Title of the lesson\n * @returns Sanitized slug\n */\nconst slugifyTitle = (lessonTitle: string): string =>\n lessonTitle\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s]/g, '')\n .replaceAll(/\\s+/g, '-')\n .substring(0, 50);\n\n/**\n * Generate the filename an asset is saved as\n * @param contentType - Type of content being downloaded\n * @param url - URL to get extension from\n * @param lessonTitle - Title of the lesson\n * @returns Generated filename\n */\nconst generateFilename = (\n contentType: string,\n url: string,\n lessonTitle: string = 'aula'\n): string =>\n `${slugifyTitle(lessonTitle)}-${contentType}.${getFileExtension(url)}`;\n\n/**\n * Read the byte size of a remote file without downloading it.\n *\n * Weights per-file progress against the real payload — a lesson video can be\n * hundreds of MB while the board frames are ~1MB, so counting files would make\n * the bar jump. Returns 0 when the server omits `content-length` or the request\n * fails, and callers then fall back to counting files.\n *\n * @param url - URL to probe\n * @returns Size in bytes, or 0 when unknown\n */\nconst getRemoteFileSize = async (url: string): Promise<number> => {\n try {\n const response = await fetch(url, { method: 'HEAD', mode: 'cors' });\n if (!response.ok) return 0;\n return Number(response.headers.get('content-length')) || 0;\n } catch {\n return 0;\n }\n};\n\n/**\n * Stream a remote file straight into a file on disk.\n *\n * Nothing is buffered in memory: each chunk is written to the file handle as it\n * arrives. This is what makes a ~900MB lesson video downloadable — holding it\n * in a Blob (and zipping it) exhausts the tab's memory and silently drops the\n * assets queued behind it.\n *\n * @param url - URL to download\n * @param dirHandle - Directory the user granted write access to\n * @param filename - Name to save the file as\n * @param onBytes - Called with the byte length of each chunk written\n */\nconst streamToDisk = async (\n url: string,\n dirHandle: FileSystemDirectoryHandleLike,\n filename: string,\n onBytes: (chunkSize: number) => void\n): Promise<void> => {\n const response = await fetch(url, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`\n );\n }\n if (!response.body) {\n throw new Error('Response has no readable body');\n }\n\n const fileHandle = await dirHandle.getFileHandle(filename, { create: true });\n const writable = await fileHandle.createWritable();\n\n try {\n const reader = response.body.getReader();\n\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n await writable.write(value);\n onBytes(value.length);\n }\n\n await writable.close();\n } catch (error) {\n await writable.abort?.();\n throw error;\n }\n};\n\n/**\n * Save a file through a plain anchor click, letting the browser download it.\n *\n * Fallback for browsers without the File System Access API (Safari, Firefox).\n * Note the `download` attribute is ignored cross-origin, and the CDN does not\n * send `Content-Disposition: attachment`, so these browsers may open the asset\n * in a tab rather than save it. Fixing that properly means setting that header\n * on the CDN — it cannot be forced from the client.\n *\n * The browser streams the response to its own downloads folder, so memory is\n * not a concern — but it\n * cannot report progress, and firing several in a row is unreliable, which is\n * why the caller spaces them out.\n *\n * @param url - URL to download\n * @param filename - Suggested filename\n */\nconst triggerBrowserDownload = (url: string, filename: string): void => {\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n\n document.body.appendChild(link);\n link.click();\n link.remove();\n};\n\n/**\n * DownloadButton component for downloading lesson content\n *\n * Downloads every asset of a lesson (video, podcast and board frames), matching\n * the mobile app's \"baixar aula completa\". Where the browser supports it, the\n * user picks a folder and each file is streamed straight to disk with real\n * progress; elsewhere the browser downloads them one by one.\n *\n * @param props - DownloadButton component props\n * @returns Download button element\n */\nconst DownloadButton = ({\n content,\n className,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n lessonTitle = 'aula',\n disabled = false,\n}: DownloadButtonProps) => {\n const [isDownloading, setIsDownloading] = useState(false);\n const [progress, setProgress] = useState(0);\n\n /**\n * Check if URL is valid and not empty\n * @param url - URL to validate\n * @returns Whether URL is valid\n */\n const isValidUrl = useCallback((url?: string): boolean => {\n return Boolean(\n url && url.trim() !== '' && url !== 'undefined' && url !== 'null'\n );\n }, []);\n\n /**\n * Get available download content\n * @returns Array of available download items\n */\n const getAvailableContent = useCallback((): DownloadItem[] => {\n const downloads: DownloadItem[] = [];\n\n if (isValidUrl(content.urlVideo)) {\n downloads.push({ type: 'video', url: content.urlVideo!, label: 'Vídeo' });\n }\n\n if (isValidUrl(content.urlPodcast)) {\n downloads.push({\n type: 'podcast',\n url: content.urlPodcast!,\n label: 'Podcast',\n });\n }\n\n if (isValidUrl(content.urlInitialFrame)) {\n downloads.push({\n type: 'quadro-inicial',\n url: content.urlInitialFrame!,\n label: 'Quadro Inicial',\n });\n }\n\n if (isValidUrl(content.urlFinalFrame)) {\n downloads.push({\n type: 'quadro-final',\n url: content.urlFinalFrame!,\n label: 'Quadro Final',\n });\n }\n\n return downloads;\n }, [content, isValidUrl]);\n\n /**\n * Download every asset by streaming it into the folder the user picked.\n *\n * @param items - Assets to download\n * @param dirHandle - Directory the user granted write access to\n */\n const downloadToFolder = useCallback(\n async (items: DownloadItem[], dirHandle: FileSystemDirectoryHandleLike) => {\n const sizes = await Promise.all(\n items.map((item) => getRemoteFileSize(item.url))\n );\n const totalBytes = sizes.reduce((sum, size) => sum + size, 0);\n // No content-length anywhere: weight each file equally instead of by bytes.\n const weighByFile = totalBytes === 0;\n\n let downloadedBytes = 0;\n let succeeded = 0;\n\n for (const [index, item] of items.entries()) {\n try {\n onDownloadStart?.(item.type);\n\n await streamToDisk(\n item.url,\n dirHandle,\n generateFilename(item.type, item.url, lessonTitle),\n (chunkSize) => {\n downloadedBytes += chunkSize;\n if (!weighByFile) {\n setProgress(\n Math.min(99, Math.round((downloadedBytes / totalBytes) * 100))\n );\n }\n }\n );\n\n succeeded++;\n onDownloadComplete?.(item.type);\n } catch (error) {\n onDownloadError?.(\n item.type,\n error instanceof Error\n ? error\n : new Error(`Falha ao baixar ${item.label}`)\n );\n } finally {\n if (weighByFile) {\n setProgress(\n Math.min(99, Math.round(((index + 1) / items.length) * 100))\n );\n }\n }\n }\n\n if (succeeded === 0) {\n throw new Error('Nenhum conteúdo da aula pôde ser baixado');\n }\n\n setProgress(100);\n },\n [lessonTitle, onDownloadStart, onDownloadComplete, onDownloadError]\n );\n\n /**\n * Hand each asset to the browser's own downloader, one at a time.\n *\n * Used where the File System Access API is missing. Downloads are spaced out\n * because browsers throttle several programmatic downloads fired at once.\n *\n * @param items - Assets to download\n */\n const downloadViaBrowser = useCallback(\n async (items: DownloadItem[]) => {\n for (const [index, item] of items.entries()) {\n onDownloadStart?.(item.type);\n\n triggerBrowserDownload(\n item.url,\n generateFilename(item.type, item.url, lessonTitle)\n );\n\n onDownloadComplete?.(item.type);\n setProgress(Math.round(((index + 1) / items.length) * 100));\n\n if (index < items.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 800));\n }\n }\n },\n [lessonTitle, onDownloadStart, onDownloadComplete]\n );\n\n /**\n * Download every asset of the lesson.\n *\n * Prefers streaming to a user-picked folder, which is the only approach that\n * survives a large lesson video. Falls back to the browser's downloader when\n * the File System Access API is unavailable.\n */\n const handleDownload = useCallback(async () => {\n if (disabled || isDownloading) return;\n\n const availableContent = getAvailableContent();\n if (availableContent.length === 0) return;\n\n let dirHandle: FileSystemDirectoryHandleLike | null = null;\n\n if (supportsDirectoryPicker()) {\n try {\n const picker = (globalThis as { showDirectoryPicker?: DirectoryPicker })\n .showDirectoryPicker!;\n dirHandle = await picker({ mode: 'readwrite', id: 'aulas' });\n } catch {\n // The user dismissed the folder picker: abort quietly, nothing to report.\n return;\n }\n }\n\n setIsDownloading(true);\n setProgress(0);\n\n try {\n if (dirHandle) {\n await downloadToFolder(availableContent, dirHandle);\n } else {\n await downloadViaBrowser(availableContent);\n }\n } catch (error) {\n onDownloadError?.(\n 'aula',\n error instanceof Error\n ? error\n : new Error('Falha ao baixar o conteúdo da aula')\n );\n } finally {\n setIsDownloading(false);\n setProgress(0);\n }\n }, [\n disabled,\n isDownloading,\n getAvailableContent,\n downloadToFolder,\n downloadViaBrowser,\n onDownloadError,\n ]);\n\n // Don't render if no content is available\n const availableCount = getAvailableContent().length;\n\n if (availableCount === 0) {\n return null;\n }\n\n const suffix = availableCount > 1 ? 's' : '';\n\n return (\n <div className={cn('flex items-center', className)}>\n <IconButton\n icon={<DownloadSimpleIcon size={24} />}\n onClick={handleDownload}\n disabled={disabled || isDownloading}\n aria-label={\n isDownloading\n ? 'Baixando conteúdo...'\n : `Baixar conteúdo da aula (${availableCount} arquivo${suffix})`\n }\n className={cn(\n '!bg-transparent hover:!bg-black/10 transition-colors',\n isDownloading && 'opacity-60 cursor-not-allowed'\n )}\n />\n\n <ProgressModal\n isOpen={isDownloading}\n onClose={() => {}}\n message=\"Baixando conteúdo da aula...\"\n progress={progress}\n />\n </div>\n );\n};\n\nexport default DownloadButton;\n"]}
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-ZRBIE5ZM.mjs";
7
7
  import {
8
8
  Modal_default
9
- } from "./chunk-XZUMD3CB.mjs";
9
+ } from "./chunk-LODDYDTP.mjs";
10
10
  import {
11
11
  Button_default
12
12
  } from "./chunk-ZQZBOTZJ.mjs";
@@ -261,4 +261,4 @@ export {
261
261
  resolveInsertWidth,
262
262
  ImageDialog
263
263
  };
264
- //# sourceMappingURL=chunk-DCEWWHV5.mjs.map
264
+ //# sourceMappingURL=chunk-Z3HGYPH7.mjs.map
@@ -6,10 +6,10 @@ var _chunkLW5PPXIUjs = require('./chunk-LW5PPXIU.js');
6
6
 
7
7
 
8
8
 
9
- var _chunkYA7XLH7Djs = require('./chunk-YA7XLH7D.js');
9
+ var _chunk3MFACEZ2js = require('./chunk-3MFACEZ2.js');
10
10
 
11
11
 
12
- var _chunkNU2WNYODjs = require('./chunk-NU2WNYOD.js');
12
+ var _chunkEW7HGUFVjs = require('./chunk-EW7HGUFV.js');
13
13
 
14
14
 
15
15
  var _chunkTRVEHJ6Mjs = require('./chunk-TRVEHJ6M.js');
@@ -90,7 +90,7 @@ var SUBJECT_COLUMNS = [
90
90
  key: "rate",
91
91
  label: "Taxa de acerto",
92
92
  sortable: true,
93
- render: (_value, row) => row.rate === null ? _chunkLW5PPXIUjs.MISSING_VALUE : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkYA7XLH7Djs.RateCell, { rate: row.rate })
93
+ render: (_value, row) => row.rate === null ? _chunkLW5PPXIUjs.MISSING_VALUE : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk3MFACEZ2js.RateCell, { rate: row.rate })
94
94
  }
95
95
  ];
96
96
  function sortRows(rows, key, order) {
@@ -147,7 +147,7 @@ function SubjectsTable({
147
147
  const noun = (count) => count === 1 ? "componente curricular" : "componentes curriculares";
148
148
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col items-center gap-4", children: [
149
149
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
150
- _chunkNU2WNYODjs.TableProvider,
150
+ _chunkEW7HGUFVjs.TableProvider,
151
151
  {
152
152
  tableId,
153
153
  data: visibleRows,
@@ -242,7 +242,7 @@ function KnowledgePerformanceCard({
242
242
  )
243
243
  ] }),
244
244
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
245
- _chunkYA7XLH7Djs.QuestionsBars,
245
+ _chunk3MFACEZ2js.QuestionsBars,
246
246
  {
247
247
  values,
248
248
  aside: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -265,4 +265,4 @@ function KnowledgePerformanceCard({
265
265
 
266
266
 
267
267
  exports.KnowledgePerformanceCard = KnowledgePerformanceCard;
268
- //# sourceMappingURL=chunk-Q3PKW6TH.js.map
268
+ //# sourceMappingURL=chunk-ZCV3HWI2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-Q3PKW6TH.js","../src/components/KnowledgePerformanceCard/KnowledgePerformanceCard.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-Q3PKW6TH.js","sourcesContent":[null,"import { useMemo, useState } from 'react';\nimport { CaretDownIcon } from '@phosphor-icons/react/dist/csr/CaretDown';\nimport { CaretUpIcon } from '@phosphor-icons/react/dist/csr/CaretUp';\nimport Text from '../Text/Text';\nimport Badge from '../Badge/Badge';\nimport Button from '../Button/Button';\nimport Select, {\n SelectTrigger,\n SelectValue,\n SelectContent,\n SelectItem,\n} from '../Select/Select';\nimport {\n QuestionsBars,\n RateCell,\n type QuestionsBarsValues,\n} from '../QuestionsPerformanceCard';\nimport { StatCard } from '../StatisticsCard/StatisticsCard';\nimport {\n TableProvider,\n type ColumnConfig,\n type TableParams,\n} from '../TableProvider/TableProvider';\nimport { renderSubjectsCell } from '../../utils/renderSubjectCell';\nimport {\n formatCount,\n formatReportScore,\n MISSING_VALUE,\n} from '../../utils/reportFormat';\nimport type { KnowledgePerformanceData, KnowledgeSubjectRow } from './types';\n\n/** Value of the select meaning every área. */\nconst ALL_AREAS = 'all';\n\n/** Componentes shown before \"Mostrar todos\". */\nconst COLLAPSED_SUBJECT_ROWS = 4;\n\ntype SubjectRow = {\n rowId: string;\n name: string;\n /** What `renderSubjectsCell` draws: the chip and the name. */\n subject: Array<{ id: string; name: string; color: string; icon: string }>;\n areaName: string;\n total: number;\n correct: number;\n incorrect: number;\n blank: number;\n /** `null` when nobody answered its questions. */\n rate: number | null;\n};\n\ntype SubjectSortKey =\n | 'name'\n | 'areaName'\n | 'correct'\n | 'incorrect'\n | 'blank'\n | 'rate';\n\nconst SORT_KEYS: ReadonlySet<string> = new Set<SubjectSortKey>([\n 'name',\n 'areaName',\n 'correct',\n 'incorrect',\n 'blank',\n 'rate',\n]);\n\nconst SUBJECT_COLUMNS: ColumnConfig<SubjectRow>[] = [\n {\n key: 'name',\n label: 'Componente curricular',\n sortable: true,\n className: 'min-w-[200px]',\n render: (_value, row) => renderSubjectsCell(row.subject),\n },\n {\n key: 'areaName',\n label: 'Área do conhecimento',\n sortable: true,\n className: 'min-w-[240px]',\n },\n {\n key: 'total',\n label: 'Total',\n sortable: false,\n render: (_value, row) => formatCount(row.total),\n },\n {\n key: 'correct',\n label: 'Corretas',\n sortable: true,\n render: (_value, row) => formatCount(row.correct),\n },\n {\n key: 'incorrect',\n label: 'Incorretas',\n sortable: true,\n render: (_value, row) => formatCount(row.incorrect),\n },\n {\n key: 'blank',\n label: 'Em branco',\n sortable: true,\n render: (_value, row) => formatCount(row.blank),\n },\n {\n key: 'rate',\n label: 'Taxa de acerto',\n sortable: true,\n render: (_value, row) =>\n row.rate === null ? MISSING_VALUE : <RateCell rate={row.rate} />,\n },\n];\n\n/** Sort rows by one column; names in pt-BR order. */\nfunction sortRows(\n rows: SubjectRow[],\n key: SubjectSortKey,\n order: 'asc' | 'desc'\n): SubjectRow[] {\n const direction = order === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const result =\n key === 'name' || key === 'areaName'\n ? a[key].localeCompare(b[key], 'pt-BR')\n : (a[key] ?? -1) - (b[key] ?? -1);\n return result * direction;\n });\n}\n\n/**\n * \"Desempenho por componente curricular\": searchable (by componente or área)\n * and sortable, collapsed to the first rows behind \"Mostrar todos\". Until a\n * column is sorted, the componentes keep the API's order: worst hit rate\n * first.\n */\nfunction SubjectsTable({\n subjects,\n tableId,\n}: Readonly<{ subjects: KnowledgeSubjectRow[]; tableId: string }>) {\n const [search, setSearch] = useState('');\n const [sort, setSort] = useState<{\n key: SubjectSortKey;\n order: 'asc' | 'desc';\n } | null>(null);\n const [expanded, setExpanded] = useState(false);\n\n const rows = useMemo(() => {\n const term = search.trim().toLocaleLowerCase('pt-BR');\n const all: SubjectRow[] = subjects.map((subject) => ({\n rowId: subject.subjectId,\n name: subject.subjectName,\n subject: [\n {\n id: subject.subjectId,\n name: subject.subjectName,\n color: subject.color,\n icon: subject.icon,\n },\n ],\n areaName: subject.areaKnowledgeName,\n total: subject.answered,\n correct: subject.correct,\n incorrect: subject.incorrect,\n blank: subject.blank,\n rate: subject.correctPercentage,\n }));\n const matching = term\n ? all.filter((row) =>\n `${row.name} ${row.areaName}`\n .toLocaleLowerCase('pt-BR')\n .includes(term)\n )\n : all;\n return sort ? sortRows(matching, sort.key, sort.order) : matching;\n }, [subjects, search, sort]);\n\n const visibleRows = expanded ? rows : rows.slice(0, COLLAPSED_SUBJECT_ROWS);\n const hasMore = rows.length > COLLAPSED_SUBJECT_ROWS;\n\n // Server mode: the table only reports search and sort, and they are applied\n // here to every componente before the collapse cut.\n const handleParamsChange = (params: TableParams) => {\n setSearch(params.search ?? '');\n const key = params.sortBy;\n setSort(\n key && SORT_KEYS.has(key)\n ? {\n key: key as SubjectSortKey,\n order: params.sortOrder === 'desc' ? 'desc' : 'asc',\n }\n : null\n );\n };\n\n const noun = (count: number) =>\n count === 1 ? 'componente curricular' : 'componentes curriculares';\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <TableProvider<SubjectRow>\n tableId={tableId}\n data={visibleRows}\n headers={SUBJECT_COLUMNS}\n rowKey=\"rowId\"\n variant=\"borderless\"\n enableSearch\n enableTableSort\n sortMode=\"server\"\n onParamsChange={handleParamsChange}\n searchPlaceholder=\"Buscar\"\n headerContent={\n <div className=\"flex flex-col gap-2\">\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Desempenho por componente curricular\n </Text>\n <Badge\n variant=\"solid\"\n action=\"info\"\n size=\"small\"\n className=\"self-start uppercase\"\n >\n {`${subjects.length} ${noun(subjects.length)} ${\n subjects.length === 1 ? 'total' : 'totais'\n }`}\n </Badge>\n </div>\n }\n />\n {hasMore && (\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setExpanded((previous) => !previous)}\n >\n {expanded\n ? 'Mostrar menos'\n : `Mostrar todos os ${rows.length} ${noun(rows.length)}`}\n {expanded ? <CaretUpIcon size={18} /> : <CaretDownIcon size={18} />}\n </Button>\n )}\n </div>\n );\n}\n\nexport interface KnowledgePerformanceCardProps {\n readonly data: KnowledgePerformanceData;\n /** Card heading; \"Dados de questões\" unless the report names it otherwise. */\n readonly title?: string;\n /**\n * Table identity for the persisted search and sort. Reports that show this\n * card side by side must pass distinct ids, or they share one state.\n */\n readonly tableId?: string;\n}\n\n/**\n * \"Desempenho por área de conhecimento\" — the questions of the cut split by\n * área, and every componente curricular with its own counts.\n *\n * The área select narrows the bars, the score and the table together.\n *\n * The counts are the API's, never summed from the table: a question mapped to\n * two componentes counts in both rows but once in its área and in the totals.\n * The score is the one derived from the hit rate, for every área and for all of\n * them — a corrected score would jump against the áreas'.\n *\n * Drawn from the Momento ENEM report and from the Simulados and Atividades\n * reports of the NRE and SEED profiles, which answer the same shape.\n *\n * @example\n * ```tsx\n * <KnowledgePerformanceCard data={questions} tableId=\"reportKnowledge\" />\n * ```\n */\nexport function KnowledgePerformanceCard({\n data,\n title = 'Dados de questões',\n tableId = 'knowledgePerformanceSubjects',\n}: KnowledgePerformanceCardProps) {\n const [areaId, setAreaId] = useState(ALL_AREAS);\n\n // The select lists them in pt-BR order; the API sends worst first.\n const areas = useMemo(\n () =>\n [...data.areas].sort((a, b) =>\n a.areaKnowledgeName.localeCompare(b.areaKnowledgeName, 'pt-BR')\n ),\n [data.areas]\n );\n // An área that vanished with a new cut falls back to \"all\" on read.\n const area = areas.find((item) => item.areaKnowledgeId === areaId) ?? null;\n\n const subjects = useMemo(\n () =>\n area\n ? data.subjects.filter(\n (subject) => subject.areaKnowledgeId === area.areaKnowledgeId\n )\n : data.subjects,\n [data.subjects, area]\n );\n\n const shown = area ?? data.totals;\n const values: QuestionsBarsValues = {\n total: shown.answered,\n corretas: shown.correct,\n incorretas: shown.incorrect,\n emBranco: shown.blank,\n };\n\n return (\n <div className=\"flex flex-col gap-4 rounded-xl border border-border-50 bg-background p-5\">\n <div className=\"flex flex-row flex-wrap items-center justify-between gap-4\">\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {title}\n </Text>\n <Select\n value={area ? area.areaKnowledgeId : ALL_AREAS}\n onValueChange={setAreaId}\n className=\"w-[380px] max-w-full\"\n >\n <SelectTrigger className=\"w-full\" aria-label=\"Área do conhecimento\">\n <SelectValue placeholder=\"Todas as áreas do conhecimento\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_AREAS}>\n Todas as áreas do conhecimento\n </SelectItem>\n {areas.map((item) => (\n <SelectItem\n key={item.areaKnowledgeId}\n value={item.areaKnowledgeId}\n >\n {item.areaKnowledgeName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <QuestionsBars\n values={values}\n aside={\n <StatCard\n item={{\n label: 'Nota média',\n value:\n shown.averageScore === null\n ? MISSING_VALUE\n : formatReportScore(shown.averageScore),\n variant: 'total',\n }}\n />\n }\n />\n\n <div aria-hidden=\"true\" className=\"h-px w-full bg-border-200\" />\n\n <SubjectsTable subjects={subjects} tableId={tableId} />\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-ZCV3HWI2.js","../src/components/KnowledgePerformanceCard/KnowledgePerformanceCard.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-ZCV3HWI2.js","sourcesContent":[null,"import { useMemo, useState } from 'react';\nimport { CaretDownIcon } from '@phosphor-icons/react/dist/csr/CaretDown';\nimport { CaretUpIcon } from '@phosphor-icons/react/dist/csr/CaretUp';\nimport Text from '../Text/Text';\nimport Badge from '../Badge/Badge';\nimport Button from '../Button/Button';\nimport Select, {\n SelectTrigger,\n SelectValue,\n SelectContent,\n SelectItem,\n} from '../Select/Select';\nimport {\n QuestionsBars,\n RateCell,\n type QuestionsBarsValues,\n} from '../QuestionsPerformanceCard';\nimport { StatCard } from '../StatisticsCard/StatisticsCard';\nimport {\n TableProvider,\n type ColumnConfig,\n type TableParams,\n} from '../TableProvider/TableProvider';\nimport { renderSubjectsCell } from '../../utils/renderSubjectCell';\nimport {\n formatCount,\n formatReportScore,\n MISSING_VALUE,\n} from '../../utils/reportFormat';\nimport type { KnowledgePerformanceData, KnowledgeSubjectRow } from './types';\n\n/** Value of the select meaning every área. */\nconst ALL_AREAS = 'all';\n\n/** Componentes shown before \"Mostrar todos\". */\nconst COLLAPSED_SUBJECT_ROWS = 4;\n\ntype SubjectRow = {\n rowId: string;\n name: string;\n /** What `renderSubjectsCell` draws: the chip and the name. */\n subject: Array<{ id: string; name: string; color: string; icon: string }>;\n areaName: string;\n total: number;\n correct: number;\n incorrect: number;\n blank: number;\n /** `null` when nobody answered its questions. */\n rate: number | null;\n};\n\ntype SubjectSortKey =\n | 'name'\n | 'areaName'\n | 'correct'\n | 'incorrect'\n | 'blank'\n | 'rate';\n\nconst SORT_KEYS: ReadonlySet<string> = new Set<SubjectSortKey>([\n 'name',\n 'areaName',\n 'correct',\n 'incorrect',\n 'blank',\n 'rate',\n]);\n\nconst SUBJECT_COLUMNS: ColumnConfig<SubjectRow>[] = [\n {\n key: 'name',\n label: 'Componente curricular',\n sortable: true,\n className: 'min-w-[200px]',\n render: (_value, row) => renderSubjectsCell(row.subject),\n },\n {\n key: 'areaName',\n label: 'Área do conhecimento',\n sortable: true,\n className: 'min-w-[240px]',\n },\n {\n key: 'total',\n label: 'Total',\n sortable: false,\n render: (_value, row) => formatCount(row.total),\n },\n {\n key: 'correct',\n label: 'Corretas',\n sortable: true,\n render: (_value, row) => formatCount(row.correct),\n },\n {\n key: 'incorrect',\n label: 'Incorretas',\n sortable: true,\n render: (_value, row) => formatCount(row.incorrect),\n },\n {\n key: 'blank',\n label: 'Em branco',\n sortable: true,\n render: (_value, row) => formatCount(row.blank),\n },\n {\n key: 'rate',\n label: 'Taxa de acerto',\n sortable: true,\n render: (_value, row) =>\n row.rate === null ? MISSING_VALUE : <RateCell rate={row.rate} />,\n },\n];\n\n/** Sort rows by one column; names in pt-BR order. */\nfunction sortRows(\n rows: SubjectRow[],\n key: SubjectSortKey,\n order: 'asc' | 'desc'\n): SubjectRow[] {\n const direction = order === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const result =\n key === 'name' || key === 'areaName'\n ? a[key].localeCompare(b[key], 'pt-BR')\n : (a[key] ?? -1) - (b[key] ?? -1);\n return result * direction;\n });\n}\n\n/**\n * \"Desempenho por componente curricular\": searchable (by componente or área)\n * and sortable, collapsed to the first rows behind \"Mostrar todos\". Until a\n * column is sorted, the componentes keep the API's order: worst hit rate\n * first.\n */\nfunction SubjectsTable({\n subjects,\n tableId,\n}: Readonly<{ subjects: KnowledgeSubjectRow[]; tableId: string }>) {\n const [search, setSearch] = useState('');\n const [sort, setSort] = useState<{\n key: SubjectSortKey;\n order: 'asc' | 'desc';\n } | null>(null);\n const [expanded, setExpanded] = useState(false);\n\n const rows = useMemo(() => {\n const term = search.trim().toLocaleLowerCase('pt-BR');\n const all: SubjectRow[] = subjects.map((subject) => ({\n rowId: subject.subjectId,\n name: subject.subjectName,\n subject: [\n {\n id: subject.subjectId,\n name: subject.subjectName,\n color: subject.color,\n icon: subject.icon,\n },\n ],\n areaName: subject.areaKnowledgeName,\n total: subject.answered,\n correct: subject.correct,\n incorrect: subject.incorrect,\n blank: subject.blank,\n rate: subject.correctPercentage,\n }));\n const matching = term\n ? all.filter((row) =>\n `${row.name} ${row.areaName}`\n .toLocaleLowerCase('pt-BR')\n .includes(term)\n )\n : all;\n return sort ? sortRows(matching, sort.key, sort.order) : matching;\n }, [subjects, search, sort]);\n\n const visibleRows = expanded ? rows : rows.slice(0, COLLAPSED_SUBJECT_ROWS);\n const hasMore = rows.length > COLLAPSED_SUBJECT_ROWS;\n\n // Server mode: the table only reports search and sort, and they are applied\n // here to every componente before the collapse cut.\n const handleParamsChange = (params: TableParams) => {\n setSearch(params.search ?? '');\n const key = params.sortBy;\n setSort(\n key && SORT_KEYS.has(key)\n ? {\n key: key as SubjectSortKey,\n order: params.sortOrder === 'desc' ? 'desc' : 'asc',\n }\n : null\n );\n };\n\n const noun = (count: number) =>\n count === 1 ? 'componente curricular' : 'componentes curriculares';\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <TableProvider<SubjectRow>\n tableId={tableId}\n data={visibleRows}\n headers={SUBJECT_COLUMNS}\n rowKey=\"rowId\"\n variant=\"borderless\"\n enableSearch\n enableTableSort\n sortMode=\"server\"\n onParamsChange={handleParamsChange}\n searchPlaceholder=\"Buscar\"\n headerContent={\n <div className=\"flex flex-col gap-2\">\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Desempenho por componente curricular\n </Text>\n <Badge\n variant=\"solid\"\n action=\"info\"\n size=\"small\"\n className=\"self-start uppercase\"\n >\n {`${subjects.length} ${noun(subjects.length)} ${\n subjects.length === 1 ? 'total' : 'totais'\n }`}\n </Badge>\n </div>\n }\n />\n {hasMore && (\n <Button\n variant=\"outline\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setExpanded((previous) => !previous)}\n >\n {expanded\n ? 'Mostrar menos'\n : `Mostrar todos os ${rows.length} ${noun(rows.length)}`}\n {expanded ? <CaretUpIcon size={18} /> : <CaretDownIcon size={18} />}\n </Button>\n )}\n </div>\n );\n}\n\nexport interface KnowledgePerformanceCardProps {\n readonly data: KnowledgePerformanceData;\n /** Card heading; \"Dados de questões\" unless the report names it otherwise. */\n readonly title?: string;\n /**\n * Table identity for the persisted search and sort. Reports that show this\n * card side by side must pass distinct ids, or they share one state.\n */\n readonly tableId?: string;\n}\n\n/**\n * \"Desempenho por área de conhecimento\" — the questions of the cut split by\n * área, and every componente curricular with its own counts.\n *\n * The área select narrows the bars, the score and the table together.\n *\n * The counts are the API's, never summed from the table: a question mapped to\n * two componentes counts in both rows but once in its área and in the totals.\n * The score is the one derived from the hit rate, for every área and for all of\n * them — a corrected score would jump against the áreas'.\n *\n * Drawn from the Momento ENEM report and from the Simulados and Atividades\n * reports of the NRE and SEED profiles, which answer the same shape.\n *\n * @example\n * ```tsx\n * <KnowledgePerformanceCard data={questions} tableId=\"reportKnowledge\" />\n * ```\n */\nexport function KnowledgePerformanceCard({\n data,\n title = 'Dados de questões',\n tableId = 'knowledgePerformanceSubjects',\n}: KnowledgePerformanceCardProps) {\n const [areaId, setAreaId] = useState(ALL_AREAS);\n\n // The select lists them in pt-BR order; the API sends worst first.\n const areas = useMemo(\n () =>\n [...data.areas].sort((a, b) =>\n a.areaKnowledgeName.localeCompare(b.areaKnowledgeName, 'pt-BR')\n ),\n [data.areas]\n );\n // An área that vanished with a new cut falls back to \"all\" on read.\n const area = areas.find((item) => item.areaKnowledgeId === areaId) ?? null;\n\n const subjects = useMemo(\n () =>\n area\n ? data.subjects.filter(\n (subject) => subject.areaKnowledgeId === area.areaKnowledgeId\n )\n : data.subjects,\n [data.subjects, area]\n );\n\n const shown = area ?? data.totals;\n const values: QuestionsBarsValues = {\n total: shown.answered,\n corretas: shown.correct,\n incorretas: shown.incorrect,\n emBranco: shown.blank,\n };\n\n return (\n <div className=\"flex flex-col gap-4 rounded-xl border border-border-50 bg-background p-5\">\n <div className=\"flex flex-row flex-wrap items-center justify-between gap-4\">\n <Text as=\"h3\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {title}\n </Text>\n <Select\n value={area ? area.areaKnowledgeId : ALL_AREAS}\n onValueChange={setAreaId}\n className=\"w-[380px] max-w-full\"\n >\n <SelectTrigger className=\"w-full\" aria-label=\"Área do conhecimento\">\n <SelectValue placeholder=\"Todas as áreas do conhecimento\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_AREAS}>\n Todas as áreas do conhecimento\n </SelectItem>\n {areas.map((item) => (\n <SelectItem\n key={item.areaKnowledgeId}\n value={item.areaKnowledgeId}\n >\n {item.areaKnowledgeName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <QuestionsBars\n values={values}\n aside={\n <StatCard\n item={{\n label: 'Nota média',\n value:\n shown.averageScore === null\n ? MISSING_VALUE\n : formatReportScore(shown.averageScore),\n variant: 'total',\n }}\n />\n }\n />\n\n <div aria-hidden=\"true\" className=\"h-px w-full bg-border-200\" />\n\n <SubjectsTable subjects={subjects} tableId={tableId} />\n </div>\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ProgressModal_default
3
- } from "./chunk-P6FX3NT6.mjs";
3
+ } from "./chunk-AZGDHAP5.mjs";
4
4
  import {
5
5
  IconButton_default
6
6
  } from "./chunk-ROS5XGTQ.mjs";
@@ -251,4 +251,4 @@ var DownloadButton_default = DownloadButton;
251
251
  export {
252
252
  DownloadButton_default
253
253
  };
254
- //# sourceMappingURL=chunk-OSA3YVDZ.mjs.map
254
+ //# sourceMappingURL=chunk-ZF46DCAC.mjs.map
@@ -9,7 +9,7 @@ var _chunk5DY2JMMJjs = require('./chunk-5DY2JMMJ.js');
9
9
  var _chunkK74NP7Q7js = require('./chunk-K74NP7Q7.js');
10
10
 
11
11
 
12
- var _chunkZRIYCU2Vjs = require('./chunk-ZRIYCU2V.js');
12
+ var _chunkS2BZPPAIjs = require('./chunk-S2BZPPAI.js');
13
13
 
14
14
 
15
15
  var _chunkGXIZAJ2Wjs = require('./chunk-GXIZAJ2W.js');
@@ -358,7 +358,7 @@ var StudentPerformanceDetailsModal = ({
358
358
  return null;
359
359
  }
360
360
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
361
- _chunkZRIYCU2Vjs.ReportDetailModal_default,
361
+ _chunkS2BZPPAIjs.ReportDetailModal_default,
362
362
  {
363
363
  isOpen,
364
364
  onClose,
@@ -375,4 +375,4 @@ var StudentPerformanceDetailsModal = ({
375
375
 
376
376
 
377
377
  exports.StudentPerformanceDetailsModal = StudentPerformanceDetailsModal;
378
- //# sourceMappingURL=chunk-5T6IDXSB.js.map
378
+ //# sourceMappingURL=chunk-ZO2GA23I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-5T6IDXSB.js","../src/components/StudentPerformanceDetailsModal/StudentPerformanceDetailsModal.tsx","../src/components/StudentPerformanceDetailsModal/activityProgress.ts","../src/components/StudentPerformanceDetailsModal/exportSheets.ts"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;AC5BA,8BAAqD;AACrD,+CAAkD;AD8BlD;AACA;AEdO,IAAM,gBAAA,EAAkB,CAAC,QAAA,EAAA,GAC9B,CAAC,QAAA,CAAS,UAAA,GAAa,QAAA,CAAS,WAAA,EAAa,CAAA;AAKxC,IAAM,mBAAA,EAAqB,CAChC,QAAA,EACA,OAAA,EACA,KAAA,EAAA,GAEA,QAAA,CACG,OAAA,CAAQ,WAAA,EAAa,MAAA,CAAO,OAAO,CAAC,CAAA,CACpC,OAAA,CAAQ,SAAA,EAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AFIrC;AACA;AGGO,IAAM,mBAAA,EAAqB,yBAAA;AAS3B,IAAM,sBAAA,EAAwB,uBAAA;AAGrC,IAAM,YAAA,EAAc,GAAA;AAQpB,IAAM,kBAAA,EAAoB,WAAA;AAE1B,IAAM,gBAAA,EAAkB,CAAC,WAAA,EAAa,OAAO,CAAA;AAC7C,IAAM,mBAAA,EAAqB,CAAC,WAAA,EAAa,WAAA,EAAa,iBAAW,CAAA;AAa1D,SAAS,iBAAA,CACd,IAAA,EACA,MAAA,EACa;AACb,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO,EAAE,IAAA,EAAM,kBAAA,EAAoB,OAAA,EAAS,eAAA,EAAiB,IAAA,EAAM,CAAC,EAAE,CAAA;AAAA,EACxE;AAEA,EAAA,MAAM,KAAA,EAAsB;AAAA,IAC1B,CAAC,iBAAA,EAAmB,IAAA,CAAK,WAAW,CAAA;AAAA,IACpC,CAAC,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAAA,IACpC,CAAC,MAAA,CAAO,mBAAA,EAAqB,IAAA,CAAK,KAAA,CAAM,iBAAA,GAAoB,WAAW,CAAA;AAAA,IACvE,CAAC,MAAA,CAAO,qBAAA,EAAuB,IAAA,CAAK,gBAAA,CAAiB,KAAK,CAAA;AAAA,IAC1D;AAAA,MACE,MAAA,CAAO,eAAA;AAAA,MACP,IAAA,CAAK,gBAAA,CAAiB,gBAAA,GAAmB;AAAA,IAC3C,CAAA;AAAA,IACA,CAAC,MAAA,CAAO,uBAAA,EAAyB,IAAA,CAAK,kBAAA,CAAmB,KAAK,CAAA;AAAA,IAC9D;AAAA,MACE,MAAA,CAAO,iBAAA;AAAA,MACP,IAAA,CAAK,kBAAA,CAAmB,aAAA,GAAgB;AAAA,IAC1C,CAAA;AAAA,IACA,CAAC,MAAA,CAAO,eAAA,EAAiB,IAAA,CAAK,mBAAmB,CAAA;AAAA,IACjD,CAAC,MAAA,CAAO,cAAA,EAAgB,IAAA,CAAK,iBAAiB,CAAA;AAAA,IAC9C,CAAC,MAAA,CAAO,gBAAA,EAAkB,IAAA,CAAK,WAAW,CAAA;AAAA,IAC1C,CAAC,MAAA,CAAO,eAAA,EAAiB,IAAA,CAAK,UAAU,CAAA;AAAA,IACxC,CAAC,MAAA,CAAO,cAAA,EAAgB,IAAA,CAAK,SAAS;AAAA,EACxC,CAAA;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,kBAAA,EAAoB,OAAA,EAAS,eAAA,EAAiB,KAAK,CAAA;AACpE;AAeO,SAAS,oBAAA,CACd,IAAA,EACA,MAAA,EACa;AACb,EAAA,MAAM,KAAA,EAAA,kCAAuB,IAAA,2BAAM,YAAA,UAAc,CAAC,GAAA,CAAA,CAAG,GAAA,CAAI,CAAC,QAAA,EAAA,GAAa;AAAA,IACrE,QAAA,CAAS,IAAA;AAAA,IACT,eAAA,CAAgB,QAAQ,EAAA,EACpB,kBAAA;AAAA,MACE,MAAA,CAAO,oBAAA;AAAA,MACP,QAAA,CAAS,YAAA;AAAA,MACT,QAAA,CAAS;AAAA,IACX,EAAA,EACA,MAAA,CAAO,aAAA;AAAA,IACX,QAAA,CAAS,YAAA,GAAe,MAAA,CAAO;AAAA,EACjC,CAAC,CAAA;AAED,EAAA,OAAO,EAAE,IAAA,EAAM,qBAAA,EAAuB,OAAA,EAAS,kBAAA,EAAoB,KAAK,CAAA;AAC1E;AAKO,SAAS,6BAAA,CACd,IAAA,EACA,MAAA,EACe;AACf,EAAA,OAAO,CAAC,iBAAA,CAAkB,IAAA,EAAM,MAAM,CAAA,EAAG,oBAAA,CAAqB,IAAA,EAAM,MAAM,CAAC,CAAA;AAC7E;AH/DA;AACA;ACkBM,+CAAA;AA5EN,IAAM,mBAAA,EAAqB,sBAAA;AAY3B,IAAM,mBAAA,EAAqB,cAAA;AAqB3B,IAAM,iBAAA,EAAmB;AAAA,EACvB,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,uBAAA;AAAA,IACV,MAAA,EAAQ,gBAAA;AAAA,IACR,UAAA,EAAY,kBAAA;AAAA,IACZ,mBAAA,EAAqB;AAAA,EACvB,CAAA;AAAA,EACA,KAAA,EAAO;AAAA,IACL,QAAA,EAAU,gBAAA;AAAA,IACV,MAAA,EAAQ,gBAAA;AAAA,IACR,UAAA,EAAY,kBAAA;AAAA,IACZ,mBAAA,EAAqB;AAAA,EACvB,CAAA;AAAA,EACA,GAAA,EAAK;AAAA,IACH,QAAA,EAAU,cAAA;AAAA,IACV,MAAA,EAAQ,cAAA;AAAA,IACR,UAAA,EAAY,gBAAA;AAAA,IACZ,mBAAA,EAAqB;AAAA,EACvB,CAAA;AAAA,EACA,OAAA,EAAS;AAAA,IACP,QAAA,EAAU,uBAAA;AAAA,IACV,MAAA,EAAQ,gBAAA;AAAA,IACR,UAAA,EAAY,kBAAA;AAAA,IACZ,mBAAA,EAAqB;AAAA,EACvB;AACF,CAAA;AAKA,IAAM,oBAAA,EAAsB,CAAC;AAAA,EAC3B,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAAA,GAAgC;AAC9B,EAAA,MAAM,OAAA,mBAAS,gBAAA,CAAiB,OAAO,CAAA,UAAK,gBAAA,CAAiB,SAAA;AAE7D,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uFAAA,EAEb,QAAA,EAAA;AAAA,oBAAA,8BAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,CAAA,kEAAA,EAAqE,MAAA,CAAO,QAAQ,CAAA,CAAA;AAE/F,QAAA;AAAA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACI,cAAA;AAC6E,cAAA;AAE/E,cAAA;AAAA,YAAA;AACH,UAAA;AACA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACM,cAAA;AACE,cAAA;AACG,cAAA;AAET,cAAA;AAAA,YAAA;AACH,UAAA;AACA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACM,cAAA;AACE,cAAA;AACwB,cAAA;AAE9B,cAAA;AAAA,YAAA;AACH,UAAA;AAAA,QAAA;AAAA,MAAA;AACF,IAAA;AAIE,oBAAA;AAAA,sBAAA;AAAC,QAAA;AAAA,QAAA;AACM,UAAA;AACE,UAAA;AACiC,UAAA;AAEvC,UAAA;AAAA,QAAA;AACH,MAAA;AAGA,sBAAA;AACF,IAAA;AACF,EAAA;AAEJ;AAgB+B;AAC7B,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACuB;AAIiB,EAAA;AAE/B,EAAA;AACH,IAAA;AAC+D,IAAA;AAEjE,EAAA;AAGF,EAAA;AAAC,IAAA;AAAA,IAAA;AACiB,MAAA;AACN,MAAA;AACO,MAAA;AACA,MAAA;AAGb,MAAA;AACG,wBAAA;AAIC,QAAA;AACE,0BAAA;AAAC,YAAA;AAAA,YAAA;AACQ,cAAA;AACC,cAAA;AACH,cAAA;AAAA,YAAA;AAET,UAAA;AACA,0BAAA;AAAC,YAAA;AAAA,YAAA;AACM,cAAA;AACE,cAAA;AACG,cAAA;AAET,cAAA;AACC,gBAAA;AACS,gBAAA;AACA,gBAAA;AACX,cAAA;AAAA,YAAA;AACF,UAAA;AAIC,QAAA;AAGP,MAAA;AAIC,MAAA;AACH,IAAA;AACF,EAAA;AAEJ;AAMiB;AACuC,kBAAA;AAElD,kBAAA;AAAmD,oBAAA;AACA,oBAAA;AACA,oBAAA;AACrD,EAAA;AAEE,kBAAA;AAAmD,oBAAA;AACA,oBAAA;AACrD,EAAA;AAEE,kBAAA;AAAmD,oBAAA;AACA,oBAAA;AACA,oBAAA;AACrD,EAAA;AACF;AAOe;AACb,kBAAA;AAAC,IAAA;AAAA,IAAA;AACI,MAAA;AACO,MAAA;AAE+C,MAAA;AAAO,IAAA;AAClE,EAAA;AAEG,kBAAA;AAEL;AAM0B;AAC1B,EAAA;AACA,EAAA;AAOE;AACE,kBAAA;AAAA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACI,QAAA;AACO,QAAA;AAE8C,QAAA;AAAO,MAAA;AACjE,IAAA;AAEQ,oBAAA;AAEV,EAAA;AAKE,kBAAA;AAAA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACkE,QAAA;AACnD,QAAA;AACmB,QAAA;AACV,QAAA;AACD,QAAA;AACd,QAAA;AAAA,MAAA;AACV,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACqE,QAAA;AACtD,QAAA;AACe,QAAA;AACN,QAAA;AACU,QAAA;AACzB,QAAA;AAAA,MAAA;AACV,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACmE,QAAA;AACpD,QAAA;AACiB,QAAA;AACR,QAAA;AACY,QAAA;AAC3B,QAAA;AAAA,MAAA;AACV,IAAA;AACF,EAAA;AAIE,kBAAA;AAAA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACe,QAAA;AACF,QAAA;AAAA,MAAA;AACd,IAAA;AACwE,oBAAA;AAC1E,EAAA;AAIE,kBAAA;AAAoE,oBAAA;AACF,oBAAA;AACF,oBAAA;AAClE,EAAA;AAKI,EAAA;AACG,oBAAA;AAGqB,oBAAA;AACnB,MAAA;AAAA,MAAA;AAEC,QAAA;AACsB,QAAA;AACO,QAAA;AACM,QAAA;AAAA,MAAA;AAJrB,MAAA;AAOpB,IAAA;AACF,EAAA;AAEJ;AAWc;AACD,EAAA;AACa,IAAA;AAC1B,EAAA;AAEW,EAAA;AAC4B,IAAA;AACvC,EAAA;AAEU,EAAA;AAC+C,IAAA;AACzD,EAAA;AAEO,EAAA;AACT;AA0C+C;AAC7C,EAAA;AACA,EAAA;AACA,EAAA;AACU,EAAA;AACF,EAAA;AACA,EAAA;AACiC;AAC1B,EAAA;AACmD,IAAA;AACnD,IAAA;AACf,EAAA;AAE2E,EAAA;AAE7B,EAAA;AACuB,IAAA;AAC1C,EAAA;AAEM,EAAA;AACxB,IAAA;AACT,EAAA;AAGE,EAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACA,MAAA;AACc,MAAA;AACT,MAAA;AACM,MAAA;AAKX,MAAA;AACiB,MAAA;AAE+B,MAAA;AAAA,IAAA;AAClD,EAAA;AAEJ;ADzFwG;AACA;AACA;AACA","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-5T6IDXSB.js","sourcesContent":[null,"import { useCallback, useMemo, type ReactNode } from 'react';\nimport { UserIcon, TrophyIcon, XCircleIcon } from '@phosphor-icons/react';\nimport ReportDetailModal from '../ReportDetailModal/ReportDetailModal';\nimport Text from '../Text/Text';\nimport Badge from '../Badge/Badge';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { CardAccordation } from '../Accordation/Accordation';\nimport { downloadExcel } from '../../utils/exportExcel';\nimport { formatDateForFileName } from '../../utils/exportFormat';\nimport { formatProgressText, hasActivityData } from './activityProgress';\nimport { buildStudentPerformanceSheets } from './exportSheets';\nimport type {\n StudentPerformanceDetailsModalProps,\n StudentPerformanceDetailsData,\n StudentPerformanceDetailsLabels,\n ActivityProgress,\n} from './types';\nimport { DEFAULT_PERFORMANCE_DETAILS_LABELS } from './types';\nimport { MetricBox } from '../shared/MetricBox';\n\n/**\n * Prefixo do arquivo exportado, em PDF e em XLSX. Recebe a data do dia.\n *\n * Sem o nome do estudante de propósito: ele traz acentos, espaços e às vezes\n * pontuação, e um nome de arquivo pede transliteração — que não existe nesta\n * lib e que não vale a pena inventar aqui.\n */\nconst EXPORT_FILE_PREFIX = 'desempenho-estudante';\n\n/**\n * Teto de altura do modal.\n *\n * Vem do `contentClassName=\"max-h-[80vh]\"` que este componente passava ao\n * `Modal` base; o `ReportDetailModal` não repassa `contentClassName`, então o\n * limite migrou para o `<dialog>` pela prop `className`, onde vence o\n * `max-h-[calc(100dvh-2rem)]` padrão no tailwind-merge (pinado em teste). A\n * rolagem interna continua vindo do `Modal`, que já põe `overflow-y-auto` na\n * área de conteúdo.\n */\nconst MODAL_HEIGHT_CLASS = 'max-h-[80vh]';\n\n/**\n * Performance stat card with colored header and white footer\n * Used for NOTA, CORRETAS, INCORRETAS cards\n */\ninterface PerformanceStatCardProps {\n /** Icon to display */\n icon: ReactNode;\n /** Card label (e.g., \"NOTA\") */\n label: string;\n /** Main value (e.g., \"9.0\" or \"8\") */\n value: string | number;\n /** Secondary label (e.g., \"DESEMPENHO\") */\n secondaryLabel: string;\n /** Badge text (e.g., \"Acima da média\") */\n badgeText: string | null;\n /** Color variant */\n variant: 'orange' | 'green' | 'red';\n}\n\nconst STAT_CARD_COLORS = {\n orange: {\n headerBg: 'bg-warning-background',\n iconBg: 'bg-warning-300',\n valueColor: 'text-warning-600',\n secondaryLabelColor: 'text-warning-500',\n },\n green: {\n headerBg: 'bg-success-200',\n iconBg: 'bg-success-300',\n valueColor: 'text-success-700',\n secondaryLabelColor: 'text-success-600',\n },\n red: {\n headerBg: 'bg-error-100',\n iconBg: 'bg-error-500',\n valueColor: 'text-error-700',\n secondaryLabelColor: 'text-error-300',\n },\n default: {\n headerBg: 'bg-warning-background',\n iconBg: 'bg-warning-300',\n valueColor: 'text-warning-600',\n secondaryLabelColor: 'text-warning-500',\n },\n} as const;\n\n/**\n * Performance stat card component\n */\nconst PerformanceStatCard = ({\n icon,\n label,\n value,\n secondaryLabel,\n badgeText,\n variant,\n}: PerformanceStatCardProps) => {\n const colors = STAT_CARD_COLORS[variant] ?? STAT_CARD_COLORS.default;\n\n return (\n <div className=\"flex flex-col rounded-xl border border-border-50 bg-background overflow-hidden flex-1\">\n {/* Header section with colored background */}\n <div\n className={`flex flex-col items-center justify-center px-2 sm:px-3 py-2 gap-1 ${colors.headerBg}`}\n >\n <Text\n as=\"span\"\n className={`size-8 rounded-full flex items-center justify-center ${colors.iconBg}`}\n >\n {icon}\n </Text>\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className=\"text-text-800 uppercase text-center leading-none\"\n >\n {label}\n </Text>\n <Text\n size=\"xl\"\n weight=\"bold\"\n className={`${colors.valueColor} text-center`}\n >\n {value}\n </Text>\n </div>\n\n {/* Footer section with white background */}\n <div className=\"flex flex-col items-center gap-2 px-2 sm:px-3 py-2\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className={`${colors.secondaryLabelColor} uppercase text-center`}\n >\n {secondaryLabel}\n </Text>\n <Badge size=\"large\" action=\"info\">\n {badgeText || '-'}\n </Badge>\n </div>\n </div>\n );\n};\n\n/**\n * Activity progress card with expandable accordion content\n */\ninterface ActivityCardProps {\n /** Activity data */\n activity: ActivityProgress;\n /** No data message */\n noDataMessage: string;\n /** Activity progress text template */\n activityProgressText: string;\n /** Activity details unavailable message */\n activityDetailsUnavailable: string;\n}\n\nconst ActivityAccordionCard = ({\n activity,\n noDataMessage,\n activityProgressText,\n activityDetailsUnavailable,\n}: ActivityCardProps) => {\n // `hasActivityData` e `formatProgressText` vivem em `./activityProgress` para\n // que a aba \"Desempenho atividades\" da planilha decida e formate pelas mesmas\n // regras deste card, em vez de manter uma segunda cópia que pode divergir.\n const hasData = hasActivityData(activity);\n const progressPercentage = hasData\n ? Math.min(\n 100,\n Math.max(0, (activity.correctCount / activity.totalCount) * 100)\n )\n : 0;\n\n return (\n <CardAccordation\n value={activity.id}\n className=\"bg-background rounded-xl border border-border-50\"\n triggerClassName=\"p-4\"\n contentClassName=\"px-4 pb-4\"\n trigger={\n <div className=\"flex flex-col gap-2 flex-1\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {activity.name}\n </Text>\n {hasData ? (\n <div className=\"flex flex-row items-center gap-2\">\n <div className=\"flex-1\">\n <ProgressBar\n value={progressPercentage}\n variant=\"green\"\n size=\"medium\"\n />\n </div>\n <Text\n size=\"xs\"\n weight=\"medium\"\n className=\"text-text-950 whitespace-nowrap\"\n >\n {formatProgressText(\n activityProgressText,\n activity.correctCount,\n activity.totalCount\n )}\n </Text>\n </div>\n ) : (\n <Text size=\"xs\" className=\"text-text-500\">\n {noDataMessage}\n </Text>\n )}\n </div>\n }\n >\n <Text size=\"sm\" className=\"text-text-700\">\n {activity.description || activityDetailsUnavailable}\n </Text>\n </CardAccordation>\n );\n};\n\n/**\n * Loading skeleton for the modal content\n */\nconst LoadingSkeleton = () => (\n <div className=\"flex flex-col gap-4 animate-pulse\">\n <div className=\"h-6 bg-background-200 rounded w-48\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2\">\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2\">\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n </div>\n </div>\n);\n\n/**\n * Error state component\n */\nconst ErrorContent = ({ message }: { message: string }) => (\n <div className=\"flex flex-col items-center justify-center py-8 gap-3\">\n <Text\n as=\"span\"\n className=\"size-12 rounded-full bg-error-100 flex items-center justify-center\"\n >\n <XCircleIcon size={24} className=\"text-error-700\" weight=\"fill\" />\n </Text>\n <Text size=\"md\" className=\"text-error-700 text-center\">\n {message}\n </Text>\n </div>\n);\n\n/**\n * Performance content when data is available\n */\nconst PerformanceContent = ({\n data,\n labels,\n}: {\n data: StudentPerformanceDetailsData;\n labels: StudentPerformanceDetailsLabels;\n}) => (\n <div className=\"flex flex-col gap-4\">\n {/* Student name with icon */}\n <div className=\"flex items-center gap-2 pt-2\">\n <Text\n as=\"span\"\n className=\"size-6 rounded-full bg-primary-100 flex items-center justify-center\"\n >\n <UserIcon size={14} className=\"text-primary-800\" weight=\"fill\" />\n </Text>\n <Text size=\"md\" className=\"text-text-950\">\n {data.studentName}\n </Text>\n </div>\n\n {/* Main stats row - 3 colored cards */}\n <div className=\"flex flex-col sm:flex-row gap-2 sm:gap-3\">\n {/* NOTA card */}\n <PerformanceStatCard\n icon={<TrophyIcon size={16} weight=\"fill\" className=\"text-white\" />}\n label={labels.gradeLabel}\n value={data.grade.value.toFixed(1)}\n secondaryLabel={labels.performanceTagLabel}\n badgeText={data.grade.performanceLabel}\n variant=\"orange\"\n />\n\n {/* CORRETAS card */}\n <PerformanceStatCard\n icon={<TrophyIcon size={16} weight=\"fill\" className=\"text-text-950\" />}\n label={labels.correctQuestionsLabel}\n value={data.correctQuestions.value}\n secondaryLabel={labels.bestResultLabel}\n badgeText={data.correctQuestions.bestResultTopic}\n variant=\"green\"\n />\n\n {/* INCORRETAS card */}\n <PerformanceStatCard\n icon={<XCircleIcon size={16} weight=\"fill\" className=\"text-white\" />}\n label={labels.incorrectQuestionsLabel}\n value={data.incorrectQuestions.value}\n secondaryLabel={labels.hardestTopicLabel}\n badgeText={data.incorrectQuestions.hardestTopic}\n variant=\"red\"\n />\n </div>\n\n {/* Secondary stats row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-3\">\n <MetricBox\n label={labels.activitiesLabel}\n value={data.activitiesCompleted}\n />\n <MetricBox label={labels.questionsLabel} value={data.questionsAnswered} />\n </div>\n\n {/* Tertiary stats row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-3\">\n <MetricBox label={labels.accessCountLabel} value={data.accessCount} />\n <MetricBox label={labels.timeOnlineLabel} value={data.timeOnline} />\n <MetricBox label={labels.lastLoginLabel} value={data.lastLogin} />\n </div>\n\n {/* Activities progress section */}\n {data.activities.length > 0 && (\n <div className=\"flex flex-col gap-4 pt-4\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {labels.activitiesProgressTitle}\n </Text>\n <div className=\"flex flex-col gap-2\">\n {data.activities.map((activity) => (\n <ActivityAccordionCard\n key={activity.id}\n activity={activity}\n noDataMessage={labels.noDataMessage}\n activityProgressText={labels.activityProgressText}\n activityDetailsUnavailable={labels.activityDetailsUnavailable}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n);\n\n/**\n * Renders the modal content based on loading, error, and data state\n */\nconst renderModalContent = (\n loading: boolean,\n error: string | null | undefined,\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): ReactNode => {\n if (loading) {\n return <LoadingSkeleton />;\n }\n\n if (error) {\n return <ErrorContent message={error} />;\n }\n\n if (data) {\n return <PerformanceContent data={data} labels={labels} />;\n }\n\n return null;\n};\n\n/**\n * StudentPerformanceDetailsModal component\n *\n * Displays a modal with detailed student performance information including:\n * - Student name with profile icon\n * - Three main stat cards (grade, correct questions, incorrect questions)\n * - Five metric cards (activities, questions, access, time, last login)\n * - Expandable list of activity progress\n *\n * Exportável: monta sobre o `ReportDetailModal`, então traz o botão \"Baixar\n * relatório\" com PDF (impressão só deste modal) e XLSX. As duas saídas usam o\n * dado que chega por `data` — nada é buscado aqui — e a planilha espelha os\n * blocos acima aba a aba (veja `exportSheets.ts`).\n *\n * O consumidor precisa importar `analytica-frontend-lib/print.css`: sem as\n * regras `@media print` o PDF sai com a página inteira em vez do modal.\n *\n * @example\n * ```tsx\n * <StudentPerformanceDetailsModal\n * isOpen={isModalOpen}\n * onClose={() => setIsModalOpen(false)}\n * data={{\n * studentName: 'Fernanda Rocha',\n * grade: { value: 9.0, performanceLabel: 'Acima da média' },\n * correctQuestions: { value: 8, bestResultTopic: 'Fotossíntese' },\n * incorrectQuestions: { value: 7, hardestTopic: 'Células' },\n * activitiesCompleted: 10,\n * questionsAnswered: 40,\n * accessCount: '00',\n * timeOnline: '00:00:00',\n * lastLogin: '00/00/0000 • 00:00h',\n * activities: [\n * { id: '1', name: 'Atividade 1', correctCount: 30, totalCount: 50 },\n * { id: '2', name: 'Atividade 2', correctCount: 0, totalCount: 0, hasNoData: true },\n * ],\n * }}\n * />\n * ```\n */\nexport const StudentPerformanceDetailsModal = ({\n isOpen,\n onClose,\n data,\n loading = false,\n error = null,\n labels: customLabels,\n}: StudentPerformanceDetailsModalProps) => {\n const labels = useMemo(\n () => ({ ...DEFAULT_PERFORMANCE_DETAILS_LABELS, ...customLabels }),\n [customLabels]\n );\n\n const fileName = `${EXPORT_FILE_PREFIX}-${formatDateForFileName(new Date())}`;\n\n const handleDownloadExcel = useCallback(() => {\n downloadExcel(fileName, buildStudentPerformanceSheets(data, labels));\n }, [fileName, data, labels]);\n\n if (!data && !loading && !error) {\n return null;\n }\n\n return (\n <ReportDetailModal\n isOpen={isOpen}\n onClose={onClose}\n title={labels.title}\n size=\"lg\"\n className={MODAL_HEIGHT_CLASS}\n // Só `fileName`: o PDF é a impressão deste modal, e o\n // `ReportDetailModal` já a faz. Ligar `onDownloadPdf` a um\n // `useReportPrint` local somaria uma segunda impressão — o callback\n // SUBSTITUI a embutida, então o usuário veria dois diálogos.\n fileName={fileName}\n onDownloadExcel={handleDownloadExcel}\n >\n {renderModalContent(loading, error, data, labels)}\n </ReportDetailModal>\n );\n};\n\nexport default StudentPerformanceDetailsModal;\n","import type { ActivityProgress } from './types';\n\n/**\n * Regras de leitura de uma atividade, compartilhadas pela TELA e pela\n * EXPORTAÇÃO.\n *\n * Moram aqui, e não dentro do componente, porque a planilha tem que dizer o\n * mesmo que o card: se as duas tivessem cópias da mesma condição, um ajuste na\n * tela sairia calado do arquivo exportado. Puras, sem React.\n */\n\n/**\n * Uma atividade tem resultado a mostrar?\n *\n * `totalCount > 0` entra junto com a flag porque uma atividade com zero questões\n * dividiria por zero na barra de progresso, e \"0 de 0 corretas\" não informa\n * nada — a tela mostra a mensagem de \"sem dados\" nos dois casos.\n */\nexport const hasActivityData = (activity: ActivityProgress): boolean =>\n !activity.hasNoData && activity.totalCount > 0;\n\n/**\n * Preenche `{correct}` e `{total}` no texto de progresso configurado.\n */\nexport const formatProgressText = (\n template: string,\n correct: number,\n total: number\n): string =>\n template\n .replace('{correct}', String(correct))\n .replace('{total}', String(total));\n","import type { ExcelCell, SheetConfig } from '../../utils/exportExcel';\nimport { formatProgressText, hasActivityData } from './activityProgress';\nimport type {\n StudentPerformanceDetailsData,\n StudentPerformanceDetailsLabels,\n} from './types';\n\n/**\n * Montagem das abas do XLSX do `StudentPerformanceDetailsModal`. Funções puras:\n * nada de React, nada de requisição, nada de escrita em disco — quem grava o\n * arquivo é `downloadExcel`.\n *\n * REGRA QUE GOVERNA ESTE ARQUIVO: a planilha traz o que o modal DESENHA, nada\n * mais e nada menos. As abas espelham os blocos do componente:\n *\n * | Bloco na tela | Onde foi parar |\n * | ------------------------------------------------- | -------------------------- |\n * | nome do estudante (linha com o ícone de usuário) | 1ª linha do resumo |\n * | 3 cards coloridos (NOTA / CORRETAS / INCORRETAS) | resumo, valor + badge |\n * | 2 MetricBox (atividades, questões) | resumo |\n * | 3 MetricBox (acessos, tempo online, último login) | resumo |\n * | lista de atividades (accordion) | aba \"Desempenho atividades\"|\n *\n * O tipo `StudentPerformanceDetailsData` carrega MAIS do que isso:\n * `contentsCompleted` está lá, marcado como deprecated, e o componente nunca o\n * renderiza. Ele fica de fora de propósito — mapear pelo tipo, e não pelo que a\n * tela desenha, poria na planilha um número que o professor nunca viu. Vale para\n * qualquer campo que venha a ser acrescentado ao tipo: só entra aqui depois de\n * entrar na tela.\n */\n\n/**\n * Nome da aba de indicadores. Sem rótulo próprio na tela — o título do modal é\n * do consumidor (`labels.title`, hoje \"Desempenho em 7 dias\") e varia por\n * período, o que daria um nome de aba instável e sem teto de tamanho.\n *\n * 23 caracteres, dentro do limite de 31 do Excel (acima disso o arquivo INTEIRO\n * não abre), e sem nenhum dos caracteres que o formato proíbe (`: \\ / ? * [ ]`).\n */\nexport const SUMMARY_SHEET_NAME = 'Desempenho do estudante';\n\n/**\n * Nome da aba de atividades: 21 caracteres.\n *\n * Repete a palavra do título da seção na tela, mas é um literal e não\n * `labels.activitiesProgressTitle` — um rótulo customizado pelo consumidor\n * poderia passar dos 31 caracteres e derrubar a abertura do arquivo.\n */\nexport const ACTIVITIES_SHEET_NAME = 'Desempenho atividades';\n\n/** O que os badges da tela mostram quando o tópico vem nulo. */\nconst EMPTY_BADGE = '-';\n\n/**\n * Rótulo da linha do nome do estudante.\n *\n * Único texto da planilha que não está na tela: lá o nome aparece ao lado de um\n * ícone de usuário, sem rótulo, e uma tabela de chave/valor precisa de chave.\n */\nconst STUDENT_ROW_LABEL = 'Estudante';\n\nconst SUMMARY_HEADERS = ['Indicador', 'Valor'];\nconst ACTIVITIES_HEADERS = ['Atividade', 'Progresso', 'Descrição'];\n\n/**\n * Aba de indicadores: uma linha por rótulo/valor visível, na ordem da tela.\n *\n * Os rótulos saem do objeto `labels` já resolvido pelo componente, então o que\n * o consumidor customizou na tela aparece igual na planilha.\n *\n * Os valores numéricos vão como número, não como o texto formatado do card:\n * `9` e não `\"9.0\"`. É a única diferença deliberada em relação ao pixel — o\n * `.toFixed(1)` é formatação de exibição, e uma célula numérica é o que permite\n * ordenar e somar do outro lado. O dado é o mesmo.\n */\nexport function buildSummarySheet(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig {\n if (!data) {\n return { name: SUMMARY_SHEET_NAME, headers: SUMMARY_HEADERS, rows: [] };\n }\n\n const rows: ExcelCell[][] = [\n [STUDENT_ROW_LABEL, data.studentName],\n [labels.gradeLabel, data.grade.value],\n [labels.performanceTagLabel, data.grade.performanceLabel || EMPTY_BADGE],\n [labels.correctQuestionsLabel, data.correctQuestions.value],\n [\n labels.bestResultLabel,\n data.correctQuestions.bestResultTopic || EMPTY_BADGE,\n ],\n [labels.incorrectQuestionsLabel, data.incorrectQuestions.value],\n [\n labels.hardestTopicLabel,\n data.incorrectQuestions.hardestTopic || EMPTY_BADGE,\n ],\n [labels.activitiesLabel, data.activitiesCompleted],\n [labels.questionsLabel, data.questionsAnswered],\n [labels.accessCountLabel, data.accessCount],\n [labels.timeOnlineLabel, data.timeOnline],\n [labels.lastLoginLabel, data.lastLogin],\n ];\n\n return { name: SUMMARY_SHEET_NAME, headers: SUMMARY_HEADERS, rows };\n}\n\n/**\n * Aba de atividades: uma linha por card do accordion.\n *\n * \"Progresso\" repete o texto do card — o de progresso quando há dados, a\n * mensagem de \"sem dados\" quando não há — usando as MESMAS funções que o\n * componente usa para decidir e formatar. \"Descrição\" é o conteúdo que o\n * accordion revela ao abrir, com o mesmo fallback.\n *\n * A aba existe mesmo com a lista vazia, embora a tela esconda a seção nesse\n * caso: uma aba só com cabeçalho diz \"não havia atividades\", enquanto um\n * conjunto de abas que muda de tamanho conforme o dado quebra quem consome o\n * arquivo.\n */\nexport function buildActivitiesSheet(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig {\n const rows: ExcelCell[][] = (data?.activities ?? []).map((activity) => [\n activity.name,\n hasActivityData(activity)\n ? formatProgressText(\n labels.activityProgressText,\n activity.correctCount,\n activity.totalCount\n )\n : labels.noDataMessage,\n activity.description || labels.activityDetailsUnavailable,\n ]);\n\n return { name: ACTIVITIES_SHEET_NAME, headers: ACTIVITIES_HEADERS, rows };\n}\n\n/**\n * As abas da planilha deste modal, na ordem em que os blocos aparecem na tela.\n */\nexport function buildStudentPerformanceSheets(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig[] {\n return [buildSummarySheet(data, labels), buildActivitiesSheet(data, labels)];\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-ZO2GA23I.js","../src/components/StudentPerformanceDetailsModal/StudentPerformanceDetailsModal.tsx","../src/components/StudentPerformanceDetailsModal/activityProgress.ts","../src/components/StudentPerformanceDetailsModal/exportSheets.ts"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-ZO2GA23I.js","sourcesContent":[null,"import { useCallback, useMemo, type ReactNode } from 'react';\nimport { UserIcon, TrophyIcon, XCircleIcon } from '@phosphor-icons/react';\nimport ReportDetailModal from '../ReportDetailModal/ReportDetailModal';\nimport Text from '../Text/Text';\nimport Badge from '../Badge/Badge';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { CardAccordation } from '../Accordation/Accordation';\nimport { downloadExcel } from '../../utils/exportExcel';\nimport { formatDateForFileName } from '../../utils/exportFormat';\nimport { formatProgressText, hasActivityData } from './activityProgress';\nimport { buildStudentPerformanceSheets } from './exportSheets';\nimport type {\n StudentPerformanceDetailsModalProps,\n StudentPerformanceDetailsData,\n StudentPerformanceDetailsLabels,\n ActivityProgress,\n} from './types';\nimport { DEFAULT_PERFORMANCE_DETAILS_LABELS } from './types';\nimport { MetricBox } from '../shared/MetricBox';\n\n/**\n * Prefixo do arquivo exportado, em PDF e em XLSX. Recebe a data do dia.\n *\n * Sem o nome do estudante de propósito: ele traz acentos, espaços e às vezes\n * pontuação, e um nome de arquivo pede transliteração — que não existe nesta\n * lib e que não vale a pena inventar aqui.\n */\nconst EXPORT_FILE_PREFIX = 'desempenho-estudante';\n\n/**\n * Teto de altura do modal.\n *\n * Vem do `contentClassName=\"max-h-[80vh]\"` que este componente passava ao\n * `Modal` base; o `ReportDetailModal` não repassa `contentClassName`, então o\n * limite migrou para o `<dialog>` pela prop `className`, onde vence o\n * `max-h-[calc(100dvh-2rem)]` padrão no tailwind-merge (pinado em teste). A\n * rolagem interna continua vindo do `Modal`, que já põe `overflow-y-auto` na\n * área de conteúdo.\n */\nconst MODAL_HEIGHT_CLASS = 'max-h-[80vh]';\n\n/**\n * Performance stat card with colored header and white footer\n * Used for NOTA, CORRETAS, INCORRETAS cards\n */\ninterface PerformanceStatCardProps {\n /** Icon to display */\n icon: ReactNode;\n /** Card label (e.g., \"NOTA\") */\n label: string;\n /** Main value (e.g., \"9.0\" or \"8\") */\n value: string | number;\n /** Secondary label (e.g., \"DESEMPENHO\") */\n secondaryLabel: string;\n /** Badge text (e.g., \"Acima da média\") */\n badgeText: string | null;\n /** Color variant */\n variant: 'orange' | 'green' | 'red';\n}\n\nconst STAT_CARD_COLORS = {\n orange: {\n headerBg: 'bg-warning-background',\n iconBg: 'bg-warning-300',\n valueColor: 'text-warning-600',\n secondaryLabelColor: 'text-warning-500',\n },\n green: {\n headerBg: 'bg-success-200',\n iconBg: 'bg-success-300',\n valueColor: 'text-success-700',\n secondaryLabelColor: 'text-success-600',\n },\n red: {\n headerBg: 'bg-error-100',\n iconBg: 'bg-error-500',\n valueColor: 'text-error-700',\n secondaryLabelColor: 'text-error-300',\n },\n default: {\n headerBg: 'bg-warning-background',\n iconBg: 'bg-warning-300',\n valueColor: 'text-warning-600',\n secondaryLabelColor: 'text-warning-500',\n },\n} as const;\n\n/**\n * Performance stat card component\n */\nconst PerformanceStatCard = ({\n icon,\n label,\n value,\n secondaryLabel,\n badgeText,\n variant,\n}: PerformanceStatCardProps) => {\n const colors = STAT_CARD_COLORS[variant] ?? STAT_CARD_COLORS.default;\n\n return (\n <div className=\"flex flex-col rounded-xl border border-border-50 bg-background overflow-hidden flex-1\">\n {/* Header section with colored background */}\n <div\n className={`flex flex-col items-center justify-center px-2 sm:px-3 py-2 gap-1 ${colors.headerBg}`}\n >\n <Text\n as=\"span\"\n className={`size-8 rounded-full flex items-center justify-center ${colors.iconBg}`}\n >\n {icon}\n </Text>\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className=\"text-text-800 uppercase text-center leading-none\"\n >\n {label}\n </Text>\n <Text\n size=\"xl\"\n weight=\"bold\"\n className={`${colors.valueColor} text-center`}\n >\n {value}\n </Text>\n </div>\n\n {/* Footer section with white background */}\n <div className=\"flex flex-col items-center gap-2 px-2 sm:px-3 py-2\">\n <Text\n size=\"2xs\"\n weight=\"medium\"\n className={`${colors.secondaryLabelColor} uppercase text-center`}\n >\n {secondaryLabel}\n </Text>\n <Badge size=\"large\" action=\"info\">\n {badgeText || '-'}\n </Badge>\n </div>\n </div>\n );\n};\n\n/**\n * Activity progress card with expandable accordion content\n */\ninterface ActivityCardProps {\n /** Activity data */\n activity: ActivityProgress;\n /** No data message */\n noDataMessage: string;\n /** Activity progress text template */\n activityProgressText: string;\n /** Activity details unavailable message */\n activityDetailsUnavailable: string;\n}\n\nconst ActivityAccordionCard = ({\n activity,\n noDataMessage,\n activityProgressText,\n activityDetailsUnavailable,\n}: ActivityCardProps) => {\n // `hasActivityData` e `formatProgressText` vivem em `./activityProgress` para\n // que a aba \"Desempenho atividades\" da planilha decida e formate pelas mesmas\n // regras deste card, em vez de manter uma segunda cópia que pode divergir.\n const hasData = hasActivityData(activity);\n const progressPercentage = hasData\n ? Math.min(\n 100,\n Math.max(0, (activity.correctCount / activity.totalCount) * 100)\n )\n : 0;\n\n return (\n <CardAccordation\n value={activity.id}\n className=\"bg-background rounded-xl border border-border-50\"\n triggerClassName=\"p-4\"\n contentClassName=\"px-4 pb-4\"\n trigger={\n <div className=\"flex flex-col gap-2 flex-1\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {activity.name}\n </Text>\n {hasData ? (\n <div className=\"flex flex-row items-center gap-2\">\n <div className=\"flex-1\">\n <ProgressBar\n value={progressPercentage}\n variant=\"green\"\n size=\"medium\"\n />\n </div>\n <Text\n size=\"xs\"\n weight=\"medium\"\n className=\"text-text-950 whitespace-nowrap\"\n >\n {formatProgressText(\n activityProgressText,\n activity.correctCount,\n activity.totalCount\n )}\n </Text>\n </div>\n ) : (\n <Text size=\"xs\" className=\"text-text-500\">\n {noDataMessage}\n </Text>\n )}\n </div>\n }\n >\n <Text size=\"sm\" className=\"text-text-700\">\n {activity.description || activityDetailsUnavailable}\n </Text>\n </CardAccordation>\n );\n};\n\n/**\n * Loading skeleton for the modal content\n */\nconst LoadingSkeleton = () => (\n <div className=\"flex flex-col gap-4 animate-pulse\">\n <div className=\"h-6 bg-background-200 rounded w-48\" />\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2\">\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n <div className=\"h-48 bg-background-200 rounded-xl\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2\">\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n <div className=\"h-20 bg-background-200 rounded-xl\" />\n </div>\n </div>\n);\n\n/**\n * Error state component\n */\nconst ErrorContent = ({ message }: { message: string }) => (\n <div className=\"flex flex-col items-center justify-center py-8 gap-3\">\n <Text\n as=\"span\"\n className=\"size-12 rounded-full bg-error-100 flex items-center justify-center\"\n >\n <XCircleIcon size={24} className=\"text-error-700\" weight=\"fill\" />\n </Text>\n <Text size=\"md\" className=\"text-error-700 text-center\">\n {message}\n </Text>\n </div>\n);\n\n/**\n * Performance content when data is available\n */\nconst PerformanceContent = ({\n data,\n labels,\n}: {\n data: StudentPerformanceDetailsData;\n labels: StudentPerformanceDetailsLabels;\n}) => (\n <div className=\"flex flex-col gap-4\">\n {/* Student name with icon */}\n <div className=\"flex items-center gap-2 pt-2\">\n <Text\n as=\"span\"\n className=\"size-6 rounded-full bg-primary-100 flex items-center justify-center\"\n >\n <UserIcon size={14} className=\"text-primary-800\" weight=\"fill\" />\n </Text>\n <Text size=\"md\" className=\"text-text-950\">\n {data.studentName}\n </Text>\n </div>\n\n {/* Main stats row - 3 colored cards */}\n <div className=\"flex flex-col sm:flex-row gap-2 sm:gap-3\">\n {/* NOTA card */}\n <PerformanceStatCard\n icon={<TrophyIcon size={16} weight=\"fill\" className=\"text-white\" />}\n label={labels.gradeLabel}\n value={data.grade.value.toFixed(1)}\n secondaryLabel={labels.performanceTagLabel}\n badgeText={data.grade.performanceLabel}\n variant=\"orange\"\n />\n\n {/* CORRETAS card */}\n <PerformanceStatCard\n icon={<TrophyIcon size={16} weight=\"fill\" className=\"text-text-950\" />}\n label={labels.correctQuestionsLabel}\n value={data.correctQuestions.value}\n secondaryLabel={labels.bestResultLabel}\n badgeText={data.correctQuestions.bestResultTopic}\n variant=\"green\"\n />\n\n {/* INCORRETAS card */}\n <PerformanceStatCard\n icon={<XCircleIcon size={16} weight=\"fill\" className=\"text-white\" />}\n label={labels.incorrectQuestionsLabel}\n value={data.incorrectQuestions.value}\n secondaryLabel={labels.hardestTopicLabel}\n badgeText={data.incorrectQuestions.hardestTopic}\n variant=\"red\"\n />\n </div>\n\n {/* Secondary stats row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-3\">\n <MetricBox\n label={labels.activitiesLabel}\n value={data.activitiesCompleted}\n />\n <MetricBox label={labels.questionsLabel} value={data.questionsAnswered} />\n </div>\n\n {/* Tertiary stats row */}\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-2 sm:gap-3\">\n <MetricBox label={labels.accessCountLabel} value={data.accessCount} />\n <MetricBox label={labels.timeOnlineLabel} value={data.timeOnline} />\n <MetricBox label={labels.lastLoginLabel} value={data.lastLogin} />\n </div>\n\n {/* Activities progress section */}\n {data.activities.length > 0 && (\n <div className=\"flex flex-col gap-4 pt-4\">\n <Text size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n {labels.activitiesProgressTitle}\n </Text>\n <div className=\"flex flex-col gap-2\">\n {data.activities.map((activity) => (\n <ActivityAccordionCard\n key={activity.id}\n activity={activity}\n noDataMessage={labels.noDataMessage}\n activityProgressText={labels.activityProgressText}\n activityDetailsUnavailable={labels.activityDetailsUnavailable}\n />\n ))}\n </div>\n </div>\n )}\n </div>\n);\n\n/**\n * Renders the modal content based on loading, error, and data state\n */\nconst renderModalContent = (\n loading: boolean,\n error: string | null | undefined,\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): ReactNode => {\n if (loading) {\n return <LoadingSkeleton />;\n }\n\n if (error) {\n return <ErrorContent message={error} />;\n }\n\n if (data) {\n return <PerformanceContent data={data} labels={labels} />;\n }\n\n return null;\n};\n\n/**\n * StudentPerformanceDetailsModal component\n *\n * Displays a modal with detailed student performance information including:\n * - Student name with profile icon\n * - Three main stat cards (grade, correct questions, incorrect questions)\n * - Five metric cards (activities, questions, access, time, last login)\n * - Expandable list of activity progress\n *\n * Exportável: monta sobre o `ReportDetailModal`, então traz o botão \"Baixar\n * relatório\" com PDF (impressão só deste modal) e XLSX. As duas saídas usam o\n * dado que chega por `data` — nada é buscado aqui — e a planilha espelha os\n * blocos acima aba a aba (veja `exportSheets.ts`).\n *\n * O consumidor precisa importar `analytica-frontend-lib/print.css`: sem as\n * regras `@media print` o PDF sai com a página inteira em vez do modal.\n *\n * @example\n * ```tsx\n * <StudentPerformanceDetailsModal\n * isOpen={isModalOpen}\n * onClose={() => setIsModalOpen(false)}\n * data={{\n * studentName: 'Fernanda Rocha',\n * grade: { value: 9.0, performanceLabel: 'Acima da média' },\n * correctQuestions: { value: 8, bestResultTopic: 'Fotossíntese' },\n * incorrectQuestions: { value: 7, hardestTopic: 'Células' },\n * activitiesCompleted: 10,\n * questionsAnswered: 40,\n * accessCount: '00',\n * timeOnline: '00:00:00',\n * lastLogin: '00/00/0000 • 00:00h',\n * activities: [\n * { id: '1', name: 'Atividade 1', correctCount: 30, totalCount: 50 },\n * { id: '2', name: 'Atividade 2', correctCount: 0, totalCount: 0, hasNoData: true },\n * ],\n * }}\n * />\n * ```\n */\nexport const StudentPerformanceDetailsModal = ({\n isOpen,\n onClose,\n data,\n loading = false,\n error = null,\n labels: customLabels,\n}: StudentPerformanceDetailsModalProps) => {\n const labels = useMemo(\n () => ({ ...DEFAULT_PERFORMANCE_DETAILS_LABELS, ...customLabels }),\n [customLabels]\n );\n\n const fileName = `${EXPORT_FILE_PREFIX}-${formatDateForFileName(new Date())}`;\n\n const handleDownloadExcel = useCallback(() => {\n downloadExcel(fileName, buildStudentPerformanceSheets(data, labels));\n }, [fileName, data, labels]);\n\n if (!data && !loading && !error) {\n return null;\n }\n\n return (\n <ReportDetailModal\n isOpen={isOpen}\n onClose={onClose}\n title={labels.title}\n size=\"lg\"\n className={MODAL_HEIGHT_CLASS}\n // Só `fileName`: o PDF é a impressão deste modal, e o\n // `ReportDetailModal` já a faz. Ligar `onDownloadPdf` a um\n // `useReportPrint` local somaria uma segunda impressão — o callback\n // SUBSTITUI a embutida, então o usuário veria dois diálogos.\n fileName={fileName}\n onDownloadExcel={handleDownloadExcel}\n >\n {renderModalContent(loading, error, data, labels)}\n </ReportDetailModal>\n );\n};\n\nexport default StudentPerformanceDetailsModal;\n","import type { ActivityProgress } from './types';\n\n/**\n * Regras de leitura de uma atividade, compartilhadas pela TELA e pela\n * EXPORTAÇÃO.\n *\n * Moram aqui, e não dentro do componente, porque a planilha tem que dizer o\n * mesmo que o card: se as duas tivessem cópias da mesma condição, um ajuste na\n * tela sairia calado do arquivo exportado. Puras, sem React.\n */\n\n/**\n * Uma atividade tem resultado a mostrar?\n *\n * `totalCount > 0` entra junto com a flag porque uma atividade com zero questões\n * dividiria por zero na barra de progresso, e \"0 de 0 corretas\" não informa\n * nada — a tela mostra a mensagem de \"sem dados\" nos dois casos.\n */\nexport const hasActivityData = (activity: ActivityProgress): boolean =>\n !activity.hasNoData && activity.totalCount > 0;\n\n/**\n * Preenche `{correct}` e `{total}` no texto de progresso configurado.\n */\nexport const formatProgressText = (\n template: string,\n correct: number,\n total: number\n): string =>\n template\n .replace('{correct}', String(correct))\n .replace('{total}', String(total));\n","import type { ExcelCell, SheetConfig } from '../../utils/exportExcel';\nimport { formatProgressText, hasActivityData } from './activityProgress';\nimport type {\n StudentPerformanceDetailsData,\n StudentPerformanceDetailsLabels,\n} from './types';\n\n/**\n * Montagem das abas do XLSX do `StudentPerformanceDetailsModal`. Funções puras:\n * nada de React, nada de requisição, nada de escrita em disco — quem grava o\n * arquivo é `downloadExcel`.\n *\n * REGRA QUE GOVERNA ESTE ARQUIVO: a planilha traz o que o modal DESENHA, nada\n * mais e nada menos. As abas espelham os blocos do componente:\n *\n * | Bloco na tela | Onde foi parar |\n * | ------------------------------------------------- | -------------------------- |\n * | nome do estudante (linha com o ícone de usuário) | 1ª linha do resumo |\n * | 3 cards coloridos (NOTA / CORRETAS / INCORRETAS) | resumo, valor + badge |\n * | 2 MetricBox (atividades, questões) | resumo |\n * | 3 MetricBox (acessos, tempo online, último login) | resumo |\n * | lista de atividades (accordion) | aba \"Desempenho atividades\"|\n *\n * O tipo `StudentPerformanceDetailsData` carrega MAIS do que isso:\n * `contentsCompleted` está lá, marcado como deprecated, e o componente nunca o\n * renderiza. Ele fica de fora de propósito — mapear pelo tipo, e não pelo que a\n * tela desenha, poria na planilha um número que o professor nunca viu. Vale para\n * qualquer campo que venha a ser acrescentado ao tipo: só entra aqui depois de\n * entrar na tela.\n */\n\n/**\n * Nome da aba de indicadores. Sem rótulo próprio na tela — o título do modal é\n * do consumidor (`labels.title`, hoje \"Desempenho em 7 dias\") e varia por\n * período, o que daria um nome de aba instável e sem teto de tamanho.\n *\n * 23 caracteres, dentro do limite de 31 do Excel (acima disso o arquivo INTEIRO\n * não abre), e sem nenhum dos caracteres que o formato proíbe (`: \\ / ? * [ ]`).\n */\nexport const SUMMARY_SHEET_NAME = 'Desempenho do estudante';\n\n/**\n * Nome da aba de atividades: 21 caracteres.\n *\n * Repete a palavra do título da seção na tela, mas é um literal e não\n * `labels.activitiesProgressTitle` — um rótulo customizado pelo consumidor\n * poderia passar dos 31 caracteres e derrubar a abertura do arquivo.\n */\nexport const ACTIVITIES_SHEET_NAME = 'Desempenho atividades';\n\n/** O que os badges da tela mostram quando o tópico vem nulo. */\nconst EMPTY_BADGE = '-';\n\n/**\n * Rótulo da linha do nome do estudante.\n *\n * Único texto da planilha que não está na tela: lá o nome aparece ao lado de um\n * ícone de usuário, sem rótulo, e uma tabela de chave/valor precisa de chave.\n */\nconst STUDENT_ROW_LABEL = 'Estudante';\n\nconst SUMMARY_HEADERS = ['Indicador', 'Valor'];\nconst ACTIVITIES_HEADERS = ['Atividade', 'Progresso', 'Descrição'];\n\n/**\n * Aba de indicadores: uma linha por rótulo/valor visível, na ordem da tela.\n *\n * Os rótulos saem do objeto `labels` já resolvido pelo componente, então o que\n * o consumidor customizou na tela aparece igual na planilha.\n *\n * Os valores numéricos vão como número, não como o texto formatado do card:\n * `9` e não `\"9.0\"`. É a única diferença deliberada em relação ao pixel — o\n * `.toFixed(1)` é formatação de exibição, e uma célula numérica é o que permite\n * ordenar e somar do outro lado. O dado é o mesmo.\n */\nexport function buildSummarySheet(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig {\n if (!data) {\n return { name: SUMMARY_SHEET_NAME, headers: SUMMARY_HEADERS, rows: [] };\n }\n\n const rows: ExcelCell[][] = [\n [STUDENT_ROW_LABEL, data.studentName],\n [labels.gradeLabel, data.grade.value],\n [labels.performanceTagLabel, data.grade.performanceLabel || EMPTY_BADGE],\n [labels.correctQuestionsLabel, data.correctQuestions.value],\n [\n labels.bestResultLabel,\n data.correctQuestions.bestResultTopic || EMPTY_BADGE,\n ],\n [labels.incorrectQuestionsLabel, data.incorrectQuestions.value],\n [\n labels.hardestTopicLabel,\n data.incorrectQuestions.hardestTopic || EMPTY_BADGE,\n ],\n [labels.activitiesLabel, data.activitiesCompleted],\n [labels.questionsLabel, data.questionsAnswered],\n [labels.accessCountLabel, data.accessCount],\n [labels.timeOnlineLabel, data.timeOnline],\n [labels.lastLoginLabel, data.lastLogin],\n ];\n\n return { name: SUMMARY_SHEET_NAME, headers: SUMMARY_HEADERS, rows };\n}\n\n/**\n * Aba de atividades: uma linha por card do accordion.\n *\n * \"Progresso\" repete o texto do card — o de progresso quando há dados, a\n * mensagem de \"sem dados\" quando não há — usando as MESMAS funções que o\n * componente usa para decidir e formatar. \"Descrição\" é o conteúdo que o\n * accordion revela ao abrir, com o mesmo fallback.\n *\n * A aba existe mesmo com a lista vazia, embora a tela esconda a seção nesse\n * caso: uma aba só com cabeçalho diz \"não havia atividades\", enquanto um\n * conjunto de abas que muda de tamanho conforme o dado quebra quem consome o\n * arquivo.\n */\nexport function buildActivitiesSheet(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig {\n const rows: ExcelCell[][] = (data?.activities ?? []).map((activity) => [\n activity.name,\n hasActivityData(activity)\n ? formatProgressText(\n labels.activityProgressText,\n activity.correctCount,\n activity.totalCount\n )\n : labels.noDataMessage,\n activity.description || labels.activityDetailsUnavailable,\n ]);\n\n return { name: ACTIVITIES_SHEET_NAME, headers: ACTIVITIES_HEADERS, rows };\n}\n\n/**\n * As abas da planilha deste modal, na ordem em que os blocos aparecem na tela.\n */\nexport function buildStudentPerformanceSheets(\n data: StudentPerformanceDetailsData | null,\n labels: StudentPerformanceDetailsLabels\n): SheetConfig[] {\n return [buildSummarySheet(data, labels), buildActivitiesSheet(data, labels)];\n}\n"]}
@@ -10,10 +10,10 @@ var _chunkOITBOOARjs = require('./chunk-OITBOOAR.js');
10
10
  var _chunkYNQTCV4Wjs = require('./chunk-YNQTCV4W.js');
11
11
 
12
12
 
13
- var _chunkSADSJ2MVjs = require('./chunk-SADSJ2MV.js');
13
+ var _chunkQVGUYPXOjs = require('./chunk-QVGUYPXO.js');
14
14
 
15
15
 
16
- var _chunkBKBDQXZXjs = require('./chunk-BKBDQXZX.js');
16
+ var _chunkAOYPUZROjs = require('./chunk-AOYPUZRO.js');
17
17
 
18
18
 
19
19
  var _chunkBWCGUJ6Yjs = require('./chunk-BWCGUJ6Y.js');
@@ -77,7 +77,7 @@ var _chunkCFD6NHL7js = require('./chunk-CFD6NHL7.js');
77
77
  var _chunkJVDEOFXDjs = require('./chunk-JVDEOFXD.js');
78
78
 
79
79
 
80
- var _chunk23PAYKWBjs = require('./chunk-23PAYKWB.js');
80
+ var _chunkKM27ZDHZjs = require('./chunk-KM27ZDHZ.js');
81
81
 
82
82
 
83
83
  var _chunkRSO6RH5Kjs = require('./chunk-RSO6RH5K.js');
@@ -749,7 +749,7 @@ var StudentPerformanceModal = ({
749
749
  return null;
750
750
  }
751
751
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
752
- _chunk23PAYKWBjs.Modal_default,
752
+ _chunkKM27ZDHZjs.Modal_default,
753
753
  {
754
754
  isOpen,
755
755
  onClose,
@@ -1445,7 +1445,7 @@ var StudentActivityPerformanceModal = ({
1445
1445
  return null;
1446
1446
  };
1447
1447
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1448
- _chunk23PAYKWBjs.Modal_default,
1448
+ _chunkKM27ZDHZjs.Modal_default,
1449
1449
  {
1450
1450
  isOpen,
1451
1451
  onClose: handleClose,
@@ -1523,7 +1523,7 @@ var LessonContent = ({
1523
1523
  const hasBoards = boardImages.length > 0;
1524
1524
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-4", children: [
1525
1525
  hasVideo ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1526
- _chunkSADSJ2MVjs.VideoPlayer_default,
1526
+ _chunkQVGUYPXOjs.VideoPlayer_default,
1527
1527
  {
1528
1528
  src: lesson.urlVideo,
1529
1529
  poster: lesson.urlCover,
@@ -1557,7 +1557,7 @@ var LessonContent = ({
1557
1557
  {
1558
1558
  className: "flex flex-row rounded-xl bg-background-50",
1559
1559
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1560
- _chunkBKBDQXZXjs.Whiteboard_default,
1560
+ _chunkAOYPUZROjs.Whiteboard_default,
1561
1561
  {
1562
1562
  images: [image],
1563
1563
  showDownload: true,
@@ -1658,7 +1658,7 @@ var RecommendedLessonViewModal = ({
1658
1658
  const badgeAction = _chunk6WDBJAVCjs.getStatusBadgeAction.call(void 0, status);
1659
1659
  const footer = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex justify-end", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXN2HPVVHjs.Button_default, { variant: "outline", onClick: onClose, children: "Fechar" }) });
1660
1660
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1661
- _chunk23PAYKWBjs.Modal_default,
1661
+ _chunkKM27ZDHZjs.Modal_default,
1662
1662
  {
1663
1663
  isOpen,
1664
1664
  onClose,
@@ -1954,4 +1954,4 @@ var RecommendedLessonDetails_default = RecommendedLessonDetails;
1954
1954
 
1955
1955
 
1956
1956
  exports.StudentPerformanceModal = StudentPerformanceModal; exports.RecommendedLessonDetails_default = RecommendedLessonDetails_default;
1957
- //# sourceMappingURL=chunk-ZN3763VZ.js.map
1957
+ //# sourceMappingURL=chunk-ZQLW4EJB.js.map