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.
- package/dist/AccessReportModal/index.js +3 -3
- package/dist/AccessReportModal/index.mjs +2 -2
- package/dist/ActivitiesHistory/index.js +49 -49
- package/dist/ActivitiesHistory/index.mjs +48 -48
- package/dist/ActivityCardQuestionBanks/index.js +49 -49
- package/dist/ActivityCardQuestionBanks/index.mjs +48 -48
- package/dist/ActivityCardQuestionPreview/index.js +49 -49
- package/dist/ActivityCardQuestionPreview/index.mjs +48 -48
- package/dist/ActivityCreate/ActivityCreateContent/index.js +49 -49
- package/dist/ActivityCreate/ActivityCreateContent/index.mjs +48 -48
- package/dist/ActivityCreate/index.js +49 -49
- package/dist/ActivityCreate/index.mjs +48 -48
- package/dist/ActivityDetails/index.js +49 -49
- package/dist/ActivityDetails/index.mjs +48 -48
- package/dist/ActivityFilters/index.js +49 -49
- package/dist/ActivityFilters/index.mjs +48 -48
- package/dist/ActivityPageLayout/index.js +49 -49
- package/dist/ActivityPageLayout/index.mjs +48 -48
- package/dist/ActivityPreview/index.js +49 -49
- package/dist/ActivityPreview/index.mjs +48 -48
- package/dist/AlertManager/index.js +49 -49
- package/dist/AlertManager/index.mjs +48 -48
- package/dist/AlertManagerView/index.js +3 -3
- package/dist/AlertManagerView/index.mjs +2 -2
- package/dist/AnnouncementModal/index.js +3 -3
- package/dist/AnnouncementModal/index.mjs +2 -2
- package/dist/AnnouncementsGate/index.js +4 -4
- package/dist/AnnouncementsGate/index.mjs +3 -3
- package/dist/AnswerKeyModal/index.js +3 -3
- package/dist/AnswerKeyModal/index.mjs +2 -2
- package/dist/AppHeader/index.js +6 -6
- package/dist/AppHeader/index.mjs +5 -5
- package/dist/CalendarCard/index.js +4 -4
- package/dist/CalendarCard/index.mjs +3 -3
- package/dist/Chat/index.js +3 -3
- package/dist/Chat/index.mjs +2 -2
- package/dist/CheckBox/index.d.ts.map +1 -1
- package/dist/CheckBox/index.js +2 -2
- package/dist/CheckBox/index.mjs +1 -1
- package/dist/CheckBoxGroup/index.js +49 -49
- package/dist/CheckBoxGroup/index.mjs +48 -48
- package/dist/ComparatorChart/ComparatorView/index.js +3 -3
- package/dist/ComparatorChart/ComparatorView/index.mjs +2 -2
- package/dist/CorrectActivityModal/index.js +7 -7
- package/dist/CorrectActivityModal/index.mjs +6 -6
- package/dist/DownloadButton/index.js +4 -4
- package/dist/DownloadButton/index.mjs +3 -3
- package/dist/DownloadModal/index.js +3 -3
- package/dist/DownloadModal/index.mjs +2 -2
- package/dist/DropdownMenu/index.js +3 -3
- package/dist/DropdownMenu/index.mjs +2 -2
- package/dist/EnemClassroomStartModal/index.d.ts.map +1 -1
- package/dist/EnemClassroomStartModal/index.js +7 -7
- package/dist/EnemClassroomStartModal/index.mjs +6 -6
- package/dist/EnemMoment/index.js +57 -57
- package/dist/EnemMoment/index.mjs +51 -51
- package/dist/EnemMomentStudentModal/index.js +50 -50
- package/dist/EnemMomentStudentModal/index.mjs +48 -48
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +49 -49
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +48 -48
- package/dist/ExamPageLayout/index.js +49 -49
- package/dist/ExamPageLayout/index.mjs +48 -48
- package/dist/Forum/index.js +5 -5
- package/dist/Forum/index.mjs +4 -4
- package/dist/KnowledgePerformanceCard/index.js +51 -51
- package/dist/KnowledgePerformanceCard/index.mjs +50 -50
- package/dist/LessonCardPreview/index.js +49 -49
- package/dist/LessonCardPreview/index.mjs +48 -48
- package/dist/LessonMediaSections/index.js +49 -49
- package/dist/LessonMediaSections/index.mjs +48 -48
- package/dist/LessonsCatalog/index.js +49 -49
- package/dist/LessonsCatalog/index.mjs +48 -48
- package/dist/Modal/index.d.ts.map +1 -1
- package/dist/Modal/index.js +2 -2
- package/dist/Modal/index.mjs +1 -1
- package/dist/MultiCheckSelect/index.js +5 -5
- package/dist/MultiCheckSelect/index.mjs +4 -4
- package/dist/MultiSearchSelect/index.js +3 -3
- package/dist/MultiSearchSelect/index.mjs +2 -2
- package/dist/MultipleChoice/index.js +3 -3
- package/dist/MultipleChoice/index.mjs +2 -2
- package/dist/NotificationCard/index.js +4 -4
- package/dist/NotificationCard/index.mjs +3 -3
- package/dist/PerformanceReportModal/index.js +3 -3
- package/dist/PerformanceReportModal/index.mjs +2 -2
- package/dist/PrintableUsersTable/index.js +49 -49
- package/dist/PrintableUsersTable/index.mjs +48 -48
- package/dist/ProgressModal/index.js +3 -3
- package/dist/ProgressModal/index.mjs +2 -2
- package/dist/QuestionsPerformanceCard/index.js +50 -50
- package/dist/QuestionsPerformanceCard/index.mjs +49 -49
- package/dist/Quiz/index.js +6 -6
- package/dist/Quiz/index.mjs +5 -5
- package/dist/RecommendedLessonCreate/index.js +49 -49
- package/dist/RecommendedLessonCreate/index.mjs +48 -48
- package/dist/RecommendedLessonDetails/index.js +7 -7
- package/dist/RecommendedLessonDetails/index.mjs +6 -6
- package/dist/RecommendedLessonsHistory/index.js +49 -49
- package/dist/RecommendedLessonsHistory/index.mjs +48 -48
- package/dist/ReportDetailModal/index.js +4 -4
- package/dist/ReportDetailModal/index.mjs +3 -3
- package/dist/RichEditor/index.js +5 -5
- package/dist/RichEditor/index.mjs +4 -4
- package/dist/{RichEditorCore-6MTYSSKS.mjs → RichEditorCore-2S5YUAOI.mjs} +5 -5
- package/dist/{RichEditorCore-KDARBEK4.js → RichEditorCore-N7A2445B.js} +16 -16
- package/dist/{RichEditorCore-KDARBEK4.js.map → RichEditorCore-N7A2445B.js.map} +1 -1
- package/dist/Search/index.js +4 -4
- package/dist/Search/index.mjs +3 -3
- package/dist/SendActivityModal/SendActivityModal.js +49 -49
- package/dist/SendActivityModal/SendActivityModal.mjs +48 -48
- package/dist/SendActivityModal/index.js +49 -49
- package/dist/SendActivityModal/index.mjs +48 -48
- package/dist/SendLessonModal/index.js +49 -49
- package/dist/SendLessonModal/index.mjs +48 -48
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +49 -49
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +48 -48
- package/dist/SimulatedPerformance/useSimulatedPerformance/index.js +5 -5
- package/dist/SimulatedPerformance/useSimulatedPerformance/index.mjs +4 -4
- package/dist/SimulatedStudentSimulationsModal/index.js +6 -6
- package/dist/SimulatedStudentSimulationsModal/index.mjs +5 -5
- package/dist/SimulationsPage/index.js +49 -49
- package/dist/SimulationsPage/index.mjs +48 -48
- package/dist/StudentLessonProgressModal/index.js +3 -3
- package/dist/StudentLessonProgressModal/index.mjs +2 -2
- package/dist/StudentPerformanceDetailsModal/index.js +5 -5
- package/dist/StudentPerformanceDetailsModal/index.mjs +4 -4
- package/dist/Support/TicketModal/index.js +3 -3
- package/dist/Support/TicketModal/index.mjs +2 -2
- package/dist/Support/index.js +4 -4
- package/dist/Support/index.mjs +3 -3
- package/dist/SupportChatComposer/index.js +3 -3
- package/dist/SupportChatComposer/index.mjs +2 -2
- package/dist/SupportChatEvent/index.js +3 -3
- package/dist/SupportChatEvent/index.mjs +2 -2
- package/dist/SupportChatMessage/index.js +3 -3
- package/dist/SupportChatMessage/index.mjs +2 -2
- package/dist/SupportChatWidget/index.js +3 -3
- package/dist/SupportChatWidget/index.mjs +2 -2
- package/dist/SupportConcludeModal/index.js +4 -4
- package/dist/SupportConcludeModal/index.mjs +3 -3
- package/dist/SupportConversationHeader/index.js +4 -4
- package/dist/SupportConversationHeader/index.mjs +3 -3
- package/dist/SupportConversationMessages/index.js +5 -5
- package/dist/SupportConversationMessages/index.mjs +4 -4
- package/dist/SupportTicketConversation/index.js +12 -12
- package/dist/SupportTicketConversation/index.mjs +11 -11
- package/dist/TableProvider/index.js +49 -49
- package/dist/TableProvider/index.mjs +48 -48
- package/dist/UnifiedDraftModelPage/index.js +49 -49
- package/dist/UnifiedDraftModelPage/index.mjs +48 -48
- package/dist/UnifiedHistoryPage/index.js +49 -49
- package/dist/UnifiedHistoryPage/index.mjs +48 -48
- package/dist/VideoPlayer/index.js +5 -5
- package/dist/VideoPlayer/index.mjs +4 -4
- package/dist/Whiteboard/index.js +3 -3
- package/dist/Whiteboard/index.mjs +2 -2
- package/dist/{chunk-B6QOBHZM.mjs → chunk-24BEZK43.mjs} +2 -2
- package/dist/{chunk-CYSODZGX.js → chunk-27Y6V3GM.js} +169 -66
- package/dist/chunk-27Y6V3GM.js.map +1 -0
- package/dist/{chunk-OQTXTDSK.mjs → chunk-2OJ3QSLU.mjs} +3 -3
- package/dist/{chunk-VDUO65VK.mjs → chunk-3EGJJDIM.mjs} +2 -2
- package/dist/{chunk-VDUO65VK.mjs.map → chunk-3EGJJDIM.mjs.map} +1 -1
- package/dist/{chunk-4OUBPEVS.mjs → chunk-3HKLGNUE.mjs} +3 -3
- package/dist/{chunk-YA7XLH7D.js → chunk-3MFACEZ2.js} +3 -3
- package/dist/{chunk-YA7XLH7D.js.map → chunk-3MFACEZ2.js.map} +1 -1
- package/dist/{chunk-IHBWJM4C.mjs → chunk-3Q7RJKUP.mjs} +2 -2
- package/dist/{chunk-PY6WD6SB.mjs → chunk-3WRB4WCD.mjs} +4 -4
- package/dist/{chunk-4RHCBMMP.js → chunk-46VHAL4S.js} +3 -3
- package/dist/{chunk-4RHCBMMP.js.map → chunk-46VHAL4S.js.map} +1 -1
- package/dist/{chunk-POT3ALY3.js → chunk-5DINXU6W.js} +1 -1
- package/dist/{chunk-POT3ALY3.js.map → chunk-5DINXU6W.js.map} +1 -1
- package/dist/{chunk-L52NFNFK.mjs → chunk-5VC4BK65.mjs} +3 -3
- package/dist/{chunk-B67RLIUM.js → chunk-72D7G6YQ.js} +3 -3
- package/dist/{chunk-B67RLIUM.js.map → chunk-72D7G6YQ.js.map} +1 -1
- package/dist/{chunk-C65DG2RI.mjs → chunk-7FSBWUOM.mjs} +2 -2
- package/dist/{chunk-QWMFWYII.js → chunk-7IZNLARX.js} +3 -3
- package/dist/{chunk-QWMFWYII.js.map → chunk-7IZNLARX.js.map} +1 -1
- package/dist/{chunk-ZCBT2BUZ.js → chunk-7RFHXFFV.js} +13 -13
- package/dist/{chunk-ZCBT2BUZ.js.map → chunk-7RFHXFFV.js.map} +1 -1
- package/dist/{chunk-EMWX6IKP.js → chunk-7VJP5D26.js} +13 -13
- package/dist/{chunk-EMWX6IKP.js.map → chunk-7VJP5D26.js.map} +1 -1
- package/dist/{chunk-HCQJR5CE.mjs → chunk-7ZWEBNFB.mjs} +2 -2
- package/dist/{chunk-3RHNJUXU.mjs → chunk-ACSEJOW6.mjs} +2 -2
- package/dist/{chunk-WQZHUW7C.mjs → chunk-ANFHLSDB.mjs} +3 -3
- package/dist/{chunk-H6NQOLQY.mjs → chunk-ANFLWCVD.mjs} +2 -2
- package/dist/{chunk-BKBDQXZX.js → chunk-AOYPUZRO.js} +3 -3
- package/dist/{chunk-BKBDQXZX.js.map → chunk-AOYPUZRO.js.map} +1 -1
- package/dist/{chunk-5WS6IENQ.mjs → chunk-AU2HLQDJ.mjs} +2 -2
- package/dist/{chunk-P6FX3NT6.mjs → chunk-AZGDHAP5.mjs} +2 -2
- package/dist/{chunk-NCGO7FPB.js → chunk-AZKNYREH.js} +8 -8
- package/dist/{chunk-NCGO7FPB.js.map → chunk-AZKNYREH.js.map} +1 -1
- package/dist/{chunk-R6MTPYYW.js → chunk-AZTBNERQ.js} +3 -3
- package/dist/{chunk-R6MTPYYW.js.map → chunk-AZTBNERQ.js.map} +1 -1
- package/dist/{chunk-NPB2JTYC.mjs → chunk-BO3QVQ37.mjs} +4 -4
- package/dist/{chunk-2WI4Q2I2.mjs → chunk-BPMWRFSI.mjs} +4 -4
- package/dist/{chunk-TEW33GLR.mjs → chunk-BXN3VO6I.mjs} +2 -2
- package/dist/{chunk-7ZB3YHF2.mjs → chunk-C6LC5LD5.mjs} +1 -1
- package/dist/{chunk-7ZB3YHF2.mjs.map → chunk-C6LC5LD5.mjs.map} +1 -1
- package/dist/{chunk-5HONWYQA.js → chunk-C7ILTLT5.js} +3 -3
- package/dist/{chunk-5HONWYQA.js.map → chunk-C7ILTLT5.js.map} +1 -1
- package/dist/{chunk-OES54VU6.mjs → chunk-CFV2OZ3P.mjs} +116 -13
- package/dist/chunk-CFV2OZ3P.mjs.map +1 -0
- package/dist/{chunk-4OVIHZ3I.js → chunk-CGXWWPEA.js} +6 -6
- package/dist/{chunk-4OVIHZ3I.js.map → chunk-CGXWWPEA.js.map} +1 -1
- package/dist/{chunk-MNU45FM3.js → chunk-COT4KW3H.js} +2 -2
- package/dist/{chunk-MNU45FM3.js.map → chunk-COT4KW3H.js.map} +1 -1
- package/dist/{chunk-JDQUXG75.js → chunk-CVDNCU6L.js} +3 -3
- package/dist/{chunk-JDQUXG75.js.map → chunk-CVDNCU6L.js.map} +1 -1
- package/dist/{chunk-HBZHWSMZ.mjs → chunk-D46PJSQ7.mjs} +13 -13
- package/dist/{chunk-ARYIJ4RU.js → chunk-DDORKDQO.js} +3 -3
- package/dist/{chunk-ARYIJ4RU.js.map → chunk-DDORKDQO.js.map} +1 -1
- package/dist/{chunk-7YQTDJFA.js → chunk-DKXSR2V3.js} +3 -3
- package/dist/{chunk-7YQTDJFA.js.map → chunk-DKXSR2V3.js.map} +1 -1
- package/dist/{chunk-I6EAPPBL.mjs → chunk-DZMYIOKZ.mjs} +5 -5
- package/dist/{chunk-SKM7NFPY.js → chunk-EPXAFJVW.js} +6 -6
- package/dist/{chunk-SKM7NFPY.js.map → chunk-EPXAFJVW.js.map} +1 -1
- package/dist/{chunk-AYYMS23L.js → chunk-ESIQ2GB4.js} +7 -7
- package/dist/{chunk-AYYMS23L.js.map → chunk-ESIQ2GB4.js.map} +1 -1
- package/dist/{chunk-NU2WNYOD.js → chunk-EW7HGUFV.js} +87 -87
- package/dist/{chunk-NU2WNYOD.js.map → chunk-EW7HGUFV.js.map} +1 -1
- package/dist/{chunk-I4QGQMXI.mjs → chunk-EXYXZFNK.mjs} +2 -2
- package/dist/{chunk-RBJAO57A.js → chunk-FAAR4XC5.js} +3 -3
- package/dist/{chunk-RBJAO57A.js.map → chunk-FAAR4XC5.js.map} +1 -1
- package/dist/{chunk-N2VNE3ON.js → chunk-FDTDJDZI.js} +13 -13
- package/dist/{chunk-N2VNE3ON.js.map → chunk-FDTDJDZI.js.map} +1 -1
- package/dist/{chunk-SU5Q64TW.mjs → chunk-FVCTKS33.mjs} +6 -5
- package/dist/chunk-FVCTKS33.mjs.map +1 -0
- package/dist/{chunk-AVHZHWI3.js → chunk-GBIFSFWJ.js} +15 -15
- package/dist/{chunk-AVHZHWI3.js.map → chunk-GBIFSFWJ.js.map} +1 -1
- package/dist/{chunk-KSUPPAKH.js → chunk-GIQSHTB5.js} +9 -9
- package/dist/{chunk-KSUPPAKH.js.map → chunk-GIQSHTB5.js.map} +1 -1
- package/dist/{chunk-4QBKD4JN.js → chunk-GUUJYGXF.js} +3 -3
- package/dist/{chunk-4QBKD4JN.js.map → chunk-GUUJYGXF.js.map} +1 -1
- package/dist/{chunk-M4O46VDY.mjs → chunk-GX2PKLN2.mjs} +2 -2
- package/dist/{chunk-5BUTINIM.mjs → chunk-HLVLSWKD.mjs} +3 -3
- package/dist/{chunk-FU23WOTY.js → chunk-HSV5VZ4Z.js} +6 -6
- package/dist/{chunk-FU23WOTY.js.map → chunk-HSV5VZ4Z.js.map} +1 -1
- package/dist/{chunk-MMS2ELDK.mjs → chunk-IIQCS3A2.mjs} +2 -2
- package/dist/{chunk-NKWLCZUZ.mjs → chunk-JGGUMHM6.mjs} +2 -2
- package/dist/{chunk-HT2GREFH.js → chunk-JLD46DL7.js} +11 -11
- package/dist/{chunk-HT2GREFH.js.map → chunk-JLD46DL7.js.map} +1 -1
- package/dist/{chunk-NVL5TKWD.mjs → chunk-JYFLOQYT.mjs} +2 -2
- package/dist/{chunk-DWGDEUEW.mjs → chunk-KG563P7F.mjs} +5 -5
- package/dist/{chunk-TCIC5556.mjs → chunk-KH4TEDGD.mjs} +2 -2
- package/dist/{chunk-23PAYKWB.js → chunk-KM27ZDHZ.js} +2 -2
- package/dist/{chunk-23PAYKWB.js.map → chunk-KM27ZDHZ.js.map} +1 -1
- package/dist/{chunk-XZUMD3CB.mjs → chunk-LODDYDTP.mjs} +2 -2
- package/dist/{chunk-XZUMD3CB.mjs.map → chunk-LODDYDTP.mjs.map} +1 -1
- package/dist/{chunk-E6CGJBID.mjs → chunk-LUHKMY56.mjs} +2 -2
- package/dist/{chunk-TVLEUNKE.mjs → chunk-LZP5VZCO.mjs} +2 -2
- package/dist/{chunk-EHNEBXS7.mjs → chunk-M6DXZ2FM.mjs} +2 -2
- package/dist/{chunk-B4C5DWAP.mjs → chunk-MMMJDO75.mjs} +2 -2
- package/dist/{chunk-AFCWMJQO.js → chunk-MOUPNP53.js} +3 -3
- package/dist/{chunk-AFCWMJQO.js.map → chunk-MOUPNP53.js.map} +1 -1
- package/dist/{chunk-36KFIZ43.js → chunk-MYPUZ6EG.js} +3 -3
- package/dist/{chunk-36KFIZ43.js.map → chunk-MYPUZ6EG.js.map} +1 -1
- package/dist/{chunk-6JHIKIPA.js → chunk-NUGLTWEA.js} +23 -23
- package/dist/{chunk-6JHIKIPA.js.map → chunk-NUGLTWEA.js.map} +1 -1
- package/dist/{chunk-KWXOYHYK.mjs → chunk-P7MORXSE.mjs} +2 -2
- package/dist/{chunk-YG4IZ5Q3.mjs → chunk-Q2OSEROT.mjs} +2 -2
- package/dist/{chunk-L5SXG6T6.mjs → chunk-QQKOHKO2.mjs} +3 -3
- package/dist/{chunk-SADSJ2MV.js → chunk-QVGUYPXO.js} +3 -3
- package/dist/{chunk-SADSJ2MV.js.map → chunk-QVGUYPXO.js.map} +1 -1
- package/dist/{chunk-K45PTE7F.mjs → chunk-RBKQXT73.mjs} +4 -4
- package/dist/{chunk-JZFISWTP.js → chunk-RIN2IN7E.js} +2 -2
- package/dist/{chunk-JZFISWTP.js.map → chunk-RIN2IN7E.js.map} +1 -1
- package/dist/{chunk-BXLAOYAG.mjs → chunk-RIUVPTG7.mjs} +2 -2
- package/dist/{chunk-QVB54LBS.mjs → chunk-RNZGX6MX.mjs} +3 -3
- package/dist/{chunk-VSVLIRO2.js → chunk-RXEU77VN.js} +15 -15
- package/dist/{chunk-VSVLIRO2.js.map → chunk-RXEU77VN.js.map} +1 -1
- package/dist/{chunk-ZRIYCU2V.js → chunk-S2BZPPAI.js} +5 -5
- package/dist/{chunk-ZRIYCU2V.js.map → chunk-S2BZPPAI.js.map} +1 -1
- package/dist/{chunk-MES2BP4T.mjs → chunk-SGL5IUCV.mjs} +2 -2
- package/dist/{chunk-7LS5UQCQ.mjs → chunk-STKUPDHG.mjs} +2 -2
- package/dist/{chunk-PPYCCT2K.js → chunk-SWDVSCQJ.js} +9 -8
- package/dist/chunk-SWDVSCQJ.js.map +1 -0
- package/dist/{chunk-JCA76TCX.mjs → chunk-SZKLJOTN.mjs} +2 -2
- package/dist/{chunk-F5WR6XZZ.js → chunk-T73HPYF2.js} +3 -3
- package/dist/{chunk-F5WR6XZZ.js.map → chunk-T73HPYF2.js.map} +1 -1
- package/dist/{chunk-ZB6HDDE4.js → chunk-TQO37PVW.js} +14 -14
- package/dist/{chunk-ZB6HDDE4.js.map → chunk-TQO37PVW.js.map} +1 -1
- package/dist/{chunk-VKQ5AJDT.mjs → chunk-UENZQF2Y.mjs} +2 -2
- package/dist/{chunk-DCG7EUBC.js → chunk-UWLW5YVB.js} +6 -6
- package/dist/{chunk-DCG7EUBC.js.map → chunk-UWLW5YVB.js.map} +1 -1
- package/dist/{chunk-37AJ2GVO.mjs → chunk-UXJIZDXQ.mjs} +2 -2
- package/dist/{chunk-PLLPFCN3.js → chunk-UXMDPRAI.js} +14 -14
- package/dist/{chunk-PLLPFCN3.js.map → chunk-UXMDPRAI.js.map} +1 -1
- package/dist/{chunk-7BOTKRY6.mjs → chunk-V55DG5B4.mjs} +2 -2
- package/dist/{chunk-YRTIDZTN.js → chunk-VHEDMUAW.js} +4 -4
- package/dist/{chunk-YRTIDZTN.js.map → chunk-VHEDMUAW.js.map} +1 -1
- package/dist/{chunk-4RD55E4R.js → chunk-VWLWSO4J.js} +3 -3
- package/dist/{chunk-4RD55E4R.js.map → chunk-VWLWSO4J.js.map} +1 -1
- package/dist/{chunk-VJQIKFMG.mjs → chunk-W3CWEZDB.mjs} +3 -3
- package/dist/{chunk-JM6SLU7D.js → chunk-W7NNH4VY.js} +6 -6
- package/dist/{chunk-JM6SLU7D.js.map → chunk-W7NNH4VY.js.map} +1 -1
- package/dist/{chunk-VQ7MSYIH.js → chunk-WUN5NHZE.js} +4 -4
- package/dist/{chunk-VQ7MSYIH.js.map → chunk-WUN5NHZE.js.map} +1 -1
- package/dist/{chunk-AAU7ABJN.js → chunk-WWTRSQ3X.js} +3 -3
- package/dist/{chunk-AAU7ABJN.js.map → chunk-WWTRSQ3X.js.map} +1 -1
- package/dist/{chunk-BR6D2TGF.js → chunk-WZOA2PVJ.js} +3 -3
- package/dist/{chunk-BR6D2TGF.js.map → chunk-WZOA2PVJ.js.map} +1 -1
- package/dist/{chunk-TZKR5Z4J.mjs → chunk-XJF2F36N.mjs} +2 -2
- package/dist/{chunk-P6ACVNQK.mjs → chunk-YD2XBEKO.mjs} +2 -2
- package/dist/{chunk-LMXWIPGR.js → chunk-YWPLHFAK.js} +3 -3
- package/dist/{chunk-LMXWIPGR.js.map → chunk-YWPLHFAK.js.map} +1 -1
- package/dist/{chunk-DCEWWHV5.mjs → chunk-Z3HGYPH7.mjs} +2 -2
- package/dist/{chunk-Q3PKW6TH.js → chunk-ZCV3HWI2.js} +6 -6
- package/dist/{chunk-Q3PKW6TH.js.map → chunk-ZCV3HWI2.js.map} +1 -1
- package/dist/{chunk-OSA3YVDZ.mjs → chunk-ZF46DCAC.mjs} +2 -2
- package/dist/{chunk-5T6IDXSB.js → chunk-ZO2GA23I.js} +3 -3
- package/dist/{chunk-5T6IDXSB.js.map → chunk-ZO2GA23I.js.map} +1 -1
- package/dist/{chunk-ZN3763VZ.js → chunk-ZQLW4EJB.js} +9 -9
- package/dist/{chunk-ZN3763VZ.js.map → chunk-ZQLW4EJB.js.map} +1 -1
- package/dist/{chunk-CM5R4SPF.js → chunk-ZQQJTO6T.js} +3 -3
- package/dist/{chunk-CM5R4SPF.js.map → chunk-ZQQJTO6T.js.map} +1 -1
- package/dist/{chunk-SEL4QDCO.js → chunk-ZSXSAE3D.js} +3 -3
- package/dist/{chunk-SEL4QDCO.js.map → chunk-ZSXSAE3D.js.map} +1 -1
- package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts +6 -1
- package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts.map +1 -1
- package/dist/hooks/useAppContent/index.js +49 -49
- package/dist/hooks/useAppContent/index.mjs +48 -48
- package/dist/hooks/useSupportChat.d.ts +2 -0
- package/dist/hooks/useSupportChat.d.ts.map +1 -1
- package/dist/index.js +49 -49
- package/dist/index.mjs +48 -48
- package/dist/types/supportChat/index.d.ts +14 -0
- package/dist/types/supportChat/index.d.ts.map +1 -1
- package/dist/types/supportChat/index.js +2 -2
- package/dist/types/supportChat/index.mjs +1 -1
- package/dist/types/supportChat.d.ts +14 -0
- package/dist/types/supportChat.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-CYSODZGX.js.map +0 -1
- package/dist/chunk-OES54VU6.mjs.map +0 -1
- package/dist/chunk-PPYCCT2K.js.map +0 -1
- package/dist/chunk-SU5Q64TW.mjs.map +0 -1
- /package/dist/{RichEditorCore-6MTYSSKS.mjs.map → RichEditorCore-2S5YUAOI.mjs.map} +0 -0
- /package/dist/{chunk-B6QOBHZM.mjs.map → chunk-24BEZK43.mjs.map} +0 -0
- /package/dist/{chunk-OQTXTDSK.mjs.map → chunk-2OJ3QSLU.mjs.map} +0 -0
- /package/dist/{chunk-4OUBPEVS.mjs.map → chunk-3HKLGNUE.mjs.map} +0 -0
- /package/dist/{chunk-IHBWJM4C.mjs.map → chunk-3Q7RJKUP.mjs.map} +0 -0
- /package/dist/{chunk-PY6WD6SB.mjs.map → chunk-3WRB4WCD.mjs.map} +0 -0
- /package/dist/{chunk-L52NFNFK.mjs.map → chunk-5VC4BK65.mjs.map} +0 -0
- /package/dist/{chunk-C65DG2RI.mjs.map → chunk-7FSBWUOM.mjs.map} +0 -0
- /package/dist/{chunk-HCQJR5CE.mjs.map → chunk-7ZWEBNFB.mjs.map} +0 -0
- /package/dist/{chunk-3RHNJUXU.mjs.map → chunk-ACSEJOW6.mjs.map} +0 -0
- /package/dist/{chunk-WQZHUW7C.mjs.map → chunk-ANFHLSDB.mjs.map} +0 -0
- /package/dist/{chunk-H6NQOLQY.mjs.map → chunk-ANFLWCVD.mjs.map} +0 -0
- /package/dist/{chunk-5WS6IENQ.mjs.map → chunk-AU2HLQDJ.mjs.map} +0 -0
- /package/dist/{chunk-P6FX3NT6.mjs.map → chunk-AZGDHAP5.mjs.map} +0 -0
- /package/dist/{chunk-NPB2JTYC.mjs.map → chunk-BO3QVQ37.mjs.map} +0 -0
- /package/dist/{chunk-2WI4Q2I2.mjs.map → chunk-BPMWRFSI.mjs.map} +0 -0
- /package/dist/{chunk-TEW33GLR.mjs.map → chunk-BXN3VO6I.mjs.map} +0 -0
- /package/dist/{chunk-HBZHWSMZ.mjs.map → chunk-D46PJSQ7.mjs.map} +0 -0
- /package/dist/{chunk-I6EAPPBL.mjs.map → chunk-DZMYIOKZ.mjs.map} +0 -0
- /package/dist/{chunk-I4QGQMXI.mjs.map → chunk-EXYXZFNK.mjs.map} +0 -0
- /package/dist/{chunk-M4O46VDY.mjs.map → chunk-GX2PKLN2.mjs.map} +0 -0
- /package/dist/{chunk-5BUTINIM.mjs.map → chunk-HLVLSWKD.mjs.map} +0 -0
- /package/dist/{chunk-MMS2ELDK.mjs.map → chunk-IIQCS3A2.mjs.map} +0 -0
- /package/dist/{chunk-NKWLCZUZ.mjs.map → chunk-JGGUMHM6.mjs.map} +0 -0
- /package/dist/{chunk-NVL5TKWD.mjs.map → chunk-JYFLOQYT.mjs.map} +0 -0
- /package/dist/{chunk-DWGDEUEW.mjs.map → chunk-KG563P7F.mjs.map} +0 -0
- /package/dist/{chunk-TCIC5556.mjs.map → chunk-KH4TEDGD.mjs.map} +0 -0
- /package/dist/{chunk-E6CGJBID.mjs.map → chunk-LUHKMY56.mjs.map} +0 -0
- /package/dist/{chunk-TVLEUNKE.mjs.map → chunk-LZP5VZCO.mjs.map} +0 -0
- /package/dist/{chunk-EHNEBXS7.mjs.map → chunk-M6DXZ2FM.mjs.map} +0 -0
- /package/dist/{chunk-B4C5DWAP.mjs.map → chunk-MMMJDO75.mjs.map} +0 -0
- /package/dist/{chunk-KWXOYHYK.mjs.map → chunk-P7MORXSE.mjs.map} +0 -0
- /package/dist/{chunk-YG4IZ5Q3.mjs.map → chunk-Q2OSEROT.mjs.map} +0 -0
- /package/dist/{chunk-L5SXG6T6.mjs.map → chunk-QQKOHKO2.mjs.map} +0 -0
- /package/dist/{chunk-K45PTE7F.mjs.map → chunk-RBKQXT73.mjs.map} +0 -0
- /package/dist/{chunk-BXLAOYAG.mjs.map → chunk-RIUVPTG7.mjs.map} +0 -0
- /package/dist/{chunk-QVB54LBS.mjs.map → chunk-RNZGX6MX.mjs.map} +0 -0
- /package/dist/{chunk-MES2BP4T.mjs.map → chunk-SGL5IUCV.mjs.map} +0 -0
- /package/dist/{chunk-7LS5UQCQ.mjs.map → chunk-STKUPDHG.mjs.map} +0 -0
- /package/dist/{chunk-JCA76TCX.mjs.map → chunk-SZKLJOTN.mjs.map} +0 -0
- /package/dist/{chunk-VKQ5AJDT.mjs.map → chunk-UENZQF2Y.mjs.map} +0 -0
- /package/dist/{chunk-37AJ2GVO.mjs.map → chunk-UXJIZDXQ.mjs.map} +0 -0
- /package/dist/{chunk-7BOTKRY6.mjs.map → chunk-V55DG5B4.mjs.map} +0 -0
- /package/dist/{chunk-VJQIKFMG.mjs.map → chunk-W3CWEZDB.mjs.map} +0 -0
- /package/dist/{chunk-TZKR5Z4J.mjs.map → chunk-XJF2F36N.mjs.map} +0 -0
- /package/dist/{chunk-P6ACVNQK.mjs.map → chunk-YD2XBEKO.mjs.map} +0 -0
- /package/dist/{chunk-DCEWWHV5.mjs.map → chunk-Z3HGYPH7.mjs.map} +0 -0
- /package/dist/{chunk-OSA3YVDZ.mjs.map → chunk-ZF46DCAC.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-27Y6V3GM.js","../src/components/SupportChatWidget/SupportChatWidget.tsx","../src/hooks/useSupportChat.ts","../src/components/SupportChatWidget/SupportChatContactForm.tsx","../src/components/SupportChatWidget/SupportChatArticleView.tsx","../src/components/SupportChatWidget/supportChatContent.tsx","../src/components/SupportChatWidget/NativeSupportChat.tsx","../src/components/SupportChatWidget/guestSupportChatAdapter.ts","../src/components/SupportChatWidget/mockSupportChatAdapter.ts"],"names":["useState","jsxs","jsx","useCallback","useMemo"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-27Y6V3GM.js","sourcesContent":[null,"import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type KeyboardEvent,\n} from 'react';\nimport {\n ChatCircleDotsIcon,\n MinusIcon,\n PaperclipIcon,\n PaperPlaneTiltIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { useSupportChat } from '../../hooks/useSupportChat';\nimport { SupportChatContactForm } from './SupportChatContactForm';\nimport { SupportChatArticleView } from './SupportChatArticleView';\nimport {\n AttachmentList,\n MessageText,\n PendingAttachment,\n SUPPORT_CHAT_ACCEPT,\n pickAttachments,\n} from './supportChatContent';\nimport { SupportType } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAdapter,\n type SupportChatFlags,\n type SupportChatMessage,\n type SupportChatTheme,\n type SupportChatUser,\n} from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\n/** Visibility of the chat window */\nexport type SupportChatWindowState = 'closed' | 'open' | 'minimized';\n\nexport interface SupportChatWidgetProps {\n /** Data layer (createMockSupportChatAdapter for now) */\n adapter: SupportChatAdapter;\n /** App the widget runs in, sent with the ticket (e.g. \"login\", \"backoffice\") */\n channel: string;\n institutionId?: string;\n /** Logged user; omit for visitors */\n user?: SupportChatUser;\n flags?: SupportChatFlags;\n theme?: SupportChatTheme;\n onOpen?: () => void;\n onSend?: (message: SupportChatMessage) => void;\n /**\n * Called when the window is closed with X. Closing only hides it: the\n * conversation stays connected and new replies count as unread.\n */\n onClose?: () => void;\n /**\n * Link of a FAQ article, e.g. `(id) => '/artigo/' + id`. With it, the\n * \"Baseado na FAQ\" line under an AI answer opens the article; without it\n * the line only names the article.\n */\n faqArticleUrl?: (articleId: string) => string;\n}\n\n/** Label read by screen readers on the unread badge of the launcher */\nconst formatNewMessagesLabel = (count: number): string =>\n count === 1 ? '1 nova mensagem' : `${count} novas mensagens`;\n\n/** The requester side of the conversation (user or visitor) */\nconst isOwnMessage = (message: SupportChatMessage) =>\n message.senderRole === SUPPORT_CHAT_AUTHOR.USER ||\n message.senderRole === SUPPORT_CHAT_AUTHOR.GUEST;\n\nconst formatTime = (createdAt: string) =>\n new Date(createdAt).toLocaleTimeString('pt-BR', {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n/** \"Baseado na FAQ: <title>\" under an AI answer, linked when possible */\nconst FaqReference = ({\n faq,\n faqArticleUrl,\n onOpenArticle,\n}: {\n faq: { id: string; title: string };\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title: string }) => void;\n}) => {\n const linked = !!onOpenArticle || !!faqArticleUrl;\n const label = (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-primary-700\"\n className={cn(linked && 'underline')}\n >\n Baseado na FAQ: {faq.title}\n </Text>\n );\n let content = label;\n if (onOpenArticle) {\n // Opens inside the chat, like the Zendesk messenger (no new tab)\n content = (\n <a\n href={faqArticleUrl?.(faq.id) ?? '#'}\n onClick={(event) => {\n event.preventDefault();\n onOpenArticle(faq);\n }}\n >\n {label}\n </a>\n );\n } else if (faqArticleUrl) {\n content = (\n <a href={faqArticleUrl(faq.id)} target=\"_blank\" rel=\"noreferrer\">\n {label}\n </a>\n );\n }\n return (\n <div data-testid=\"support-chat-faq\" className=\"mt-1 max-w-[80%]\">\n {content}\n </div>\n );\n};\n\nconst MessageBubble = ({\n message,\n faqArticleUrl,\n onOpenArticle,\n}: {\n message: SupportChatMessage;\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title?: string }) => void;\n}) => {\n const own = isOwnMessage(message);\n return (\n <div\n className={cn(\n 'flex flex-col mb-3 transition-opacity',\n own ? 'items-end' : 'items-start',\n message.pending && 'opacity-60'\n )}\n aria-busy={message.pending || undefined}\n >\n {!own && (\n <Text size=\"xs\" className=\"text-text-500 mb-1\">\n {message.senderName}\n </Text>\n )}\n <div\n className={cn(\n 'max-w-[80%] rounded-2xl whitespace-pre-wrap break-words',\n // Image-only messages show the image without the colored bubble\n message.content\n ? cn(\n 'px-4 py-2',\n own\n ? 'bg-primary-500 text-white rounded-br-md'\n : 'bg-background-100 text-text-900 rounded-bl-md'\n )\n : 'text-text-900'\n )}\n >\n {message.content && (\n <MessageText\n text={message.content}\n onOpenArticle={\n onOpenArticle ? (id) => onOpenArticle({ id }) : undefined\n }\n />\n )}\n {message.attachments.length > 0 && (\n <AttachmentList items={message.attachments} />\n )}\n </div>\n {message.faq && (\n <FaqReference\n faq={message.faq}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={onOpenArticle}\n />\n )}\n <Text size=\"xs\" className=\"text-text-400 mt-1\">\n {message.pending ? 'Enviando…' : formatTime(message.createdAt)}\n </Text>\n </div>\n );\n};\n\n/**\n * Floating support chat (native support, the Zendesk replacement).\n * Renders nothing when disabled or when the institution uses Zendesk.\n */\nexport const SupportChatWidget = ({\n flags,\n ...props\n}: SupportChatWidgetProps) => {\n const hidden =\n flags?.enabled === false ||\n flags?.chat === false ||\n flags?.supportType === SupportType.ZENDESK;\n\n if (hidden) return null;\n return <SupportChatWidgetContent {...props} />;\n};\n\nconst SupportChatWidgetContent = ({\n adapter,\n channel,\n institutionId,\n user,\n theme,\n onOpen,\n onSend,\n onClose,\n faqArticleUrl,\n}: Omit<SupportChatWidgetProps, 'flags'>) => {\n const [windowState, setWindowState] =\n useState<SupportChatWindowState>('closed');\n const [draft, setDraft] = useState('');\n const [files, setFiles] = useState<File[]>([]);\n const [fileError, setFileError] = useState<string | null>(null);\n const [unread, setUnread] = useState(0);\n // FAQ article open inside the chat (the conversation stays as it was)\n const [article, setArticle] = useState<{ id: string; title?: string } | null>(\n null\n );\n const [expanded, setExpanded] = useState(false);\n const listRef = useRef<HTMLDivElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const seenCount = useRef(0);\n\n const {\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact,\n reset,\n } = useSupportChat({\n adapter,\n channel,\n institutionId,\n user,\n });\n const [savedContactRequests, setSavedContactRequests] = useState<string[]>(\n []\n );\n\n // Latest AI request for name/e-mail: the form goes under it, and the\n // composer waits until it is sent\n const contactRequest = updateContact\n ? [...messages].reverse().find((message) => message.contactRequest?.length)\n : undefined;\n const awaitingContact =\n !!contactRequest && !savedContactRequests.includes(contactRequest.id);\n\n const isOpen = windowState === 'open';\n\n const canOpenArticle = !!adapter.getArticle && !!institutionId;\n const loadArticle = useCallback(\n (articleId: string) => {\n if (!adapter.getArticle || !institutionId) {\n return Promise.reject(new Error('Artigo indisponível'));\n }\n // Called on the adapter so a class-based adapter keeps its `this`\n return adapter.getArticle(institutionId, articleId);\n },\n [adapter, institutionId]\n );\n const openArticle = canOpenArticle\n ? (target: { id: string; title?: string }) => {\n setArticle(target);\n setExpanded(true);\n }\n : undefined;\n const closeArticle = () => {\n setArticle(null);\n setExpanded(false);\n };\n const title = theme?.title ?? 'Suporte';\n const left = theme?.position === 'bottom-left';\n const accentStyle = theme?.primaryColor\n ? { backgroundColor: theme.primaryColor }\n : undefined;\n\n // Count the support replies that arrive while the window is not visible\n useEffect(() => {\n const fresh = messages.slice(seenCount.current);\n seenCount.current = messages.length;\n // A resumed conversation was already read before the reload\n if (isOpen || (restored && fresh.length === messages.length)) return;\n const replies = fresh.filter((message) => !isOwnMessage(message)).length;\n if (replies) setUnread((count) => count + replies);\n }, [messages, isOpen, restored]);\n\n useEffect(() => {\n if (!isOpen || article) return;\n setUnread(0);\n listRef.current?.scrollTo?.({ top: listRef.current.scrollHeight });\n }, [isOpen, messages, article]);\n\n const open = () => {\n setWindowState('open');\n onOpen?.();\n };\n\n const close = () => {\n setWindowState('closed');\n onClose?.();\n };\n\n /** Drop a finished conversation so the next message opens a new ticket */\n const startOver = () => {\n setDraft('');\n setFiles([]);\n setFileError(null);\n setUnread(0);\n seenCount.current = 0;\n setSavedContactRequests([]);\n reset();\n };\n\n const submit = async () => {\n const content = draft;\n const pending = files;\n if (!content.trim() && pending.length === 0) return;\n setDraft('');\n setFiles([]);\n setFileError(null);\n const message = await send(content, pending);\n if (message) {\n onSend?.(message);\n } else {\n setDraft(content);\n setFiles(pending);\n }\n };\n\n const handleFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const { accepted, problem } = pickAttachments(\n files,\n Array.from(event.target.files ?? [])\n );\n setFiles(accepted);\n setFileError(problem);\n event.target.value = '';\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n void submit();\n }\n };\n\n return (\n <div\n className={cn(\n 'fixed bottom-4 z-50 flex flex-col gap-3',\n left ? 'left-4 items-start' : 'right-4 items-end'\n )}\n >\n {isOpen && (\n <section\n role=\"dialog\"\n aria-label={title}\n className={cn(\n 'max-w-[calc(100vw-2rem)] max-h-[calc(100vh-6rem)] flex flex-col bg-background rounded-2xl shadow-lg border border-border-100 overflow-hidden transition-[width,height]',\n expanded ? 'w-[720px] h-[615px]' : 'w-[360px] h-[520px]'\n )}\n >\n {article ? (\n <SupportChatArticleView\n articleId={article.id}\n initialTitle={article.title}\n loadArticle={loadArticle}\n expanded={expanded}\n onToggleExpanded={() => setExpanded((value) => !value)}\n onBack={closeArticle}\n externalUrl={faqArticleUrl?.(article.id)}\n accentStyle={accentStyle}\n />\n ) : (\n <>\n <header\n className=\"flex items-center justify-between px-4 py-3 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Text size=\"md\" weight=\"semibold\" color=\"\">\n {title}\n </Text>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"raw\"\n aria-label=\"Minimizar\"\n onClick={() => setWindowState('minimized')}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <MinusIcon size={18} />\n </Button>\n <Button\n variant=\"raw\"\n aria-label=\"Fechar\"\n onClick={close}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <XIcon size={18} />\n </Button>\n </div>\n </header>\n\n <div ref={listRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {messages.length === 0 ? (\n <Text size=\"sm\" className=\"text-text-500 text-center mt-8\">\n Olá! Como podemos te ajudar?\n </Text>\n ) : (\n messages.map((message) => (\n <div key={message.id}>\n <MessageBubble\n message={message}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={openArticle}\n />\n {updateContact &&\n message.id === contactRequest?.id &&\n message.contactRequest && (\n <SupportChatContactForm\n fields={message.contactRequest}\n accentColor={theme?.primaryColor}\n onSubmit={async (contact) => {\n const ok = await updateContact(contact);\n if (ok) {\n setSavedContactRequests((ids) => [\n ...ids,\n message.id,\n ]);\n }\n return ok;\n }}\n />\n )}\n </div>\n ))\n )}\n </div>\n\n {(fileError || error) && (\n <Text\n size=\"xs\"\n role=\"alert\"\n className=\"px-4 py-2 text-error-700 bg-error-50\"\n >\n {fileError ?? error}\n </Text>\n )}\n\n {closed ? (\n <div className=\"flex flex-col items-center gap-2 px-4 py-3 border-t border-border-100\">\n <Text size=\"sm\" color=\"text-text-500\" className=\"text-center\">\n Este atendimento foi encerrado.\n </Text>\n <Button variant=\"link\" size=\"small\" onClick={startOver}>\n Iniciar nova conversa\n </Button>\n </div>\n ) : (\n <form\n className=\"flex flex-col gap-2 px-3 py-3 border-t border-border-100\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((file, index) => (\n <PendingAttachment\n key={`${file.name}-${index}`}\n file={file}\n onRemove={() =>\n setFiles((list) =>\n list.filter((_, i) => i !== index)\n )\n }\n />\n ))}\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={SUPPORT_CHAT_ACCEPT}\n multiple\n hidden\n data-testid=\"support-chat-file-input\"\n onChange={handleFiles}\n />\n <Button\n variant=\"raw\"\n aria-label=\"Anexar arquivo\"\n disabled={\n sending ||\n awaitingContact ||\n files.length >= SUPPORT_CHAT_MAX_ATTACHMENTS\n }\n onClick={() => fileInputRef.current?.click()}\n className=\"p-2 rounded-full text-text-600 hover:bg-background-100 disabled:opacity-50\"\n >\n <PaperclipIcon size={20} />\n </Button>\n <textarea\n aria-label=\"Mensagem\"\n placeholder={\n awaitingContact\n ? 'Preencha seus dados acima'\n : 'Digite sua mensagem'\n }\n disabled={awaitingContact}\n rows={1}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 resize-none rounded-xl border border-border-300 px-3 py-2 text-sm text-text-900 focus:outline-none focus:border-primary-500 max-h-28\"\n />\n <Button\n variant=\"raw\"\n type=\"submit\"\n aria-label=\"Enviar\"\n disabled={\n sending ||\n awaitingContact ||\n (!draft.trim() && files.length === 0)\n }\n className=\"p-2 rounded-full bg-primary-500 text-white disabled:opacity-50\"\n style={accentStyle}\n >\n <PaperPlaneTiltIcon size={18} />\n </Button>\n </div>\n </form>\n )}\n </>\n )}\n </section>\n )}\n\n {!isOpen && (\n <Button\n variant=\"raw\"\n aria-label={`Abrir ${title.toLowerCase()}`}\n onClick={open}\n className=\"relative w-14 h-14 rounded-full bg-primary-500 text-white shadow-lg flex items-center justify-center hover:brightness-110\"\n style={accentStyle}\n >\n <ChatCircleDotsIcon size={28} />\n {unread > 0 && (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-white\"\n aria-label={formatNewMessagesLabel(unread)}\n className=\"absolute -top-1 -right-1 min-w-5 h-5 px-1 rounded-full bg-error-500 flex items-center justify-center\"\n >\n {unread}\n </Text>\n )}\n </Button>\n )}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatContact,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n type SupportChatUser,\n} from '../types/supportChat';\n\nexport interface UseSupportChatConfig {\n adapter: SupportChatAdapter;\n channel: string;\n institutionId?: string;\n user?: SupportChatUser;\n}\n\nexport interface UseSupportChatReturn {\n ticket: SupportChatTicket | null;\n messages: SupportChatMessage[];\n sending: boolean;\n error: string | null;\n /** Ticket resolved: no more messages can be sent */\n closed: boolean;\n /** The conversation was resumed from an earlier page load */\n restored: boolean;\n /** Send a message (text and/or files); the first one opens the ticket */\n send: (content: string, files?: File[]) => Promise<SupportChatMessage | null>;\n /**\n * Save the contact typed in the form shown under the AI request. Resolves\n * false (and sets `error`) when it fails. Undefined when the adapter cannot\n * save it.\n */\n updateContact?: (contact: SupportChatContact) => Promise<boolean>;\n /** Forget the current conversation */\n reset: () => void;\n}\n\nlet pendingSequence = 0;\n\n/**\n * Message shown right away while it is being sent (optimistic rendering).\n * Images use local object URLs, released when the real message arrives.\n */\nconst buildPendingMessage = (\n content: string,\n files: File[],\n user?: SupportChatUser\n): SupportChatMessage => {\n pendingSequence += 1;\n return {\n id: `pending-${pendingSequence}`,\n senderId: SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: user?.name || 'Você',\n senderPhoto: null,\n senderRole: user?.email\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: files.map((file, index) => ({\n id: `pending-${pendingSequence}-${index}`,\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n })),\n pending: true,\n };\n};\n\nconst releasePending = (message: SupportChatMessage) =>\n message.attachments.forEach((item) => URL.revokeObjectURL(item.url));\n\nconst getErrorMessage = (error: unknown) =>\n error instanceof Error ? error.message : 'Não foi possível enviar a mensagem';\n\n/** Add or replace a message, keeping the list ordered by arrival */\nconst upsertMessage = (\n list: SupportChatMessage[],\n message: SupportChatMessage\n): SupportChatMessage[] =>\n list.some((item) => item.id === message.id)\n ? list.map((item) => (item.id === message.id ? message : item))\n : [...list, message];\n\n/**\n * State of a support conversation on top of a SupportChatAdapter.\n * The REST-like calls write; the subscription only applies the events,\n * so a message received both ways is stored once.\n */\nexport const useSupportChat = ({\n adapter,\n channel,\n institutionId,\n user,\n}: UseSupportChatConfig): UseSupportChatReturn => {\n const [ticket, setTicket] = useState<SupportChatTicket | null>(null);\n const [messages, setMessages] = useState<SupportChatMessage[]>([]);\n const [sending, setSending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [restored, setRestored] = useState(false);\n const sendingRef = useRef(false);\n\n const ticketId = ticket?.id;\n const userEmail = user?.email;\n // Read by the restore, which must not replace a conversation started meanwhile\n const ticketRef = useRef(ticket);\n ticketRef.current = ticket;\n\n // Resume the unresolved conversation of an earlier page load, unless the\n // person already started a new one meanwhile\n useEffect(() => {\n if (!adapter.restoreConversation) return;\n let active = true;\n adapter\n .restoreConversation({ email: userEmail })\n .then((resumed) => {\n if (!active || !resumed) return;\n if (ticketRef.current || sendingRef.current) return;\n setTicket(resumed.ticket);\n setMessages(resumed.messages);\n setRestored(true);\n })\n .catch(() => {\n // Not resumed: the next message opens a new ticket\n });\n return () => {\n active = false;\n };\n }, [adapter, userEmail]);\n\n const closed = ticket?.status === SUPPORT_CHAT_STATUS.FECHADO;\n\n // A resolved conversation is not resumed on the next load\n useEffect(() => {\n if (closed) adapter.forgetConversation?.();\n }, [adapter, closed]);\n\n useEffect(() => {\n if (!ticketId) return;\n\n const handleEvent = (event: SupportChatEvent) => {\n switch (event.type) {\n case 'new_message':\n setMessages((list) => upsertMessage(list, event.payload.message));\n break;\n case 'message_updated':\n setMessages((list) =>\n list.map((item) =>\n item.id === event.payload.messageId\n ? { ...item, content: event.payload.content }\n : item\n )\n );\n break;\n case 'ticket_updated': {\n const { ticketId: id, ...rest } = event.payload;\n setTicket((current) =>\n current?.id === id ? { ...current, ...rest } : current\n );\n break;\n }\n case 'error':\n setError(event.payload.message);\n break;\n }\n };\n\n return adapter.subscribe(ticketId, handleEvent);\n }, [adapter, ticketId]);\n\n const send = useCallback(\n async (content: string, files: File[] = []) => {\n const text = content.trim();\n if ((!text && files.length === 0) || sendingRef.current) return null;\n\n sendingRef.current = true;\n setSending(true);\n setError(null);\n\n const pending = buildPendingMessage(text, files, user);\n setMessages((list) => [...list, pending]);\n // Swap the optimistic message for the confirmed ones\n const confirm = (confirmed: SupportChatMessage[]) =>\n setMessages((list) =>\n confirmed.reduce(\n upsertMessage,\n list.filter((item) => item.id !== pending.id)\n )\n );\n\n try {\n if (ticketId) {\n const message = await adapter.sendMessage(ticketId, text, files);\n confirm([message]);\n return message;\n }\n\n const started = await adapter.startConversation({\n message: text,\n channel,\n institutionId,\n requesterName: user?.name,\n email: user?.email,\n files,\n });\n setTicket(started.ticket);\n confirm(started.messages);\n return started.messages[started.messages.length - 1] ?? null;\n } catch (sendError) {\n setMessages((list) => list.filter((item) => item.id !== pending.id));\n setError(getErrorMessage(sendError));\n return null;\n } finally {\n releasePending(pending);\n sendingRef.current = false;\n setSending(false);\n }\n },\n [adapter, channel, institutionId, ticketId, user]\n );\n\n const updateContact = useCallback(\n async (contact: SupportChatContact) => {\n if (!ticketId || !adapter.updateContact) return false;\n setError(null);\n try {\n await adapter.updateContact(ticketId, contact);\n return true;\n } catch (contactError) {\n setError(\n (contactError as { response?: { status?: number } } | null)?.response\n ?.status === 409\n ? 'O e-mail deste atendimento já foi informado'\n : 'Não foi possível salvar seus dados. Tente de novo.'\n );\n return false;\n }\n },\n [adapter, ticketId]\n );\n\n const reset = useCallback(() => {\n adapter.forgetConversation?.();\n setTicket(null);\n setMessages([]);\n setError(null);\n setRestored(false);\n }, [adapter]);\n\n return {\n ticket,\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact: adapter.updateContact ? updateContact : undefined,\n reset,\n };\n};\n","import { useState, type FormEvent } from 'react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport type {\n SupportChatContact,\n SupportChatContactField,\n} from '../../types/supportChat';\n\nexport interface SupportChatContactFormProps {\n /** Fields the AI asked for, in order (one step each) */\n fields: SupportChatContactField[];\n /** Saves the contact; resolves true when it worked */\n onSubmit: (contact: SupportChatContact) => Promise<boolean>;\n /** Accent color of the theme, for the button */\n accentColor?: string;\n}\n\nconst STEPS: Record<\n SupportChatContactField,\n { label: string; type: string; autoComplete: string; placeholder: string }\n> = {\n name: {\n label: 'Seu nome',\n type: 'text',\n autoComplete: 'name',\n placeholder: 'Como podemos te chamar?',\n },\n email: {\n label: 'Seu e-mail',\n type: 'email',\n autoComplete: 'email',\n placeholder: 'nome@exemplo.com',\n },\n};\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Problem with a typed value, or null when it can be sent */\nconst validate = (\n field: SupportChatContactField,\n value: string\n): string | null => {\n if (field === 'name') {\n return value.length >= 2 ? null : 'Digite seu nome';\n }\n return EMAIL_PATTERN.test(value) ? null : 'Digite um e-mail válido';\n};\n\n/**\n * Step-by-step form for the contact the AI asks on the first contact (name,\n * then e-mail), shown under the AI message like the Zendesk pre-chat form.\n * The values go to the backend as fields, not as a chat message. Once sent,\n * it shows what was saved.\n */\nexport const SupportChatContactForm = ({\n fields,\n onSubmit,\n accentColor,\n}: SupportChatContactFormProps) => {\n const [step, setStep] = useState(0);\n const [values, setValues] = useState<Record<string, string>>({});\n const [problem, setProblem] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n const [saved, setSaved] = useState(false);\n\n const field = fields[step];\n const config = STEPS[field];\n const value = values[field] ?? '';\n const isLast = step === fields.length - 1;\n\n if (saved) {\n return (\n <div\n data-testid=\"support-chat-contact-saved\"\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-1 rounded-2xl border border-border-100 px-4 py-3\"\n >\n {fields.map((item) => (\n <Text key={item} size=\"xs\" color=\"text-text-600\">\n {STEPS[item].label}:{' '}\n <Text as=\"span\" size=\"xs\" weight=\"semibold\" color=\"text-text-900\">\n {values[item]}\n </Text>\n </Text>\n ))}\n </div>\n );\n }\n\n const advance = async (event: FormEvent) => {\n event.preventDefault();\n const typed = field === 'email' ? value.trim().toLowerCase() : value.trim();\n const error = validate(field, typed);\n setProblem(error);\n if (error) return;\n\n const next = { ...values, [field]: typed };\n setValues(next);\n if (!isLast) {\n setStep(step + 1);\n return;\n }\n\n setSaving(true);\n const ok = await onSubmit({\n ...(next.name ? { requesterName: next.name } : {}),\n ...(next.email ? { email: next.email } : {}),\n });\n setSaving(false);\n if (ok) setSaved(true);\n };\n\n const inputId = `support-chat-contact-${field}`;\n\n return (\n <form\n aria-label=\"Seus dados\"\n noValidate\n onSubmit={(event) => void advance(event)}\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-3 rounded-2xl border border-border-100 px-4 py-3\"\n >\n <Input\n id={inputId}\n key={field}\n label={config.label}\n variant=\"rounded\"\n size=\"small\"\n type={config.type}\n autoComplete={config.autoComplete}\n placeholder={config.placeholder}\n value={value}\n autoFocus\n disabled={saving}\n errorMessage={problem ?? undefined}\n onChange={(event) =>\n setValues((current) => ({ ...current, [field]: event.target.value }))\n }\n />\n <div className=\"flex items-center justify-between\">\n <Text size=\"xs\" color=\"text-text-500\">\n {fields.length > 1 ? `${step + 1} de ${fields.length}` : ''}\n </Text>\n <Button\n type=\"submit\"\n size=\"small\"\n disabled={saving}\n style={accentColor ? { backgroundColor: accentColor } : undefined}\n >\n {isLast ? 'Enviar' : 'Avançar'}\n </Button>\n </div>\n </form>\n );\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n ArrowLeftIcon,\n ArrowSquareOutIcon,\n ArrowsInSimpleIcon,\n ArrowsOutSimpleIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { ArticleContent } from '../ArticleContent/ArticleContent';\nimport { embedVideoLinks } from '../../utils/videoEmbed';\nimport type { SupportChatArticle } from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\nexport interface SupportChatArticleViewProps {\n /** Article to show */\n articleId: string;\n /** Title known from the chat, shown while the article loads */\n initialTitle?: string;\n /** Loads the article (adapter.getArticle bound to the institution) */\n loadArticle: (articleId: string) => Promise<SupportChatArticle>;\n /** Whether the chat window is in the wide mode */\n expanded: boolean;\n onToggleExpanded: () => void;\n /** Back to the conversation (it is kept as it was) */\n onBack: () => void;\n /** Page of the article in the help center, for \"Abrir em nova aba\" */\n externalUrl?: string;\n /** Header background (theme) */\n accentStyle?: { backgroundColor: string };\n}\n\nconst formatUpdatedAt = (iso: string) =>\n new Date(iso).toLocaleDateString('pt-BR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n });\n\n/**\n * FAQ article read inside the support chat, like the Zendesk messenger: the\n * conversation is replaced by the article (no new tab), with back,\n * expand/reduce and open-in-new-tab controls. The big title moves to the\n * header once it scrolls out of view. YouTube/Vimeo links of the article\n * become players.\n */\nexport const SupportChatArticleView = ({\n articleId,\n initialTitle,\n loadArticle,\n expanded,\n onToggleExpanded,\n onBack,\n externalUrl,\n accentStyle,\n}: SupportChatArticleViewProps) => {\n const [article, setArticle] = useState<SupportChatArticle | null>(null);\n const [failed, setFailed] = useState(false);\n const [titleHidden, setTitleHidden] = useState(false);\n const titleRef = useRef<HTMLHeadingElement>(null);\n\n useEffect(() => {\n let active = true;\n setArticle(null);\n setFailed(false);\n loadArticle(articleId)\n .then((loaded) => {\n if (active) setArticle(loaded);\n })\n .catch(() => {\n if (active) setFailed(true);\n });\n return () => {\n active = false;\n };\n }, [articleId, loadArticle]);\n\n // The big title leaves the screen → show it in the header\n useEffect(() => {\n const title = titleRef.current;\n if (!title || typeof IntersectionObserver === 'undefined') {\n return undefined;\n }\n const observer = new IntersectionObserver(([entry]) =>\n setTitleHidden(!entry.isIntersecting)\n );\n observer.observe(title);\n return () => observer.disconnect();\n }, [article]);\n\n const html = useMemo(\n () => (article ? embedVideoLinks(article.body) : ''),\n [article]\n );\n const title = article?.title ?? initialTitle ?? 'Artigo';\n\n return (\n <div\n data-testid=\"support-chat-article\"\n className=\"flex min-h-0 flex-1 flex-col\"\n >\n <header\n className=\"flex items-center gap-1 px-2 py-2 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Button\n variant=\"raw\"\n aria-label=\"Voltar para a janela de mensagens\"\n onClick={onBack}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowLeftIcon size={18} />\n </Button>\n <Text\n as=\"span\"\n size=\"sm\"\n weight=\"semibold\"\n color=\"\"\n aria-hidden={!titleHidden || undefined}\n className={cn(\n 'min-w-0 flex-1 truncate transition-opacity',\n titleHidden ? 'opacity-100' : 'opacity-0'\n )}\n >\n {title}\n </Text>\n <Button\n variant=\"raw\"\n aria-label={expanded ? 'Reduzir artigo' : 'Expandir artigo'}\n onClick={onToggleExpanded}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n {expanded ? (\n <ArrowsInSimpleIcon size={18} />\n ) : (\n <ArrowsOutSimpleIcon size={18} />\n )}\n </Button>\n {externalUrl && (\n <a\n href={externalUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label=\"Abrir em nova aba\"\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowSquareOutIcon size={18} />\n </a>\n )}\n </header>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Text\n as=\"h2\"\n ref={titleRef}\n size=\"xl\"\n weight=\"bold\"\n color=\"text-text-950\"\n >\n {title}\n </Text>\n {article?.updatedAt && (\n <Text size=\"xs\" color=\"text-text-500\" className=\"mt-1\">\n Atualizado em {formatUpdatedAt(article.updatedAt)}\n </Text>\n )}\n\n <div className=\"mt-4\">\n {failed && (\n <Text size=\"sm\" role=\"alert\" color=\"text-error-700\">\n Não foi possível abrir o artigo.\n </Text>\n )}\n {!failed && !article && (\n <Text size=\"sm\" color=\"text-text-500\">\n Carregando…\n </Text>\n )}\n {article && <ArticleContent html={html} />}\n </div>\n </div>\n </div>\n );\n};\n","import { useEffect, useState, type ReactNode } from 'react';\nimport {\n FileIcon,\n FilePdfIcon,\n FileVideoIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport {\n SUPPORT_CHAT_ATTACHMENT_RULES,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAttachment,\n} from '../../types/supportChat';\n\n/** Value of the `accept` attribute of the file picker */\nexport const SUPPORT_CHAT_ACCEPT = Object.keys(\n SUPPORT_CHAT_ATTACHMENT_RULES\n).join(',');\n\nconst isImage = (mimeType?: string) => Boolean(mimeType?.startsWith('image/'));\nconst isVideo = (mimeType?: string) => Boolean(mimeType?.startsWith('video/'));\n\nconst formatSize = (bytes: number) => `${Math.round(bytes / (1024 * 1024))} MB`;\n\n/**\n * Keep the files the backend accepts (type, size and count) and say why the\n * others were left out.\n * @param current - Files already picked\n * @param picked - Files just chosen\n * @returns The accepted files and the last problem found, if any\n */\nexport const pickAttachments = (current: File[], picked: File[]) => {\n const accepted = [...current];\n let problem: string | null = null;\n for (const file of picked) {\n const maxBytes = SUPPORT_CHAT_ATTACHMENT_RULES[file.type];\n if (!maxBytes) {\n problem =\n 'Envie imagens (JPG, PNG, GIF, WEBP), PDF ou vídeo (MP4, WEBM, MOV)';\n } else if (file.size > maxBytes) {\n problem = `${file.name} passa de ${formatSize(maxBytes)}`;\n } else if (accepted.length >= SUPPORT_CHAT_MAX_ATTACHMENTS) {\n problem = `Máximo de ${SUPPORT_CHAT_MAX_ATTACHMENTS} arquivos por mensagem`;\n } else {\n accepted.push(file);\n }\n }\n return { accepted, problem };\n};\n\nconst FileTypeIcon = ({ mimeType }: { mimeType?: string }) => {\n if (mimeType === 'application/pdf') return <FilePdfIcon size={18} />;\n if (isVideo(mimeType)) return <FileVideoIcon size={18} />;\n return <FileIcon size={18} />;\n};\n\n/** Files of a message: images and videos inline, other files as links */\nexport const AttachmentList = ({\n items,\n}: {\n items: SupportChatAttachment[];\n}) => (\n <div className=\"flex flex-wrap gap-2 first:mt-0 mt-1\">\n {items.map((item) => {\n if (isImage(item.mimeType)) {\n return (\n <a key={item.id} href={item.url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={item.url}\n alt={item.fileName}\n className=\"max-w-[200px] max-h-[200px] rounded-lg object-cover\"\n />\n </a>\n );\n }\n if (isVideo(item.mimeType)) {\n return (\n <video\n key={item.id}\n src={item.url}\n controls\n preload=\"metadata\"\n aria-label={item.fileName}\n className=\"max-w-[240px] max-h-[200px] rounded-lg bg-black\"\n />\n );\n }\n return (\n <a\n key={item.id}\n href={item.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"flex items-center gap-1 underline\"\n >\n <FileTypeIcon mimeType={item.mimeType} />\n <Text as=\"span\" size=\"sm\" color=\"\">\n {item.fileName}\n </Text>\n </a>\n );\n })}\n </div>\n);\n\n/** A file picked but not sent yet: thumbnail for images, name for the rest */\nexport const PendingAttachment = ({\n file,\n onRemove,\n}: {\n file: File;\n onRemove: () => void;\n}) => {\n const image = isImage(file.type);\n const [url, setUrl] = useState('');\n useEffect(() => {\n if (!image) return undefined;\n const objectUrl = URL.createObjectURL(file);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [file, image]);\n\n return (\n <div className=\"relative\">\n {image ? (\n url && (\n <img\n src={url}\n alt={file.name}\n className=\"w-14 h-14 rounded-lg object-cover border border-border-100\"\n />\n )\n ) : (\n <div className=\"flex h-14 max-w-[160px] items-center gap-1 rounded-lg border border-border-100 px-2 text-text-700\">\n <FileTypeIcon mimeType={file.type} />\n <Text as=\"span\" size=\"xs\" className=\"truncate\">\n {file.name}\n </Text>\n </div>\n )}\n <Button\n variant=\"raw\"\n aria-label={`Remover ${file.name}`}\n onClick={onRemove}\n className=\"absolute -top-1 -right-1 w-5 h-5 rounded-full bg-text-700 text-white flex items-center justify-center\"\n >\n <XIcon size={12} />\n </Button>\n </div>\n );\n};\n\n/** http(s) addresses inside a message */\nconst URL_PATTERN = /https?:\\/\\/[^\\s<>\"]+/g;\n\n/** Punctuation that ends a sentence, not the address */\nconst TRAILING_PUNCTUATION = /[.,;:!?)\\]]+$/;\n\n/** A FAQ article page: `/artigo/<uuid>` */\nconst ARTICLE_PATH =\n /\\/artigo\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;\n\n/**\n * Message text with its addresses as links. A link to a FAQ article opens in\n * the chat (when `onOpenArticle` is given); the others open a new tab.\n */\nexport const MessageText = ({\n text,\n onOpenArticle,\n}: {\n text: string;\n onOpenArticle?: (articleId: string) => void;\n}) => {\n const parts: ReactNode[] = [];\n let cursor = 0;\n for (const match of text.matchAll(URL_PATTERN)) {\n const raw = match[0];\n const url = raw.replace(TRAILING_PUNCTUATION, '');\n const start = match.index ?? 0;\n if (start > cursor) parts.push(text.slice(cursor, start));\n\n const articleId = onOpenArticle ? ARTICLE_PATH.exec(url)?.[1] : undefined;\n parts.push(\n <a\n key={`${start}-${url}`}\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"underline break-all\"\n onClick={\n articleId\n ? (event) => {\n event.preventDefault();\n onOpenArticle?.(articleId);\n }\n : undefined\n }\n >\n {url}\n </a>\n );\n cursor = start + url.length;\n }\n if (cursor < text.length) parts.push(text.slice(cursor));\n\n return (\n <Text size=\"sm\" color=\"\" className=\"whitespace-pre-wrap\">\n {parts}\n </Text>\n );\n};\n","import { useMemo } from 'react';\nimport { SupportChatWidget } from './SupportChatWidget';\nimport { createGuestSupportChatAdapter } from './guestSupportChatAdapter';\nimport { useSupportFeatureFlag } from '../../hooks/useSupportFeatureFlag';\nimport { useAppStore } from '../../store/appStore';\nimport { useAuthStore } from '../../store/authStore';\nimport { SupportType, type SupportApiClient } from '../../types/support';\nimport type {\n SupportChatHttpClient,\n SupportChatTheme,\n} from '../../types/supportChat';\n\nexport interface NativeSupportChatProps {\n /** Client of the backend API (the app axios instance fits as it is) */\n apiClient: SupportApiClient & SupportChatHttpClient;\n /** App the chat runs in, sent with the ticket (e.g. \"aluno\", \"login\") */\n channel: string;\n /**\n * Extra condition of the app to show the chat, on top of the SUPPORT flag\n * (e.g. logged in and with the support module). Default: true.\n */\n enabled?: boolean;\n /**\n * Link of a FAQ article. Default: the FAQ address of the SUPPORT flag\n * (`<faqUrl>/artigo/<id>`).\n */\n faqArticleUrl?: (articleId: string) => string;\n /** Support WebSocket; without it the chat polls the REST API */\n wsUrl?: string;\n theme?: SupportChatTheme;\n}\n\n/**\n * Native support chat ready to drop in an app: reads the SUPPORT feature flag\n * of the institution (from the app store), the logged user (from the auth\n * store) and talks to the support API by itself.\n *\n * Shown only when the institution uses the native support with the internal\n * support and its chat switched on in the backoffice, once the flag is read,\n * and when `enabled`. With Zendesk it renders nothing (the app loads the\n * Zendesk widget). A logged user opens the ticket with their name and e-mail;\n * a visitor gives them to the AI on the first contact.\n *\n * @example\n * <NativeSupportChat apiClient={api} channel=\"aluno\" enabled={hasSupport} />\n */\nexport const NativeSupportChat = ({\n apiClient,\n channel,\n enabled = true,\n faqArticleUrl,\n wsUrl,\n theme,\n}: NativeSupportChatProps) => {\n const { institutionId } = useAppStore();\n const userName = useAuthStore(\n (state) => state.user?.name ?? state.sessionInfo?.userName\n );\n const userEmail = useAuthStore((state) => state.user?.email);\n const flag = useSupportFeatureFlag({ apiClient });\n const adapter = useMemo(\n () => createGuestSupportChatAdapter({ apiClient, wsUrl }),\n [apiClient, wsUrl]\n );\n\n const user =\n userName || userEmail ? { name: userName, email: userEmail } : undefined;\n\n return (\n <SupportChatWidget\n adapter={adapter}\n channel={channel}\n institutionId={institutionId || undefined}\n user={user}\n theme={theme}\n faqArticleUrl={faqArticleUrl ?? flag.faqArticleUrl}\n flags={{\n enabled: enabled && !flag.loading && flag.isNative && flag.chatEnabled,\n supportType: flag.isNative ? SupportType.NATIVE : SupportType.ZENDESK,\n }}\n />\n );\n};\n","import { isSupportTicketOrigin } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatAnswerApi,\n type SupportChatArticle,\n type SupportChatEvent,\n type SupportChatHttpClient,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface GuestSupportChatAdapterOptions {\n /** Client of the backend API (e.g. the app axios instance) */\n apiClient: SupportChatHttpClient;\n /**\n * Support WebSocket, including ?channel=support (e.g.\n * wss://bff-hml-chat.analyticaensino.com.br?channel=support).\n * Without it the adapter only polls the REST API.\n */\n wsUrl?: string;\n /** Polling interval while the socket is not connected, in ms */\n pollIntervalMs?: number;\n /**\n * localStorage key of the open conversation, kept so a reload resumes it.\n * Default: `analytica:support-chat`.\n */\n storageKey?: string;\n}\n\n/** Open conversation kept across page loads */\ninterface StoredSupportChatSession {\n ticketId: string;\n token: string;\n name: string;\n /** E-mail of the logged user who opened it; null for a visitor */\n userEmail: string | null;\n /** Opening text the chat does not show (image-only start) */\n hiddenId?: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'analytica:support-chat';\n\n/** The browser may block storage (private mode, policies): never throw */\nconst readStoredSession = (key: string): StoredSupportChatSession | null => {\n try {\n const raw = globalThis.localStorage?.getItem(key);\n if (!raw) return null;\n const session = JSON.parse(raw) as Partial<StoredSupportChatSession>;\n return session.ticketId && session.token\n ? (session as StoredSupportChatSession)\n : null;\n } catch {\n return null;\n }\n};\n\nconst writeStoredSession = (key: string, session: StoredSupportChatSession) => {\n try {\n globalThis.localStorage?.setItem(key, JSON.stringify(session));\n } catch {\n // Not kept: a reload starts a new conversation, as before\n }\n};\n\nconst clearStoredSession = (key: string) => {\n try {\n globalThis.localStorage?.removeItem(key);\n } catch {\n // Nothing kept to clear\n }\n};\n\nconst statusOf = (error: unknown) =>\n (error as { response?: { status?: number } } | null)?.response?.status;\n\nconst GUEST_TOKEN_HEADER = 'x-support-guest-token';\n\nconst senderNameFor = (role: string, requesterName: string) => {\n if (role === SUPPORT_CHAT_AUTHOR.AGENT) return 'Suporte';\n if (role === SUPPORT_CHAT_AUTHOR.AI) return 'Assistente';\n return requesterName;\n};\n\n/** REST answer → widget message */\nexport const fromSupportAnswer = (\n answer: SupportChatAnswerApi,\n requesterName: string\n): SupportChatMessage => ({\n id: answer.id,\n senderId:\n answer.authorType === SUPPORT_CHAT_AUTHOR.GUEST\n ? SUPPORT_CHAT_GUEST_SENDER_ID\n : answer.authorType.toLowerCase(),\n senderName: senderNameFor(answer.authorType, requesterName),\n senderPhoto: null,\n senderRole: answer.authorType,\n content: answer.answer,\n messageType: 'text',\n createdAt: answer.createdAt,\n attachments: answer.attachments ?? [],\n faq:\n answer.faqArticleId && answer.faqArticleTitle\n ? { id: answer.faqArticleId, title: answer.faqArticleTitle }\n : null,\n contactRequest: answer.contactRequest ?? null,\n});\n\n/**\n * Support chat adapter for visitors (no login), on top of the support API:\n * - POST /support/external opens the ticket and returns the guest token;\n * - GET/POST /support/guest/:id/answer read and send, with the token header;\n * - the chat WebSocket (?channel=support&roomId=&token=) pushes the events,\n * and REST polling covers the time the socket is down.\n */\nexport const createGuestSupportChatAdapter = ({\n apiClient,\n wsUrl,\n pollIntervalMs = 3000,\n storageKey = DEFAULT_STORAGE_KEY,\n}: GuestSupportChatAdapterOptions): SupportChatAdapter => {\n const sessions = new Map<string, { token: string; name: string }>();\n /**\n * Ticket opening text written by the adapter (an image-only start still\n * needs a description). It is the ticket description for the agents, not\n * something the visitor typed, so the chat does not show it.\n */\n const hiddenIds = new Set<string>();\n const isVisible = (message: SupportChatMessage) => !hiddenIds.has(message.id);\n\n const headersFor = (ticketId: string) => ({\n [GUEST_TOKEN_HEADER]: sessions.get(ticketId)?.token ?? '',\n });\n const nameFor = (ticketId: string) =>\n sessions.get(ticketId)?.name ?? 'Visitante';\n\n const listMessages = async (ticketId: string) => {\n const { data } = await apiClient.get<{ data: SupportChatAnswerApi[] }>(\n `/support/guest/${ticketId}/answer`,\n { headers: headersFor(ticketId) }\n );\n return data.data\n .map((answer) => fromSupportAnswer(answer, nameFor(ticketId)))\n .filter(isVisible);\n };\n\n /**\n * Upload each file straight to the storage: ask the backend for a signed\n * URL, then PUT the file there. Returns what the message needs.\n */\n const uploadFiles = (ticketId: string, files: File[]) =>\n Promise.all(\n files.map(async (file) => {\n const { data } = await apiClient.post<{\n data: { uploadUrl: string; key: string; contentType: string };\n }>(\n `/support/guest/${ticketId}/upload-url`,\n { fileName: file.name, fileSize: file.size, mimeType: file.type },\n { headers: headersFor(ticketId) }\n );\n const { uploadUrl, key, contentType } = data.data;\n const response = await fetch(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: file,\n });\n if (!response.ok) {\n throw new Error(`Não foi possível enviar ${file.name}`);\n }\n return {\n key,\n fileName: file.name,\n mimeType: contentType,\n size: file.size,\n };\n })\n );\n\n const sendMessage = async (\n ticketId: string,\n content: string,\n files: File[] = []\n ) => {\n const attachments = await uploadFiles(ticketId, files);\n const { data } = await apiClient.post<{ data: SupportChatAnswerApi }>(\n `/support/guest/${ticketId}/answer`,\n { answer: content, attachments },\n { headers: headersFor(ticketId) }\n );\n return fromSupportAnswer(data.data, nameFor(ticketId));\n };\n\n return {\n async startConversation({\n message,\n channel,\n institutionId,\n requesterName,\n email,\n files = [],\n }) {\n // The ticket needs a description; an image-only start uses the names\n const description = message || files.map((file) => file.name).join(', ');\n const { data } = await apiClient.post<{\n data: { id: string; guestToken: string };\n }>('/support/external', {\n description,\n ...(institutionId ? { institutionId } : {}),\n ...(requesterName ? { requesterName } : {}),\n ...(email ? { email } : {}),\n // The app the chat runs in, when it is one the API knows\n ...(isSupportTicketOrigin(channel) ? { origin: channel } : {}),\n });\n const ticketId = data.data.id;\n const name = requesterName || 'Visitante';\n sessions.set(ticketId, { token: data.data.guestToken, name });\n\n // The first message is saved with the ticket; read it back for its id.\n // /support/external takes no files, so they go as the next message.\n let messages = await listMessages(ticketId);\n let hiddenId: string | undefined;\n if (!message && messages[0]) {\n hiddenId = messages[0].id;\n hiddenIds.add(hiddenId);\n messages = messages.slice(1);\n }\n writeStoredSession(storageKey, {\n ticketId,\n token: data.data.guestToken,\n name,\n userEmail: email ?? null,\n hiddenId,\n });\n if (files.length) messages.push(await sendMessage(ticketId, '', files));\n return {\n ticket: {\n id: ticketId,\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n },\n messages,\n };\n },\n\n sendMessage,\n\n async getArticle(institutionId, articleId) {\n const { data } = await apiClient.get<{\n data: { article: SupportChatArticle };\n }>(`/help-center/${institutionId}/articles/${articleId}`);\n const { id, title, body, updatedAt } = data.data.article;\n return { id, title, body, updatedAt: updatedAt ?? null };\n },\n\n async updateContact(ticketId, contact) {\n if (!apiClient.put) {\n throw new Error('O cliente da API não envia PUT');\n }\n await apiClient.put(`/support/guest/${ticketId}`, contact, {\n headers: headersFor(ticketId),\n });\n const session = sessions.get(ticketId);\n if (session && contact.requesterName) {\n session.name = contact.requesterName;\n const stored = readStoredSession(storageKey);\n if (stored?.ticketId === ticketId) {\n writeStoredSession(storageKey, {\n ...stored,\n name: contact.requesterName,\n });\n }\n }\n },\n\n async restoreConversation({ email }) {\n const stored = readStoredSession(storageKey);\n if (!stored) return null;\n // Another account on this browser: that conversation is not theirs\n if (stored.userEmail !== (email ?? null)) {\n clearStoredSession(storageKey);\n return null;\n }\n\n const { ticketId } = stored;\n sessions.set(ticketId, { token: stored.token, name: stored.name });\n if (stored.hiddenId) hiddenIds.add(stored.hiddenId);\n try {\n const { data } = await apiClient.get<{ data: SupportChatTicket }>(\n `/support/guest/${ticketId}`,\n { headers: headersFor(ticketId) }\n );\n if (data.data.status === SUPPORT_CHAT_STATUS.FECHADO) {\n clearStoredSession(storageKey);\n return null;\n }\n return { ticket: data.data, messages: await listMessages(ticketId) };\n } catch (error) {\n // Ticket gone or token expired: it cannot be resumed. Any other\n // failure (offline) keeps it for the next load.\n if (statusOf(error) === 404) clearStoredSession(storageKey);\n sessions.delete(ticketId);\n return null;\n }\n },\n\n forgetConversation() {\n clearStoredSession(storageKey);\n },\n\n listMessages,\n\n subscribe(ticketId, listener) {\n // id → what the chat shows besides the text; a message seen again with\n // more of it (a stale WebSocket history may lack contactRequest/faq) is\n // delivered again so the chat replaces it\n const seen = new Map<string, string>();\n const extrasOf = (message: SupportChatMessage) =>\n JSON.stringify([message.contactRequest ?? null, message.faq ?? null]);\n const NO_EXTRAS = JSON.stringify([null, null]);\n let socket: WebSocket | null = null;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let active = true;\n\n const deliver = (message: SupportChatMessage) => {\n if (!isVisible(message)) return;\n const extras = extrasOf(message);\n const previous = seen.get(message.id);\n // Same as before, or a poorer copy: nothing new to show\n if (previous === extras || (previous && extras === NO_EXTRAS)) return;\n seen.set(message.id, extras);\n listener({ type: 'new_message', payload: { message } });\n };\n\n const poll = async () => {\n try {\n (await listMessages(ticketId)).forEach(deliver);\n } catch {\n // Next tick tries again\n }\n };\n const startPolling = () => {\n if (pollTimer || !active) return;\n void poll();\n pollTimer = setInterval(poll, pollIntervalMs);\n };\n const stopPolling = () => {\n if (pollTimer) clearInterval(pollTimer);\n pollTimer = null;\n };\n\n const handleFrame = (raw: string) => {\n let event: { type?: string; payload?: Record<string, unknown> };\n try {\n event = JSON.parse(raw);\n } catch {\n return;\n }\n const payload = event.payload ?? {};\n if (event.type === 'history') {\n (payload.messages as SupportChatMessage[]).forEach((message) =>\n deliver({ ...message, attachments: message.attachments ?? [] })\n );\n } else if (event.type === 'new_message') {\n const message = payload.message as SupportChatMessage;\n deliver({ ...message, attachments: message.attachments ?? [] });\n } else if (\n event.type === 'message_updated' ||\n event.type === 'ticket_updated'\n ) {\n listener(event as SupportChatEvent);\n } else if (event.type === 'error') {\n listener({\n type: 'error',\n payload: {\n message: String(payload.message_text ?? payload.message ?? ''),\n },\n });\n }\n };\n\n const token = sessions.get(ticketId)?.token;\n if (wsUrl && token) {\n const url = new URL(wsUrl);\n url.searchParams.set('channel', 'support');\n url.searchParams.set('roomId', ticketId);\n url.searchParams.set('token', token);\n socket = new WebSocket(url.toString());\n socket.onopen = () => {\n stopPolling();\n socket?.send(JSON.stringify({ type: 'getInitialData' }));\n // The REST API is the source of truth: a WebSocket deployed apart\n // from it may send a history without contactRequest/faq\n void poll();\n };\n socket.onmessage = (event) => handleFrame(String(event.data));\n socket.onclose = startPolling;\n socket.onerror = startPolling;\n } else {\n startPolling();\n }\n\n return () => {\n active = false;\n stopPolling();\n if (socket) {\n socket.onclose = null;\n socket.close();\n }\n };\n },\n };\n};\n","import {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_HANDLER,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface MockSupportChatAdapterOptions {\n /** Latency of every call, in ms (default 300) */\n delayMs?: number;\n /** Delay of the automatic agent reply, in ms (default 1500) */\n replyDelayMs?: number;\n /** Text of the agent reply; null disables it */\n replyText?: string | null;\n}\n\nconst DEFAULT_REPLY =\n 'Olá! Recebemos sua mensagem e já vamos te ajudar. Este é um atendimento de demonstração.';\n\n/**\n * In-memory adapter for the support chat widget. It mirrors the backend\n * contract (statuses, authors, events) so the widget can be built and tested\n * before the real API + WebSocket adapter exists.\n */\nexport const createMockSupportChatAdapter = ({\n delayMs = 300,\n replyDelayMs = 1500,\n replyText = DEFAULT_REPLY,\n}: MockSupportChatAdapterOptions = {}): SupportChatAdapter => {\n const tickets = new Map<string, SupportChatTicket>();\n const messages = new Map<string, SupportChatMessage[]>();\n const requesters = new Map<string, { name: string; logged: boolean }>();\n const listeners = new Map<string, Set<(event: SupportChatEvent) => void>>();\n let sequence = 0;\n\n const wait = () => new Promise((resolve) => setTimeout(resolve, delayMs));\n const nextId = (prefix: string) => `${prefix}-${++sequence}`;\n\n const emit = (ticketId: string, event: SupportChatEvent) => {\n listeners.get(ticketId)?.forEach((listener) => listener(event));\n };\n\n const append = (ticketId: string, message: SupportChatMessage) => {\n messages.set(ticketId, [...(messages.get(ticketId) ?? []), message]);\n };\n\n const toAttachments = (files: File[]) =>\n files.map((file) => ({\n id: nextId('file'),\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n }));\n\n const buildRequesterMessage = (\n ticketId: string,\n content: string,\n files: File[] = []\n ): SupportChatMessage => {\n const requester = requesters.get(ticketId);\n return {\n id: nextId('msg'),\n senderId: requester?.logged ? 'user' : SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: requester?.name ?? 'Visitante',\n senderPhoto: null,\n senderRole: requester?.logged\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: toAttachments(files),\n };\n };\n\n const scheduleReply = (ticketId: string) => {\n if (replyText === null) return;\n setTimeout(() => {\n const ticket = tickets.get(ticketId);\n if (!ticket) return;\n\n const reply: SupportChatMessage = {\n id: nextId('msg'),\n senderId: 'agent',\n senderName: 'Suporte',\n senderPhoto: null,\n senderRole: SUPPORT_CHAT_AUTHOR.AGENT,\n content: replyText,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: [],\n };\n append(ticketId, reply);\n\n const updated: SupportChatTicket = {\n ...ticket,\n status: SUPPORT_CHAT_STATUS.EM_ANDAMENTO,\n handlerType: SUPPORT_CHAT_HANDLER.HUMANO,\n };\n tickets.set(ticketId, updated);\n\n emit(ticketId, { type: 'new_message', payload: { message: reply } });\n emit(ticketId, {\n type: 'ticket_updated',\n payload: {\n ticketId,\n status: updated.status,\n handlerType: updated.handlerType,\n problemType: updated.problemType,\n },\n });\n }, replyDelayMs);\n };\n\n return {\n async getArticle(_institutionId, articleId) {\n await wait();\n return {\n id: articleId,\n title: 'Como gerar e localizar simulados',\n body:\n '<p>Monte um simulado com as matérias que quiser e acompanhe o resultado. 📝</p>' +\n '<h2>Passo a passo</h2>' +\n '<ol><li><p>No menu, abra <strong>Simulados</strong>.</p></li>' +\n '<li><p>Clique em <strong>Gerar simulado</strong> e escolha as áreas.</p></li>' +\n '<li><p>Responda e veja o resultado em <strong>Histórico</strong>.</p></li></ol>' +\n '<p><a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\">Assista ao vídeo do passo a passo</a></p>' +\n '<blockquote><p>💡 Dica: refaça as questões que errou.</p></blockquote>',\n updatedAt: '2026-10-21T12:00:00.000Z',\n };\n },\n\n async startConversation({ message, requesterName, email, files }) {\n await wait();\n const ticket: SupportChatTicket = {\n id: nextId('ticket'),\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n };\n tickets.set(ticket.id, ticket);\n requesters.set(ticket.id, {\n name: requesterName || 'Visitante',\n logged: Boolean(email),\n });\n\n const first = buildRequesterMessage(ticket.id, message, files);\n append(ticket.id, first);\n scheduleReply(ticket.id);\n return { ticket, messages: [first] };\n },\n\n async sendMessage(ticketId, content, files) {\n await wait();\n const ticket = tickets.get(ticketId);\n if (!ticket) throw new Error('Atendimento não encontrado');\n if (ticket.status === SUPPORT_CHAT_STATUS.FECHADO) {\n throw new Error('Este atendimento já foi encerrado');\n }\n\n const message = buildRequesterMessage(ticketId, content, files);\n append(ticketId, message);\n scheduleReply(ticketId);\n return message;\n },\n\n async listMessages(ticketId) {\n await wait();\n return [...(messages.get(ticketId) ?? [])];\n },\n\n subscribe(ticketId, listener) {\n const set = listeners.get(ticketId) ?? new Set();\n set.add(listener);\n listeners.set(ticketId, set);\n return () => {\n set.delete(listener);\n };\n },\n };\n};\n"]}
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
DropdownMenuContent,
|
|
9
9
|
DropdownMenuTrigger,
|
|
10
10
|
DropdownMenu_default
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7ZWEBNFB.mjs";
|
|
12
12
|
import {
|
|
13
13
|
Modal_default
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LODDYDTP.mjs";
|
|
15
15
|
|
|
16
16
|
// src/components/CalendarCard/CalendarCard.tsx
|
|
17
17
|
import { CalendarBlankIcon } from "@phosphor-icons/react/dist/csr/CalendarBlank";
|
|
@@ -98,4 +98,4 @@ export {
|
|
|
98
98
|
CalendarCard,
|
|
99
99
|
CalendarCard_default
|
|
100
100
|
};
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
101
|
+
//# sourceMappingURL=chunk-2OJ3QSLU.mjs.map
|
|
@@ -140,7 +140,7 @@ var CheckBox = forwardRef(
|
|
|
140
140
|
"div",
|
|
141
141
|
{
|
|
142
142
|
className: cn(
|
|
143
|
-
"flex flex-row items-center",
|
|
143
|
+
"relative flex flex-row items-center",
|
|
144
144
|
sizeClasses.spacing,
|
|
145
145
|
disabled ? "opacity-40" : ""
|
|
146
146
|
),
|
|
@@ -214,4 +214,4 @@ var CheckBox_default = CheckBox;
|
|
|
214
214
|
export {
|
|
215
215
|
CheckBox_default
|
|
216
216
|
};
|
|
217
|
-
//# sourceMappingURL=chunk-
|
|
217
|
+
//# sourceMappingURL=chunk-3EGJJDIM.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/CheckBox/CheckBox.tsx"],"sourcesContent":["import {\n InputHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n} from 'react';\nimport Text from '../Text/Text';\nimport { CheckIcon } from '@phosphor-icons/react/dist/csr/Check';\nimport { MinusIcon } from '@phosphor-icons/react/dist/csr/Minus';\nimport { cn } from '../../utils/utils';\n\n/**\n * CheckBox size variants\n */\ntype CheckBoxSize = 'small' | 'medium' | 'large';\n\n/**\n * CheckBox visual state\n */\ntype CheckBoxState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations using Tailwind classes\n */\nconst SIZE_CLASSES = {\n small: {\n checkbox: 'w-4 h-4', // 16px x 16px\n textSize: 'sm' as const,\n spacing: 'gap-1.5', // 6px\n borderWidth: 'border-2',\n iconSize: 14, // pixels for Phosphor icons\n labelHeight: 'h-[21px]',\n },\n medium: {\n checkbox: 'w-5 h-5', // 20px x 20px\n textSize: 'md' as const,\n spacing: 'gap-2', // 8px\n borderWidth: 'border-2',\n iconSize: 16, // pixels for Phosphor icons\n labelHeight: 'h-6',\n },\n large: {\n checkbox: 'w-6 h-6', // 24px x 24px\n textSize: 'lg' as const,\n spacing: 'gap-2', // 8px\n borderWidth: 'border-[3px]', // 3px border\n iconSize: 20, // pixels for Phosphor icons\n labelHeight: 'h-[27px]',\n },\n} as const;\n\n/**\n * Base checkbox styling classes using design system colors\n */\nconst BASE_CHECKBOX_CLASSES =\n 'rounded border cursor-pointer transition-all duration-200 flex items-center justify-center focus:outline-none';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n unchecked: 'border-border-400 bg-background hover:border-border-500',\n checked:\n 'border-primary-950 bg-primary-950 text-text hover:border-primary-800 hover:bg-primary-800',\n },\n hovered: {\n unchecked: 'border-border-500 bg-background',\n checked: 'border-primary-800 bg-primary-800 text-text',\n },\n focused: {\n unchecked:\n 'border-indicator-info bg-background ring-2 ring-indicator-info/20',\n checked:\n 'border-indicator-info bg-primary-950 text-text ring-2 ring-indicator-info/20',\n },\n invalid: {\n unchecked: 'border-error-700 bg-background hover:border-error-600',\n checked: 'border-error-700 bg-primary-950 text-text',\n },\n disabled: {\n unchecked: 'border-border-400 bg-background cursor-not-allowed opacity-40',\n checked:\n 'border-primary-600 bg-primary-600 text-text cursor-not-allowed opacity-40',\n },\n} as const;\n\n/**\n * CheckBox component props interface\n */\nexport type CheckBoxProps = {\n /** Label text to display next to the checkbox */\n label?: ReactNode;\n /** Size variant of the checkbox */\n size?: CheckBoxSize;\n /** Visual state of the checkbox */\n state?: CheckBoxState;\n /** Indeterminate state for partial selections */\n indeterminate?: boolean;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperText?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'type'>;\n\n/**\n * CheckBox component for Analytica Ensino platforms\n *\n * A checkbox component with essential states, sizes and themes.\n * Uses the Analytica Ensino Design System colors from styles.css with automatic\n * light/dark mode support. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic checkbox\n * <CheckBox label=\"Option\" />\n *\n * // Small size\n * <CheckBox size=\"small\" label=\"Small option\" />\n *\n * // Invalid state\n * <CheckBox state=\"invalid\" label=\"Required field\" />\n *\n * // Disabled state\n * <CheckBox disabled label=\"Disabled option\" />\n * ```\n */\nconst CheckBox = forwardRef<HTMLInputElement, CheckBoxProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n indeterminate = false,\n errorMessage,\n helperText,\n className = '',\n labelClassName = '',\n checked: checkedProp,\n disabled,\n id,\n onChange,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `checkbox-${generatedId}`;\n\n // Handle controlled vs uncontrolled behavior\n const [internalChecked, setInternalChecked] = useState(false);\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(event.target.checked);\n }\n onChange?.(event);\n };\n\n // Determine current state based on props\n const currentState = disabled ? 'disabled' : state;\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Determine checkbox visual variant\n const checkVariant = checked || indeterminate ? 'checked' : 'unchecked';\n\n // Get styling classes\n const stylingClasses = STATE_CLASSES[currentState][checkVariant];\n\n // Special border width handling for focused/hovered states and large size\n const borderWidthClass =\n state === 'focused' || (state === 'hovered' && size === 'large')\n ? 'border-[3px]'\n : sizeClasses.borderWidth;\n\n // Get final checkbox classes\n const checkboxClasses = cn(\n BASE_CHECKBOX_CLASSES,\n sizeClasses.checkbox,\n borderWidthClass,\n stylingClasses,\n className\n );\n\n // Render appropriate icon based on state\n const renderIcon = () => {\n if (indeterminate) {\n return (\n <MinusIcon\n size={sizeClasses.iconSize}\n weight=\"bold\"\n color=\"currentColor\"\n />\n );\n }\n\n if (checked) {\n return (\n <CheckIcon\n size={sizeClasses.iconSize}\n weight=\"bold\"\n color=\"currentColor\"\n />\n );\n }\n\n return null;\n };\n\n return (\n <div className=\"flex flex-col\">\n <div\n className={cn(\n 'flex flex-row items-center',\n sizeClasses.spacing,\n disabled ? 'opacity-40' : ''\n )}\n >\n {/* Hidden native input for accessibility and form submission */}\n <input\n ref={ref}\n type=\"checkbox\"\n id={inputId}\n checked={checked}\n disabled={disabled}\n onChange={handleChange}\n className=\"sr-only\"\n {...props}\n />\n\n {/* Custom styled checkbox */}\n <label htmlFor={inputId} className={checkboxClasses}>\n {/* Show appropriate icon based on state */}\n {renderIcon()}\n </label>\n\n {/* Label text */}\n {label && (\n <div\n className={cn(\n 'flex flex-row items-center',\n sizeClasses.labelHeight\n )}\n >\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"normal\"\n className={cn(\n 'cursor-pointer select-none leading-[150%] flex items-center font-roboto',\n labelClassName\n )}\n >\n {label}\n </Text>\n </div>\n )}\n </div>\n\n {/* Error message */}\n {errorMessage && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className=\"mt-1.5\"\n color=\"text-error-600\"\n >\n {errorMessage}\n </Text>\n )}\n\n {/* Helper text */}\n {helperText && !errorMessage && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className=\"mt-1.5\"\n color=\"text-text-500\"\n >\n {helperText}\n </Text>\n )}\n </div>\n );\n }\n);\n\nCheckBox.displayName = 'CheckBox';\n\nexport default CheckBox;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/components/CheckBox/CheckBox.tsx"],"sourcesContent":["import {\n InputHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n} from 'react';\nimport Text from '../Text/Text';\nimport { CheckIcon } from '@phosphor-icons/react/dist/csr/Check';\nimport { MinusIcon } from '@phosphor-icons/react/dist/csr/Minus';\nimport { cn } from '../../utils/utils';\n\n/**\n * CheckBox size variants\n */\ntype CheckBoxSize = 'small' | 'medium' | 'large';\n\n/**\n * CheckBox visual state\n */\ntype CheckBoxState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations using Tailwind classes\n */\nconst SIZE_CLASSES = {\n small: {\n checkbox: 'w-4 h-4', // 16px x 16px\n textSize: 'sm' as const,\n spacing: 'gap-1.5', // 6px\n borderWidth: 'border-2',\n iconSize: 14, // pixels for Phosphor icons\n labelHeight: 'h-[21px]',\n },\n medium: {\n checkbox: 'w-5 h-5', // 20px x 20px\n textSize: 'md' as const,\n spacing: 'gap-2', // 8px\n borderWidth: 'border-2',\n iconSize: 16, // pixels for Phosphor icons\n labelHeight: 'h-6',\n },\n large: {\n checkbox: 'w-6 h-6', // 24px x 24px\n textSize: 'lg' as const,\n spacing: 'gap-2', // 8px\n borderWidth: 'border-[3px]', // 3px border\n iconSize: 20, // pixels for Phosphor icons\n labelHeight: 'h-[27px]',\n },\n} as const;\n\n/**\n * Base checkbox styling classes using design system colors\n */\nconst BASE_CHECKBOX_CLASSES =\n 'rounded border cursor-pointer transition-all duration-200 flex items-center justify-center focus:outline-none';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n unchecked: 'border-border-400 bg-background hover:border-border-500',\n checked:\n 'border-primary-950 bg-primary-950 text-text hover:border-primary-800 hover:bg-primary-800',\n },\n hovered: {\n unchecked: 'border-border-500 bg-background',\n checked: 'border-primary-800 bg-primary-800 text-text',\n },\n focused: {\n unchecked:\n 'border-indicator-info bg-background ring-2 ring-indicator-info/20',\n checked:\n 'border-indicator-info bg-primary-950 text-text ring-2 ring-indicator-info/20',\n },\n invalid: {\n unchecked: 'border-error-700 bg-background hover:border-error-600',\n checked: 'border-error-700 bg-primary-950 text-text',\n },\n disabled: {\n unchecked: 'border-border-400 bg-background cursor-not-allowed opacity-40',\n checked:\n 'border-primary-600 bg-primary-600 text-text cursor-not-allowed opacity-40',\n },\n} as const;\n\n/**\n * CheckBox component props interface\n */\nexport type CheckBoxProps = {\n /** Label text to display next to the checkbox */\n label?: ReactNode;\n /** Size variant of the checkbox */\n size?: CheckBoxSize;\n /** Visual state of the checkbox */\n state?: CheckBoxState;\n /** Indeterminate state for partial selections */\n indeterminate?: boolean;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperText?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'type'>;\n\n/**\n * CheckBox component for Analytica Ensino platforms\n *\n * A checkbox component with essential states, sizes and themes.\n * Uses the Analytica Ensino Design System colors from styles.css with automatic\n * light/dark mode support. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic checkbox\n * <CheckBox label=\"Option\" />\n *\n * // Small size\n * <CheckBox size=\"small\" label=\"Small option\" />\n *\n * // Invalid state\n * <CheckBox state=\"invalid\" label=\"Required field\" />\n *\n * // Disabled state\n * <CheckBox disabled label=\"Disabled option\" />\n * ```\n */\nconst CheckBox = forwardRef<HTMLInputElement, CheckBoxProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n indeterminate = false,\n errorMessage,\n helperText,\n className = '',\n labelClassName = '',\n checked: checkedProp,\n disabled,\n id,\n onChange,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `checkbox-${generatedId}`;\n\n // Handle controlled vs uncontrolled behavior\n const [internalChecked, setInternalChecked] = useState(false);\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(event.target.checked);\n }\n onChange?.(event);\n };\n\n // Determine current state based on props\n const currentState = disabled ? 'disabled' : state;\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Determine checkbox visual variant\n const checkVariant = checked || indeterminate ? 'checked' : 'unchecked';\n\n // Get styling classes\n const stylingClasses = STATE_CLASSES[currentState][checkVariant];\n\n // Special border width handling for focused/hovered states and large size\n const borderWidthClass =\n state === 'focused' || (state === 'hovered' && size === 'large')\n ? 'border-[3px]'\n : sizeClasses.borderWidth;\n\n // Get final checkbox classes\n const checkboxClasses = cn(\n BASE_CHECKBOX_CLASSES,\n sizeClasses.checkbox,\n borderWidthClass,\n stylingClasses,\n className\n );\n\n // Render appropriate icon based on state\n const renderIcon = () => {\n if (indeterminate) {\n return (\n <MinusIcon\n size={sizeClasses.iconSize}\n weight=\"bold\"\n color=\"currentColor\"\n />\n );\n }\n\n if (checked) {\n return (\n <CheckIcon\n size={sizeClasses.iconSize}\n weight=\"bold\"\n color=\"currentColor\"\n />\n );\n }\n\n return null;\n };\n\n return (\n <div className=\"flex flex-col\">\n {/*\n * `relative` anchors the `sr-only` input (absolutely positioned) to\n * this row. Without it the input is positioned against the nearest\n * positioned ancestor — inside a Modal, the <dialog> itself — and\n * ignores how far a scrolled list moved. Focusing it on click then\n * scrolled the dialog to \"reveal\" it and pushed the whole modal out of\n * view, leaving a blank box (AE-3237).\n */}\n <div\n className={cn(\n 'relative flex flex-row items-center',\n sizeClasses.spacing,\n disabled ? 'opacity-40' : ''\n )}\n >\n {/* Hidden native input for accessibility and form submission */}\n <input\n ref={ref}\n type=\"checkbox\"\n id={inputId}\n checked={checked}\n disabled={disabled}\n onChange={handleChange}\n className=\"sr-only\"\n {...props}\n />\n\n {/* Custom styled checkbox */}\n <label htmlFor={inputId} className={checkboxClasses}>\n {/* Show appropriate icon based on state */}\n {renderIcon()}\n </label>\n\n {/* Label text */}\n {label && (\n <div\n className={cn(\n 'flex flex-row items-center',\n sizeClasses.labelHeight\n )}\n >\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"normal\"\n className={cn(\n 'cursor-pointer select-none leading-[150%] flex items-center font-roboto',\n labelClassName\n )}\n >\n {label}\n </Text>\n </div>\n )}\n </div>\n\n {/* Error message */}\n {errorMessage && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className=\"mt-1.5\"\n color=\"text-error-600\"\n >\n {errorMessage}\n </Text>\n )}\n\n {/* Helper text */}\n {helperText && !errorMessage && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className=\"mt-1.5\"\n color=\"text-text-500\"\n >\n {helperText}\n </Text>\n )}\n </div>\n );\n }\n);\n\nCheckBox.displayName = 'CheckBox';\n\nexport default CheckBox;\n"],"mappings":"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","names":[]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CheckBox_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3EGJJDIM.mjs";
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenuContent,
|
|
6
6
|
DropdownMenuItem,
|
|
7
7
|
DropdownMenuSeparator,
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
DropdownMenu_default
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7ZWEBNFB.mjs";
|
|
11
11
|
import {
|
|
12
12
|
Text_default
|
|
13
13
|
} from "./chunk-IMCIR6TJ.mjs";
|
|
@@ -102,4 +102,4 @@ export {
|
|
|
102
102
|
MultiCheckSelect,
|
|
103
103
|
MultiCheckSelect_default
|
|
104
104
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
105
|
+
//# sourceMappingURL=chunk-3HKLGNUE.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkEW7HGUFVjs = require('./chunk-EW7HGUFV.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -305,7 +305,7 @@ function SubtopicsTable({
|
|
|
305
305
|
}
|
|
306
306
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col items-center gap-4", children: [
|
|
307
307
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
308
|
-
|
|
308
|
+
_chunkEW7HGUFVjs.TableProvider,
|
|
309
309
|
{
|
|
310
310
|
data: visibleRows,
|
|
311
311
|
headers: SUBTOPIC_COLUMNS,
|
|
@@ -426,4 +426,4 @@ function QuestionsPerformanceCard({
|
|
|
426
426
|
|
|
427
427
|
|
|
428
428
|
exports.QuestionsBars = QuestionsBars; exports.RateCell = RateCell; exports.QuestionsPerformanceCard = QuestionsPerformanceCard;
|
|
429
|
-
//# sourceMappingURL=chunk-
|
|
429
|
+
//# sourceMappingURL=chunk-3MFACEZ2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YA7XLH7D.js","../src/components/QuestionsPerformanceCard/QuestionsPerformanceCard.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-YA7XLH7D.js","sourcesContent":[null,"import { useId, useMemo, useState, type ReactNode } 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 ProgressBar from '../ProgressBar/ProgressBar';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport { TableProvider } from '../TableProvider/TableProvider';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport { calculateYAxisTicks } from '../shared/ChartComponents';\nimport { formatAxisTick } from '../../utils/chartUtils';\nimport { cn } from '../../utils/utils';\nimport type { ActivitiesQuestionsData, SubtopicPerformanceItem } from './types';\n\n/**\n * The four bars, in the order, colours and wording of the design: `label`\n * sits under the bar, `legendLabel` heads the card on the right.\n */\nconst BARS = [\n {\n key: 'total',\n label: 'Total',\n legendLabel: 'Total de questões respondidas',\n color: 'bg-info-600',\n },\n {\n key: 'corretas',\n label: 'Corretas',\n legendLabel: 'Questões corretas',\n color: 'bg-success-200',\n },\n {\n key: 'incorretas',\n label: 'Incorretas',\n legendLabel: 'Questões incorretas',\n color: 'bg-warning-400',\n },\n {\n key: 'emBranco',\n label: 'Em branco',\n legendLabel: 'Questões em branco',\n color: 'bg-background-200',\n },\n] as const;\n\ntype BarKey = (typeof BARS)[number]['key'];\n\n/** What each of the four bars counts. */\nexport type QuestionsBarsValues = Record<BarKey, number>;\n\nconst CHART_HEIGHT = 235;\n\n/** Sentinel of the tema select meaning \"every tema of the subject\". */\nconst ALL_TOPICS = 'all';\n\n/** Subtemas shown before \"Mostrar todos\". */\nconst COLLAPSED_ROWS = 4;\n\n/**\n * Share of `value` in `total`, as the whole percentage the cards print.\n * A total of zero reads as 0% rather than NaN.\n */\nfunction shareOfTotal(value: number, total: number): number {\n return total > 0 ? Math.round((value / total) * 100) : 0;\n}\n\n/** Hit rate as the table prints it: one decimal, pt-BR. */\nfunction formatRate(value: number): string {\n return `${value.toLocaleString('pt-BR', {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n })}%`;\n}\n\n/** One legend card: coloured dot and label, the count, its share of the total. */\nfunction LegendCard({\n color,\n label,\n value,\n percentage,\n}: {\n readonly color: string;\n readonly label: string;\n readonly value: number;\n readonly percentage: number;\n}) {\n return (\n <div className=\"flex flex-col justify-center gap-1 rounded-xl px-6 py-4\">\n <div className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className={cn('size-2 shrink-0 rounded-full', color)}\n />\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-600\">\n {label}\n </Text>\n </div>\n <Text size=\"4xl\" weight=\"bold\" className=\"text-info-700\">\n {value.toLocaleString('pt-BR')}\n </Text>\n <Text size=\"xs\" weight=\"medium\" className=\"text-text-800\">\n {`${percentage}% do total`}\n </Text>\n </div>\n );\n}\n\nexport interface QuestionsBarsProps {\n readonly values: QuestionsBarsValues;\n /**\n * Shown under the legend cards — the \"Nota média\" of a report that has one\n * (the Momento ENEM report of the gestor app).\n */\n readonly aside?: ReactNode;\n}\n\n/**\n * The bars with their axes, plus the four legend cards on the right.\n *\n * Exported for the reports that pair the same chart with a select and a\n * table of their own, not a subject's subtemas.\n */\nexport function QuestionsBars({ values, aside }: QuestionsBarsProps) {\n const [hoveredKey, setHoveredKey] = useState<string | null>(null);\n const legendId = useId();\n\n const ticks = calculateYAxisTicks(values.total);\n const chartMax = ticks[0];\n\n return (\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-stretch\">\n {/* Chart */}\n {/* As barras são divs sem semântica e os cartões de legenda ao lado já\n trazem cada número por extenso: o gráfico vira uma imagem nomeada e\n descrita pelos próprios cartões, sem repetir os dados numa tabela. */}\n <div\n role=\"img\"\n aria-label=\"Gráfico de barras das questões\"\n aria-describedby={legendId}\n className=\"flex min-w-0 flex-1 flex-col gap-2\"\n >\n <div className=\"flex flex-row\">\n {/* Y axis */}\n {/* `min-w-12` e não `w-12`: a largura fixa cortava o rótulo assim\n que a rede passou de um milhão de respostas. O formato compacto\n resolve a leitura; o mínimo preserva o desenho dos gráficos que\n contam centenas. */}\n <div\n className=\"flex min-w-12 shrink-0 flex-col items-end justify-between pr-2\"\n style={{ height: CHART_HEIGHT }}\n aria-hidden=\"true\"\n >\n {ticks.map((tick) => (\n <Text\n key={tick}\n size=\"md\"\n className=\"whitespace-nowrap text-text-700\"\n >\n {formatAxisTick(tick)}\n </Text>\n ))}\n </div>\n\n <div className=\"relative flex-1\">\n {/* Grid lines */}\n <div\n className=\"pointer-events-none absolute inset-0 flex flex-col justify-between\"\n style={{ height: CHART_HEIGHT }}\n >\n {ticks.map((tick) => (\n <div\n key={tick}\n className=\"border-t border-dashed border-border-400\"\n />\n ))}\n </div>\n\n <div\n className=\"relative z-10 flex flex-row items-end gap-4\"\n style={{ height: CHART_HEIGHT }}\n >\n {BARS.map((bar) => {\n const value = values[bar.key];\n const isHovered = hoveredKey === bar.key;\n const anyHovered = hoveredKey !== null;\n const height =\n chartMax === 0 ? 0 : (value / chartMax) * CHART_HEIGHT;\n\n return (\n <div\n key={bar.key}\n className=\"relative flex h-full flex-1 flex-col items-center justify-end\"\n onMouseEnter={() => setHoveredKey(bar.key)}\n onMouseLeave={() => setHoveredKey(null)}\n >\n {isHovered && value > 0 && (\n <div\n role=\"tooltip\"\n className=\"absolute z-10 whitespace-nowrap rounded bg-text-950 px-2 py-1 text-xs text-white shadow-lg\"\n style={{ bottom: `${height + 8}px` }}\n >\n {value.toLocaleString('pt-BR')}\n <div className=\"absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-text-950\" />\n </div>\n )}\n <div\n data-testid={`questions-bar-${bar.key}`}\n className={cn(\n 'w-full max-w-20 cursor-pointer rounded transition-all duration-300',\n bar.color\n )}\n style={{\n height: `${height}px`,\n minHeight: value > 0 ? '4px' : '0',\n opacity: !anyHovered || isHovered ? 1 : 0.5,\n }}\n />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n\n {/* X axis labels, aligned to the bars (ml-12 matches the Y axis width) */}\n <div className=\"ml-12 flex flex-row gap-4\" aria-hidden=\"true\">\n {BARS.map((bar) => (\n <div key={bar.key} className=\"flex flex-1 justify-center\">\n <Text size=\"md\" className=\"text-text-700\">\n {bar.label}\n </Text>\n </div>\n ))}\n </div>\n </div>\n\n <div aria-hidden=\"true\" className=\"hidden w-px bg-border-200 lg:block\" />\n\n {/* Legend cards, and whatever the caller adds under them */}\n <div className=\"flex flex-1 flex-col justify-center gap-2\">\n <div\n id={legendId}\n data-testid=\"questions-legend\"\n className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\"\n >\n {BARS.map((bar) => (\n <LegendCard\n key={bar.key}\n color={bar.color}\n label={bar.legendLabel}\n value={values[bar.key]}\n percentage={shareOfTotal(values[bar.key], values.total)}\n />\n ))}\n </div>\n {aside}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Subtemas (one subject selected)\n// ---------------------------------------------------------------------------\n\n/** Row shape of the subtema table; `rowId` is what `rowKey` addresses. */\ninterface SubtopicRow {\n rowId: string;\n name: string;\n total: number;\n correct: number;\n incorrect: number;\n blank: number;\n rate: number;\n /** Index signature for TableProvider compatibility */\n [key: string]: unknown;\n}\n\ntype SubtopicSortKey = 'name' | 'correct' | 'incorrect' | 'blank' | 'rate';\n\n/**\n * Taxa de acerto cell: the rate (0–100) over a green bar. Exported for the\n * other tables that print a rate the same way.\n *\n * `className` exists for the table that cannot afford the default 128px. In\n * auto table layout a fixed-width cell is the floor of its column, so this one\n * number decides how narrow \"Taxa de acerto\" can ever get — it was 144px of the\n * students table, which stopped fitting its card.\n */\nexport function RateCell({\n rate,\n className,\n}: {\n readonly rate: number;\n readonly className?: string;\n}) {\n return (\n <div className={cn('flex w-32 flex-col gap-1', className)}>\n <Text size=\"xs\" weight=\"bold\" className=\"text-success-500\">\n {formatRate(rate)}\n </Text>\n {/* O percentual já está escrito acima: a barra é só reforço visual e\n não deve ser lida de novo pelo leitor de tela. */}\n <ProgressBar value={rate} variant=\"green\" size=\"small\" decorative />\n </div>\n );\n}\n\nconst SUBTOPIC_COLUMNS: ColumnConfig<SubtopicRow>[] = [\n {\n key: 'name',\n label: 'Subtema',\n sortable: true,\n className: 'min-w-[240px]',\n },\n { key: 'total', label: 'Total', sortable: false },\n { key: 'correct', label: 'Corretas', sortable: true },\n { key: 'incorrect', label: 'Incorretas', sortable: true },\n { key: 'blank', label: 'Em branco', sortable: true },\n {\n key: 'rate',\n label: 'Taxa de acerto',\n sortable: true,\n render: (_value: unknown, row: SubtopicRow) => <RateCell rate={row.rate} />,\n },\n];\n\n/**\n * Map a subtema onto a table row.\n *\n * Every number arrives ready: the endpoint counts the blanks by answer status\n * and computes the rate over the total, so nothing is derived here.\n */\nfunction toSubtopicRow(subtopic: SubtopicPerformanceItem): SubtopicRow {\n return {\n rowId: subtopic.subtopicId,\n name: subtopic.subtopicName,\n total: subtopic.total,\n correct: subtopic.correct,\n incorrect: subtopic.incorrect,\n blank: subtopic.blank,\n rate: subtopic.correctPercentage,\n };\n}\n\n/** Sort rows by one column, names in pt-BR order. */\nfunction sortRows(\n rows: SubtopicRow[],\n key: SubtopicSortKey,\n order: 'asc' | 'desc'\n): SubtopicRow[] {\n const direction = order === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const result =\n key === 'name'\n ? a.name.localeCompare(b.name, 'pt-BR')\n : Number(a[key]) - Number(b[key]);\n return result * direction;\n });\n}\n\n/**\n * \"Desempenho por subtema\": the subtemas of the selected subject (and tema),\n * searchable and sortable, collapsed to four rows behind \"Mostrar todos\".\n */\nfunction SubtopicsTable({\n subtopics,\n loading,\n error,\n}: {\n readonly subtopics: SubtopicPerformanceItem[];\n readonly loading: boolean;\n readonly error: string | null;\n}) {\n const [search, setSearch] = useState('');\n const [sort, setSort] = useState<{\n key: SubtopicSortKey;\n order: 'asc' | 'desc';\n }>({ key: 'rate', order: 'asc' });\n const [expanded, setExpanded] = useState(false);\n\n const rows = useMemo(() => {\n const term = search.trim().toLocaleLowerCase('pt-BR');\n const matching = subtopics\n .map(toSubtopicRow)\n .filter(\n (row) => !term || row.name.toLocaleLowerCase('pt-BR').includes(term)\n );\n return sortRows(matching, sort.key, sort.order);\n }, [subtopics, search, sort]);\n\n const visibleRows = expanded ? rows : rows.slice(0, COLLAPSED_ROWS);\n const hasMore = rows.length > COLLAPSED_ROWS;\n\n // Search and sort arrive through the table params; sorting is done here\n // (server mode) so it applies to the whole list before the collapse cut,\n // not only to the four rows on screen.\n const handleParamsChange = (params: Record<string, unknown>) => {\n setSearch((params.search as string) ?? '');\n const sortBy = params.sortBy as SubtopicSortKey | undefined;\n if (sortBy) {\n setSort({\n key: sortBy,\n order: (params.sortOrder as 'asc' | 'desc') ?? 'asc',\n });\n }\n };\n\n if (loading) {\n return <SkeletonCard className=\"min-h-[240px]\" />;\n }\n\n if (error) {\n return (\n <Text size=\"sm\" className=\"text-text-500\">\n {error}\n </Text>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <TableProvider<SubtopicRow>\n data={visibleRows}\n headers={SUBTOPIC_COLUMNS}\n rowKey=\"rowId\"\n enableSearch\n enableTableSort\n sortMode=\"server\"\n onParamsChange={handleParamsChange}\n searchPlaceholder=\"Buscar subtema\"\n headerContent={\n <div className=\"flex flex-col gap-2\">\n <Text as=\"h4\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Desempenho por subtema\n </Text>\n <Badge\n variant=\"solid\"\n action=\"info\"\n size=\"small\"\n className=\"self-start uppercase\"\n >\n {`${subtopics.length} subtemas totais`}\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} subtemas`}\n {expanded ? <CaretUpIcon size={18} /> : <CaretDownIcon size={18} />}\n </Button>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/**\n * \"Dados gerais de questões\" — the bars on the left, a divider, and one card\n * per bar on the right with the count and its share of the total.\n *\n * With one componente curricular selected the card grows: a tema select joins\n * the title, and the subject's subtemas are listed below the chart. Picking a\n * tema narrows both the table and the bars, which are then summed from the\n * subtemas of that tema (the questions endpoint has no tema cut).\n *\n * Written apart from `QuestionsData` because that card shows the number\n * nowhere: switching componente curricular between two subjects with 15 and 14\n * correct answers moved one bar by ten pixels and nothing else, which read as a\n * frozen screen. The hover tooltip follows `VerticalBarChart` so the two charts\n * of the report behave alike.\n */\nexport interface QuestionsPerformanceCardProps {\n readonly data: ActivitiesQuestionsData;\n /** Subtemas of the selected subject; null while \"Todos\" is selected. */\n readonly subtopics?: SubtopicPerformanceItem[] | null;\n readonly subtopicsLoading?: boolean;\n readonly subtopicsError?: string | null;\n /**\n * Card heading. Left out, it follows the cut on screen: \"Dados gerais de\n * questões\" for the whole report, \"Dados de questões\" once a componente\n * curricular narrows it — the numbers stop being the report's totals and\n * become that subject's.\n */\n readonly title?: string;\n}\n\nexport function QuestionsPerformanceCard({\n data,\n subtopics = null,\n subtopicsLoading = false,\n subtopicsError = null,\n title,\n}: QuestionsPerformanceCardProps) {\n const [topicId, setTopicId] = useState(ALL_TOPICS);\n\n const subjectSelected = subtopics !== null;\n\n const cardTitle =\n title ??\n (subjectSelected ? 'Dados de questões' : 'Dados gerais de questões');\n\n const topics = useMemo(() => {\n const byId = new Map<string, string>();\n for (const subtopic of subtopics ?? []) {\n byId.set(subtopic.topic.id, subtopic.topic.name);\n }\n return [...byId.entries()]\n .map(([id, name]) => ({ id, name }))\n .sort((a, b) => a.name.localeCompare(b.name, 'pt-BR'));\n }, [subtopics]);\n\n // A tema that vanished with a new subject falls back to \"all\" on read.\n const activeTopicId = topics.some((topic) => topic.id === topicId)\n ? topicId\n : ALL_TOPICS;\n\n const filteredSubtopics = useMemo(\n () =>\n activeTopicId === ALL_TOPICS\n ? (subtopics ?? [])\n : (subtopics ?? []).filter(\n (subtopic) => subtopic.topic.id === activeTopicId\n ),\n [subtopics, activeTopicId]\n );\n\n const values = useMemo<QuestionsBarsValues>(() => {\n if (activeTopicId === ALL_TOPICS) {\n return {\n total: data.totalAnswered,\n corretas: data.correctAnswers,\n incorretas: data.incorrectAnswers,\n emBranco: data.blankAnswers,\n };\n }\n const rows = filteredSubtopics.map(toSubtopicRow);\n const sum = (pick: (row: SubtopicRow) => number) =>\n rows.reduce((acc, row) => acc + pick(row), 0);\n return {\n total: sum((row) => row.total),\n corretas: sum((row) => row.correct),\n incorretas: sum((row) => row.incorrect),\n emBranco: sum((row) => row.blank),\n };\n }, [data, filteredSubtopics, activeTopicId]);\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 {cardTitle}\n </Text>\n {subjectSelected && topics.length > 0 && (\n <Select\n value={activeTopicId}\n onValueChange={setTopicId}\n className=\"w-[380px] max-w-full\"\n >\n <SelectTrigger className=\"w-full\" aria-label=\"Selecionar tema\">\n <SelectValue placeholder=\"Todos os temas\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_TOPICS}>Todos os temas</SelectItem>\n {topics.map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n\n <QuestionsBars values={values} />\n\n {subjectSelected && (\n <>\n <div aria-hidden=\"true\" className=\"h-px w-full bg-border-200\" />\n <SubtopicsTable\n subtopics={filteredSubtopics}\n loading={subtopicsLoading}\n error={subtopicsError}\n />\n </>\n )}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-3MFACEZ2.js","../src/components/QuestionsPerformanceCard/QuestionsPerformanceCard.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-3MFACEZ2.js","sourcesContent":[null,"import { useId, useMemo, useState, type ReactNode } 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 ProgressBar from '../ProgressBar/ProgressBar';\nimport Select, {\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../Select/Select';\nimport { SkeletonCard } from '../Skeleton/Skeleton';\nimport { TableProvider } from '../TableProvider/TableProvider';\nimport type { ColumnConfig } from '../TableProvider/TableProvider';\nimport { calculateYAxisTicks } from '../shared/ChartComponents';\nimport { formatAxisTick } from '../../utils/chartUtils';\nimport { cn } from '../../utils/utils';\nimport type { ActivitiesQuestionsData, SubtopicPerformanceItem } from './types';\n\n/**\n * The four bars, in the order, colours and wording of the design: `label`\n * sits under the bar, `legendLabel` heads the card on the right.\n */\nconst BARS = [\n {\n key: 'total',\n label: 'Total',\n legendLabel: 'Total de questões respondidas',\n color: 'bg-info-600',\n },\n {\n key: 'corretas',\n label: 'Corretas',\n legendLabel: 'Questões corretas',\n color: 'bg-success-200',\n },\n {\n key: 'incorretas',\n label: 'Incorretas',\n legendLabel: 'Questões incorretas',\n color: 'bg-warning-400',\n },\n {\n key: 'emBranco',\n label: 'Em branco',\n legendLabel: 'Questões em branco',\n color: 'bg-background-200',\n },\n] as const;\n\ntype BarKey = (typeof BARS)[number]['key'];\n\n/** What each of the four bars counts. */\nexport type QuestionsBarsValues = Record<BarKey, number>;\n\nconst CHART_HEIGHT = 235;\n\n/** Sentinel of the tema select meaning \"every tema of the subject\". */\nconst ALL_TOPICS = 'all';\n\n/** Subtemas shown before \"Mostrar todos\". */\nconst COLLAPSED_ROWS = 4;\n\n/**\n * Share of `value` in `total`, as the whole percentage the cards print.\n * A total of zero reads as 0% rather than NaN.\n */\nfunction shareOfTotal(value: number, total: number): number {\n return total > 0 ? Math.round((value / total) * 100) : 0;\n}\n\n/** Hit rate as the table prints it: one decimal, pt-BR. */\nfunction formatRate(value: number): string {\n return `${value.toLocaleString('pt-BR', {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n })}%`;\n}\n\n/** One legend card: coloured dot and label, the count, its share of the total. */\nfunction LegendCard({\n color,\n label,\n value,\n percentage,\n}: {\n readonly color: string;\n readonly label: string;\n readonly value: number;\n readonly percentage: number;\n}) {\n return (\n <div className=\"flex flex-col justify-center gap-1 rounded-xl px-6 py-4\">\n <div className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className={cn('size-2 shrink-0 rounded-full', color)}\n />\n <Text size=\"sm\" weight=\"medium\" className=\"text-text-600\">\n {label}\n </Text>\n </div>\n <Text size=\"4xl\" weight=\"bold\" className=\"text-info-700\">\n {value.toLocaleString('pt-BR')}\n </Text>\n <Text size=\"xs\" weight=\"medium\" className=\"text-text-800\">\n {`${percentage}% do total`}\n </Text>\n </div>\n );\n}\n\nexport interface QuestionsBarsProps {\n readonly values: QuestionsBarsValues;\n /**\n * Shown under the legend cards — the \"Nota média\" of a report that has one\n * (the Momento ENEM report of the gestor app).\n */\n readonly aside?: ReactNode;\n}\n\n/**\n * The bars with their axes, plus the four legend cards on the right.\n *\n * Exported for the reports that pair the same chart with a select and a\n * table of their own, not a subject's subtemas.\n */\nexport function QuestionsBars({ values, aside }: QuestionsBarsProps) {\n const [hoveredKey, setHoveredKey] = useState<string | null>(null);\n const legendId = useId();\n\n const ticks = calculateYAxisTicks(values.total);\n const chartMax = ticks[0];\n\n return (\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-stretch\">\n {/* Chart */}\n {/* As barras são divs sem semântica e os cartões de legenda ao lado já\n trazem cada número por extenso: o gráfico vira uma imagem nomeada e\n descrita pelos próprios cartões, sem repetir os dados numa tabela. */}\n <div\n role=\"img\"\n aria-label=\"Gráfico de barras das questões\"\n aria-describedby={legendId}\n className=\"flex min-w-0 flex-1 flex-col gap-2\"\n >\n <div className=\"flex flex-row\">\n {/* Y axis */}\n {/* `min-w-12` e não `w-12`: a largura fixa cortava o rótulo assim\n que a rede passou de um milhão de respostas. O formato compacto\n resolve a leitura; o mínimo preserva o desenho dos gráficos que\n contam centenas. */}\n <div\n className=\"flex min-w-12 shrink-0 flex-col items-end justify-between pr-2\"\n style={{ height: CHART_HEIGHT }}\n aria-hidden=\"true\"\n >\n {ticks.map((tick) => (\n <Text\n key={tick}\n size=\"md\"\n className=\"whitespace-nowrap text-text-700\"\n >\n {formatAxisTick(tick)}\n </Text>\n ))}\n </div>\n\n <div className=\"relative flex-1\">\n {/* Grid lines */}\n <div\n className=\"pointer-events-none absolute inset-0 flex flex-col justify-between\"\n style={{ height: CHART_HEIGHT }}\n >\n {ticks.map((tick) => (\n <div\n key={tick}\n className=\"border-t border-dashed border-border-400\"\n />\n ))}\n </div>\n\n <div\n className=\"relative z-10 flex flex-row items-end gap-4\"\n style={{ height: CHART_HEIGHT }}\n >\n {BARS.map((bar) => {\n const value = values[bar.key];\n const isHovered = hoveredKey === bar.key;\n const anyHovered = hoveredKey !== null;\n const height =\n chartMax === 0 ? 0 : (value / chartMax) * CHART_HEIGHT;\n\n return (\n <div\n key={bar.key}\n className=\"relative flex h-full flex-1 flex-col items-center justify-end\"\n onMouseEnter={() => setHoveredKey(bar.key)}\n onMouseLeave={() => setHoveredKey(null)}\n >\n {isHovered && value > 0 && (\n <div\n role=\"tooltip\"\n className=\"absolute z-10 whitespace-nowrap rounded bg-text-950 px-2 py-1 text-xs text-white shadow-lg\"\n style={{ bottom: `${height + 8}px` }}\n >\n {value.toLocaleString('pt-BR')}\n <div className=\"absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-text-950\" />\n </div>\n )}\n <div\n data-testid={`questions-bar-${bar.key}`}\n className={cn(\n 'w-full max-w-20 cursor-pointer rounded transition-all duration-300',\n bar.color\n )}\n style={{\n height: `${height}px`,\n minHeight: value > 0 ? '4px' : '0',\n opacity: !anyHovered || isHovered ? 1 : 0.5,\n }}\n />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n\n {/* X axis labels, aligned to the bars (ml-12 matches the Y axis width) */}\n <div className=\"ml-12 flex flex-row gap-4\" aria-hidden=\"true\">\n {BARS.map((bar) => (\n <div key={bar.key} className=\"flex flex-1 justify-center\">\n <Text size=\"md\" className=\"text-text-700\">\n {bar.label}\n </Text>\n </div>\n ))}\n </div>\n </div>\n\n <div aria-hidden=\"true\" className=\"hidden w-px bg-border-200 lg:block\" />\n\n {/* Legend cards, and whatever the caller adds under them */}\n <div className=\"flex flex-1 flex-col justify-center gap-2\">\n <div\n id={legendId}\n data-testid=\"questions-legend\"\n className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\"\n >\n {BARS.map((bar) => (\n <LegendCard\n key={bar.key}\n color={bar.color}\n label={bar.legendLabel}\n value={values[bar.key]}\n percentage={shareOfTotal(values[bar.key], values.total)}\n />\n ))}\n </div>\n {aside}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Subtemas (one subject selected)\n// ---------------------------------------------------------------------------\n\n/** Row shape of the subtema table; `rowId` is what `rowKey` addresses. */\ninterface SubtopicRow {\n rowId: string;\n name: string;\n total: number;\n correct: number;\n incorrect: number;\n blank: number;\n rate: number;\n /** Index signature for TableProvider compatibility */\n [key: string]: unknown;\n}\n\ntype SubtopicSortKey = 'name' | 'correct' | 'incorrect' | 'blank' | 'rate';\n\n/**\n * Taxa de acerto cell: the rate (0–100) over a green bar. Exported for the\n * other tables that print a rate the same way.\n *\n * `className` exists for the table that cannot afford the default 128px. In\n * auto table layout a fixed-width cell is the floor of its column, so this one\n * number decides how narrow \"Taxa de acerto\" can ever get — it was 144px of the\n * students table, which stopped fitting its card.\n */\nexport function RateCell({\n rate,\n className,\n}: {\n readonly rate: number;\n readonly className?: string;\n}) {\n return (\n <div className={cn('flex w-32 flex-col gap-1', className)}>\n <Text size=\"xs\" weight=\"bold\" className=\"text-success-500\">\n {formatRate(rate)}\n </Text>\n {/* O percentual já está escrito acima: a barra é só reforço visual e\n não deve ser lida de novo pelo leitor de tela. */}\n <ProgressBar value={rate} variant=\"green\" size=\"small\" decorative />\n </div>\n );\n}\n\nconst SUBTOPIC_COLUMNS: ColumnConfig<SubtopicRow>[] = [\n {\n key: 'name',\n label: 'Subtema',\n sortable: true,\n className: 'min-w-[240px]',\n },\n { key: 'total', label: 'Total', sortable: false },\n { key: 'correct', label: 'Corretas', sortable: true },\n { key: 'incorrect', label: 'Incorretas', sortable: true },\n { key: 'blank', label: 'Em branco', sortable: true },\n {\n key: 'rate',\n label: 'Taxa de acerto',\n sortable: true,\n render: (_value: unknown, row: SubtopicRow) => <RateCell rate={row.rate} />,\n },\n];\n\n/**\n * Map a subtema onto a table row.\n *\n * Every number arrives ready: the endpoint counts the blanks by answer status\n * and computes the rate over the total, so nothing is derived here.\n */\nfunction toSubtopicRow(subtopic: SubtopicPerformanceItem): SubtopicRow {\n return {\n rowId: subtopic.subtopicId,\n name: subtopic.subtopicName,\n total: subtopic.total,\n correct: subtopic.correct,\n incorrect: subtopic.incorrect,\n blank: subtopic.blank,\n rate: subtopic.correctPercentage,\n };\n}\n\n/** Sort rows by one column, names in pt-BR order. */\nfunction sortRows(\n rows: SubtopicRow[],\n key: SubtopicSortKey,\n order: 'asc' | 'desc'\n): SubtopicRow[] {\n const direction = order === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const result =\n key === 'name'\n ? a.name.localeCompare(b.name, 'pt-BR')\n : Number(a[key]) - Number(b[key]);\n return result * direction;\n });\n}\n\n/**\n * \"Desempenho por subtema\": the subtemas of the selected subject (and tema),\n * searchable and sortable, collapsed to four rows behind \"Mostrar todos\".\n */\nfunction SubtopicsTable({\n subtopics,\n loading,\n error,\n}: {\n readonly subtopics: SubtopicPerformanceItem[];\n readonly loading: boolean;\n readonly error: string | null;\n}) {\n const [search, setSearch] = useState('');\n const [sort, setSort] = useState<{\n key: SubtopicSortKey;\n order: 'asc' | 'desc';\n }>({ key: 'rate', order: 'asc' });\n const [expanded, setExpanded] = useState(false);\n\n const rows = useMemo(() => {\n const term = search.trim().toLocaleLowerCase('pt-BR');\n const matching = subtopics\n .map(toSubtopicRow)\n .filter(\n (row) => !term || row.name.toLocaleLowerCase('pt-BR').includes(term)\n );\n return sortRows(matching, sort.key, sort.order);\n }, [subtopics, search, sort]);\n\n const visibleRows = expanded ? rows : rows.slice(0, COLLAPSED_ROWS);\n const hasMore = rows.length > COLLAPSED_ROWS;\n\n // Search and sort arrive through the table params; sorting is done here\n // (server mode) so it applies to the whole list before the collapse cut,\n // not only to the four rows on screen.\n const handleParamsChange = (params: Record<string, unknown>) => {\n setSearch((params.search as string) ?? '');\n const sortBy = params.sortBy as SubtopicSortKey | undefined;\n if (sortBy) {\n setSort({\n key: sortBy,\n order: (params.sortOrder as 'asc' | 'desc') ?? 'asc',\n });\n }\n };\n\n if (loading) {\n return <SkeletonCard className=\"min-h-[240px]\" />;\n }\n\n if (error) {\n return (\n <Text size=\"sm\" className=\"text-text-500\">\n {error}\n </Text>\n );\n }\n\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <TableProvider<SubtopicRow>\n data={visibleRows}\n headers={SUBTOPIC_COLUMNS}\n rowKey=\"rowId\"\n enableSearch\n enableTableSort\n sortMode=\"server\"\n onParamsChange={handleParamsChange}\n searchPlaceholder=\"Buscar subtema\"\n headerContent={\n <div className=\"flex flex-col gap-2\">\n <Text as=\"h4\" size=\"lg\" weight=\"bold\" className=\"text-text-950\">\n Desempenho por subtema\n </Text>\n <Badge\n variant=\"solid\"\n action=\"info\"\n size=\"small\"\n className=\"self-start uppercase\"\n >\n {`${subtopics.length} subtemas totais`}\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} subtemas`}\n {expanded ? <CaretUpIcon size={18} /> : <CaretDownIcon size={18} />}\n </Button>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/**\n * \"Dados gerais de questões\" — the bars on the left, a divider, and one card\n * per bar on the right with the count and its share of the total.\n *\n * With one componente curricular selected the card grows: a tema select joins\n * the title, and the subject's subtemas are listed below the chart. Picking a\n * tema narrows both the table and the bars, which are then summed from the\n * subtemas of that tema (the questions endpoint has no tema cut).\n *\n * Written apart from `QuestionsData` because that card shows the number\n * nowhere: switching componente curricular between two subjects with 15 and 14\n * correct answers moved one bar by ten pixels and nothing else, which read as a\n * frozen screen. The hover tooltip follows `VerticalBarChart` so the two charts\n * of the report behave alike.\n */\nexport interface QuestionsPerformanceCardProps {\n readonly data: ActivitiesQuestionsData;\n /** Subtemas of the selected subject; null while \"Todos\" is selected. */\n readonly subtopics?: SubtopicPerformanceItem[] | null;\n readonly subtopicsLoading?: boolean;\n readonly subtopicsError?: string | null;\n /**\n * Card heading. Left out, it follows the cut on screen: \"Dados gerais de\n * questões\" for the whole report, \"Dados de questões\" once a componente\n * curricular narrows it — the numbers stop being the report's totals and\n * become that subject's.\n */\n readonly title?: string;\n}\n\nexport function QuestionsPerformanceCard({\n data,\n subtopics = null,\n subtopicsLoading = false,\n subtopicsError = null,\n title,\n}: QuestionsPerformanceCardProps) {\n const [topicId, setTopicId] = useState(ALL_TOPICS);\n\n const subjectSelected = subtopics !== null;\n\n const cardTitle =\n title ??\n (subjectSelected ? 'Dados de questões' : 'Dados gerais de questões');\n\n const topics = useMemo(() => {\n const byId = new Map<string, string>();\n for (const subtopic of subtopics ?? []) {\n byId.set(subtopic.topic.id, subtopic.topic.name);\n }\n return [...byId.entries()]\n .map(([id, name]) => ({ id, name }))\n .sort((a, b) => a.name.localeCompare(b.name, 'pt-BR'));\n }, [subtopics]);\n\n // A tema that vanished with a new subject falls back to \"all\" on read.\n const activeTopicId = topics.some((topic) => topic.id === topicId)\n ? topicId\n : ALL_TOPICS;\n\n const filteredSubtopics = useMemo(\n () =>\n activeTopicId === ALL_TOPICS\n ? (subtopics ?? [])\n : (subtopics ?? []).filter(\n (subtopic) => subtopic.topic.id === activeTopicId\n ),\n [subtopics, activeTopicId]\n );\n\n const values = useMemo<QuestionsBarsValues>(() => {\n if (activeTopicId === ALL_TOPICS) {\n return {\n total: data.totalAnswered,\n corretas: data.correctAnswers,\n incorretas: data.incorrectAnswers,\n emBranco: data.blankAnswers,\n };\n }\n const rows = filteredSubtopics.map(toSubtopicRow);\n const sum = (pick: (row: SubtopicRow) => number) =>\n rows.reduce((acc, row) => acc + pick(row), 0);\n return {\n total: sum((row) => row.total),\n corretas: sum((row) => row.correct),\n incorretas: sum((row) => row.incorrect),\n emBranco: sum((row) => row.blank),\n };\n }, [data, filteredSubtopics, activeTopicId]);\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 {cardTitle}\n </Text>\n {subjectSelected && topics.length > 0 && (\n <Select\n value={activeTopicId}\n onValueChange={setTopicId}\n className=\"w-[380px] max-w-full\"\n >\n <SelectTrigger className=\"w-full\" aria-label=\"Selecionar tema\">\n <SelectValue placeholder=\"Todos os temas\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_TOPICS}>Todos os temas</SelectItem>\n {topics.map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n\n <QuestionsBars values={values} />\n\n {subjectSelected && (\n <>\n <div aria-hidden=\"true\" className=\"h-px w-full bg-border-200\" />\n <SubtopicsTable\n subtopics={filteredSubtopics}\n loading={subtopicsLoading}\n error={subtopicsError}\n />\n </>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-RV6HA5CB.mjs";
|
|
10
10
|
import {
|
|
11
11
|
ReportDetailModal_default
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RNZGX6MX.mjs";
|
|
13
13
|
import {
|
|
14
14
|
MetricBox
|
|
15
15
|
} from "./chunk-TH4XUMR5.mjs";
|
|
@@ -375,4 +375,4 @@ var StudentPerformanceDetailsModal = ({
|
|
|
375
375
|
export {
|
|
376
376
|
StudentPerformanceDetailsModal
|
|
377
377
|
};
|
|
378
|
-
//# sourceMappingURL=chunk-
|
|
378
|
+
//# sourceMappingURL=chunk-3Q7RJKUP.mjs.map
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
} from "./chunk-GNLZTQRR.mjs";
|
|
7
7
|
import {
|
|
8
8
|
CalendarCard_default
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2OJ3QSLU.mjs";
|
|
10
10
|
import {
|
|
11
11
|
NotificationCard_default
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ANFHLSDB.mjs";
|
|
13
13
|
import {
|
|
14
14
|
useMobile
|
|
15
15
|
} from "./chunk-IG4GK7ME.mjs";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
ProfileMenuInfo,
|
|
30
30
|
ProfileMenuSection,
|
|
31
31
|
ProfileToggleTheme
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-7ZWEBNFB.mjs";
|
|
33
33
|
import {
|
|
34
34
|
Text_default
|
|
35
35
|
} from "./chunk-IMCIR6TJ.mjs";
|
|
@@ -307,4 +307,4 @@ export {
|
|
|
307
307
|
AppHeader,
|
|
308
308
|
AppHeader_default
|
|
309
309
|
};
|
|
310
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-3WRB4WCD.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkKM27ZDHZjs = require('./chunk-KM27ZDHZ.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var _chunkANT66KVKjs = require('./chunk-ANT66KVK.js');
|
|
@@ -99,7 +99,7 @@ var AnswerRow = ({
|
|
|
99
99
|
)
|
|
100
100
|
] });
|
|
101
101
|
};
|
|
102
|
-
var AnswerKeyModal = ({ answers, onClose }) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
102
|
+
var AnswerKeyModal = ({ answers, onClose }) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKM27ZDHZjs.Modal_default, { isOpen: true, onClose, title: "Gabarito", size: "xl", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-3", children: [
|
|
103
103
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", weight: "bold", color: "text-text-950", children: "Respostas" }),
|
|
104
104
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "grid gap-3 md:grid-cols-2 lg:grid-cols-3", children: answers.map((answer, index) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
105
105
|
AnswerRow,
|
|
@@ -115,4 +115,4 @@ var AnswerKeyModal_default = AnswerKeyModal;
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
exports.AnswerKeyModal_default = AnswerKeyModal_default;
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-46VHAL4S.js.map
|