analytica-frontend-lib 1.6.19 → 1.6.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActivitiesHistory/index.js +9 -9
- package/dist/ActivitiesHistory/index.mjs +8 -8
- package/dist/ActivityCardQuestionBanks/index.js +9 -9
- package/dist/ActivityCardQuestionBanks/index.mjs +8 -8
- package/dist/ActivityCardQuestionPreview/index.js +9 -9
- package/dist/ActivityCardQuestionPreview/index.mjs +8 -8
- package/dist/ActivityCreate/ActivityCreateContent/index.js +9 -9
- package/dist/ActivityCreate/ActivityCreateContent/index.mjs +8 -8
- package/dist/ActivityCreate/index.js +9 -9
- package/dist/ActivityCreate/index.mjs +8 -8
- package/dist/ActivityDetails/index.js +9 -9
- package/dist/ActivityDetails/index.mjs +8 -8
- package/dist/ActivityFilters/index.js +9 -9
- package/dist/ActivityFilters/index.mjs +8 -8
- package/dist/ActivityPageLayout/index.js +9 -9
- package/dist/ActivityPageLayout/index.mjs +8 -8
- package/dist/ActivityPreview/index.js +9 -9
- package/dist/ActivityPreview/index.mjs +8 -8
- package/dist/AlertManager/index.js +9 -9
- package/dist/AlertManager/index.mjs +8 -8
- package/dist/AppHeader/index.d.ts.map +1 -1
- package/dist/AppHeader/index.js +5 -5
- package/dist/AppHeader/index.mjs +4 -4
- package/dist/CalendarCard/index.js +3 -3
- package/dist/CalendarCard/index.mjs +2 -2
- package/dist/CheckBoxGroup/index.js +9 -9
- package/dist/CheckBoxGroup/index.mjs +8 -8
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +2 -2
- package/dist/DropdownMenu/index.mjs +1 -1
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +9 -9
- package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +8 -8
- package/dist/ExamPageLayout/index.js +9 -9
- package/dist/ExamPageLayout/index.mjs +8 -8
- package/dist/Forum/index.js +3 -3
- package/dist/Forum/index.mjs +2 -2
- package/dist/NotificationCard/index.js +3 -3
- package/dist/NotificationCard/index.mjs +2 -2
- package/dist/PrintableUsersTable/index.js +9 -9
- package/dist/PrintableUsersTable/index.mjs +8 -8
- package/dist/QuestionsPerformanceCard/index.js +9 -9
- package/dist/QuestionsPerformanceCard/index.mjs +8 -8
- package/dist/RecommendedLessonCreate/index.js +9 -9
- package/dist/RecommendedLessonCreate/index.mjs +8 -8
- package/dist/RecommendedLessonsHistory/index.js +9 -9
- package/dist/RecommendedLessonsHistory/index.mjs +8 -8
- package/dist/Search/index.js +3 -3
- package/dist/Search/index.mjs +2 -2
- package/dist/SendActivityModal/SendActivityModal.js +9 -9
- package/dist/SendActivityModal/SendActivityModal.mjs +8 -8
- package/dist/SendActivityModal/index.js +9 -9
- package/dist/SendActivityModal/index.mjs +8 -8
- package/dist/SendLessonModal/index.js +9 -9
- package/dist/SendLessonModal/index.mjs +8 -8
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +9 -9
- package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +8 -8
- package/dist/SimulatedStudentSimulationsModal/index.js +15 -15
- package/dist/SimulatedStudentSimulationsModal/index.mjs +1 -1
- package/dist/SimulationsPage/index.js +9 -9
- package/dist/SimulationsPage/index.mjs +8 -8
- package/dist/TableProvider/index.js +9 -9
- package/dist/TableProvider/index.mjs +8 -8
- package/dist/UnifiedDraftModelPage/index.js +9 -9
- package/dist/UnifiedDraftModelPage/index.mjs +8 -8
- package/dist/UnifiedHistoryPage/index.js +9 -9
- package/dist/UnifiedHistoryPage/index.mjs +8 -8
- package/dist/{chunk-OMNDC4BZ.js → chunk-7XWWI5EY.js} +25 -28
- package/dist/chunk-7XWWI5EY.js.map +1 -0
- package/dist/{chunk-6LJZGFSL.mjs → chunk-CFTQ24UT.mjs} +2 -2
- package/dist/{chunk-LLUJLIDC.mjs → chunk-CKSOC6YI.mjs} +5 -4
- package/dist/{chunk-LLUJLIDC.mjs.map → chunk-CKSOC6YI.mjs.map} +1 -1
- package/dist/{chunk-DXSNLRL4.mjs → chunk-CWGYVJTQ.mjs} +25 -28
- package/dist/chunk-CWGYVJTQ.mjs.map +1 -0
- package/dist/{chunk-OYEDIVC2.js → chunk-CWTCN6F7.js} +5 -5
- package/dist/{chunk-OYEDIVC2.js.map → chunk-CWTCN6F7.js.map} +1 -1
- package/dist/{chunk-QKRUOG3N.mjs → chunk-E4MLDRLD.mjs} +2 -2
- package/dist/{chunk-GP47TASX.mjs → chunk-ENCFJBY5.mjs} +2 -2
- package/dist/{chunk-FBA6QDJM.js → chunk-IUH7JW7H.js} +16 -15
- package/dist/chunk-IUH7JW7H.js.map +1 -0
- package/dist/{chunk-UEUGH2QZ.js → chunk-KYGFYAU7.js} +7 -7
- package/dist/{chunk-UEUGH2QZ.js.map → chunk-KYGFYAU7.js.map} +1 -1
- package/dist/{chunk-T3V5QTJZ.mjs → chunk-MUVQIQAG.mjs} +2 -2
- package/dist/{chunk-7HW4PZLV.js → chunk-MYQ2LJWO.js} +6 -6
- package/dist/{chunk-7HW4PZLV.js.map → chunk-MYQ2LJWO.js.map} +1 -1
- package/dist/{chunk-K2ILHIT6.mjs → chunk-NAOHJW6H.mjs} +4 -4
- package/dist/{chunk-S4CNM57F.js → chunk-QHXJB5QM.js} +10 -10
- package/dist/{chunk-S4CNM57F.js.map → chunk-QHXJB5QM.js.map} +1 -1
- package/dist/{chunk-IXZT6MKP.js → chunk-QN32O3J3.js} +19 -19
- package/dist/{chunk-IXZT6MKP.js.map → chunk-QN32O3J3.js.map} +1 -1
- package/dist/{chunk-PTFHHHQG.mjs → chunk-X2YQT5BS.mjs} +22 -7
- package/dist/chunk-X2YQT5BS.mjs.map +1 -0
- package/dist/{chunk-M5BZW7BG.js → chunk-Y64LT6BL.js} +22 -7
- package/dist/chunk-Y64LT6BL.js.map +1 -0
- package/dist/components/SimulationsPage/SimulationsDetailModal.d.ts.map +1 -1
- package/dist/hooks/useAppContent/index.js +9 -9
- package/dist/hooks/useAppContent/index.mjs +8 -8
- package/dist/index.js +9 -9
- package/dist/index.mjs +8 -8
- package/dist/utils/questionDuration.d.ts +14 -12
- package/dist/utils/questionDuration.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DXSNLRL4.mjs.map +0 -1
- package/dist/chunk-FBA6QDJM.js.map +0 -1
- package/dist/chunk-M5BZW7BG.js.map +0 -1
- package/dist/chunk-OMNDC4BZ.js.map +0 -1
- package/dist/chunk-PTFHHHQG.mjs.map +0 -1
- /package/dist/{chunk-6LJZGFSL.mjs.map → chunk-CFTQ24UT.mjs.map} +0 -0
- /package/dist/{chunk-QKRUOG3N.mjs.map → chunk-E4MLDRLD.mjs.map} +0 -0
- /package/dist/{chunk-GP47TASX.mjs.map → chunk-ENCFJBY5.mjs.map} +0 -0
- /package/dist/{chunk-T3V5QTJZ.mjs.map → chunk-MUVQIQAG.mjs.map} +0 -0
- /package/dist/{chunk-K2ILHIT6.mjs.map → chunk-NAOHJW6H.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-IUH7JW7H.js","../src/components/AppHeader/AppHeader.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-IUH7JW7H.js","sourcesContent":[null,"import type { ReactNode } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { CaretRightIcon } from '@phosphor-icons/react/dist/csr/CaretRight';\nimport { UserIcon as UserPhosphorIcon } from '@phosphor-icons/react/dist/csr/User';\n\nimport { BrandingLogo } from '../BrandingLogo/BrandingLogo';\nimport CalendarCard from '../CalendarCard/CalendarCard';\nimport IconButton from '../IconButton/IconButton';\nimport DropdownMenu, {\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n ProfileMenuFooter,\n ProfileMenuHeader,\n ProfileMenuInfo,\n ProfileMenuSection,\n ProfileToggleTheme,\n} from '../DropdownMenu/DropdownMenu';\nimport NotificationCard, {\n type NotificationGroup,\n} from '../NotificationCard/NotificationCard';\nimport { PageContainer } from '../PageContainer/PageContainer';\nimport Text from '../Text/Text';\nimport { UserIcon } from '../UserIcon/UserIcon';\nimport { useMobile } from '../../hooks/useMobile';\nimport { syncDropdownState } from '../../utils/dropdown';\nimport {\n NotificationEntityType,\n NotificationEntityStatus,\n} from '../../types/notifications';\n\n/**\n * Subset of session info consumed by the application header.\n * Each property is optional so the consumer can pass whatever it has.\n */\nexport interface AppHeaderSessionInfo {\n userName?: string;\n email?: string;\n urlProfilePicture?: string;\n schoolName?: string;\n className?: string;\n schoolYearName?: string;\n}\n\n/**\n * Minimal user shape needed to fill the profile dropdown header.\n */\nexport interface AppHeaderUser {\n name?: string;\n email?: string;\n}\n\n/**\n * Tutorial action rendered as a pill button in the header (left of the\n * notifications bell). The consumer owns the click behavior (usually opening\n * the configured tutorial URL in a new tab).\n */\nexport interface AppHeaderTutorial {\n /** Show the tutorial button. */\n visible?: boolean;\n /** Button label. Defaults to \"Tutoriais\". */\n label?: string;\n /** Fired when the tutorial button is clicked. */\n onClick: () => void;\n}\n\n/**\n * Notifications data + callbacks the AppHeader forwards to NotificationCard.\n * Built by the consumer (usually via `createNotificationsHook(api)`).\n */\nexport interface AppHeaderNotifications {\n unreadCount: number;\n loading: boolean;\n error: string | null;\n groupedNotifications: NotificationGroup[];\n refreshNotifications: () => void;\n markAsRead: (id: string) => void;\n deleteNotification: (id: string) => void;\n fetchNotifications: () => void;\n fetchUnreadCount?: () => void;\n /**\n * Optional bulk action. When provided, the \"Marcar todas como lidas\" button\n * renders in the notification center; when omitted, it stays hidden, which is\n * the behaviour every consumer had before this prop existed.\n */\n markAllAsRead?: () => void;\n getActionLabel: (entityType?: NotificationEntityType) => string | undefined;\n}\n\nexport interface AppHeaderProps {\n /** Authenticated user (fallback when sessionInfo doesn't carry name/email). */\n user?: AppHeaderUser;\n /** Session metadata used to render profile name, email, photo and school info. */\n sessionInfo?: AppHeaderSessionInfo;\n /** Notifications wiring (state + callbacks). */\n notifications: AppHeaderNotifications;\n /** Tutorial pill button shown before the notifications bell. */\n tutorial?: AppHeaderTutorial;\n /** Show the calendar widget in the header. */\n showCalendar?: boolean;\n /** Content rendered inside the calendar (dropdown on tablet/desktop, modal on mobile). */\n calendarContent?: ReactNode;\n /**\n * Optional controlled open state for the calendar.\n * When provided, the consumer is the source of truth; useful for closing\n * the calendar programmatically (e.g. on route change).\n */\n calendarOpen?: boolean;\n /** Fired whenever the calendar open state changes. */\n onCalendarOpenChange?: (open: boolean) => void;\n /** Show school/class/year inside the profile dropdown. */\n showProfileInfo?: boolean;\n /** Optional fallback image for the notifications empty state. */\n emptyNotificationsImage?: string;\n /** Empty-state title for notifications dropdown. */\n emptyNotificationsTitle?: string;\n /** Empty-state description for notifications dropdown. */\n emptyNotificationsDescription?: string;\n /** Callback fired when the user activates logout. */\n onLogout: () => void;\n /** Callback fired when the user activates the \"My data\" menu item. */\n onNavigateToMyData: () => void;\n /** Callback fired when the user clicks on a notification with entity info. */\n onNavigateByNotification?: (\n entityType?: NotificationEntityType,\n entityId?: string,\n entityStatus?: NotificationEntityStatus | null,\n questionId?: string | null\n ) => void;\n /**\n * Tailwind class overriding the default `max-w-[1000px]` of the header\n * content (forwarded to the internal `PageContainer`'s `innerClassName`).\n * Pass e.g. `max-w-[1350px]` to align the header with a widened page\n * content. When omitted, the default `max-w-[1000px]` is kept, so other\n * consumers are unaffected.\n */\n contentMaxWidth?: string;\n}\n\nconst DEFAULT_EMPTY_TITLE = 'Nenhuma notificação no momento';\nconst DEFAULT_EMPTY_DESCRIPTION =\n 'Você está em dia com todas as novidades. Volte depois para conferir atualizações!';\n\n/**\n * Application header shared across the analytica frontends.\n *\n * Renders the institution logo, optional calendar dropdown (mobile/tablet),\n * notifications center and a profile menu. Width is constrained by\n * `<PageContainer>` so it always aligns with the rest of the page content.\n */\nexport const AppHeader = ({\n user,\n sessionInfo,\n notifications,\n tutorial,\n showCalendar = false,\n calendarContent,\n calendarOpen,\n onCalendarOpenChange,\n showProfileInfo = false,\n emptyNotificationsImage,\n emptyNotificationsTitle = DEFAULT_EMPTY_TITLE,\n emptyNotificationsDescription = DEFAULT_EMPTY_DESCRIPTION,\n onLogout,\n onNavigateToMyData,\n onNavigateByNotification,\n contentMaxWidth,\n}: AppHeaderProps) => {\n const { isMobile, isExtraSmallMobile } = useMobile();\n const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n notifications.fetchUnreadCount?.();\n }, []);\n\n const toggleActive = (buttonId: string) => {\n setActiveStates((prev) => {\n if (prev[buttonId]) {\n return { ...prev, [buttonId]: false };\n }\n return {\n calendar: false,\n notifications: false,\n profile: false,\n [buttonId]: true,\n };\n });\n };\n\n // Stable callback so `DropdownMenu`'s `useEffect [open, onOpenChange]`\n // doesn't re-fire on every parent re-render, which would cascade into a\n // Maximum-update-depth loop (same root cause class as PR #435).\n const handleCalendarOpenChange = useCallback(\n (open: boolean) => {\n setActiveStates((prev) => {\n const current = prev.calendar ?? false;\n if (current === open) {\n return prev;\n }\n if (open) {\n return {\n calendar: true,\n notifications: false,\n profile: false,\n };\n }\n return { ...prev, calendar: false };\n });\n onCalendarOpenChange?.(open);\n },\n [onCalendarOpenChange]\n );\n\n useEffect(() => {\n const handleEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setActiveStates({});\n }\n };\n document.addEventListener('keydown', handleEscapeKey);\n return () => document.removeEventListener('keydown', handleEscapeKey);\n }, []);\n\n const handleMyDataClick = () => {\n setActiveStates({});\n onNavigateToMyData();\n };\n\n const displayName = sessionInfo?.userName ?? user?.name ?? 'Usuário';\n const displayEmail = sessionInfo?.email ?? user?.email ?? '';\n\n const pickResponsiveClass = (\n extraSmall: string,\n mobile: string,\n desktop: string\n ): string => {\n if (isExtraSmallMobile) return extraSmall;\n if (isMobile) return mobile;\n return desktop;\n };\n const profileDropdownWidth = pickResponsiveClass(\n 'min-w-[260px]',\n 'min-w-[280px]',\n 'min-w-[320px]'\n );\n const sectionGap = pickResponsiveClass('gap-1', 'gap-2', 'gap-3');\n const logoHeight = pickResponsiveClass('h-6', 'h-8', 'h-10');\n const tutorialPadding = pickResponsiveClass(\n 'px-3 py-1.5 text-xs',\n 'px-3.5 py-2 text-sm',\n 'px-4 py-2 text-sm'\n );\n\n return (\n <header\n data-component=\"AppHeader\"\n className=\"bg-primary-800 w-full h-[70px] flex justify-center items-center\"\n >\n <PageContainer\n className=\"pb-0 justify-center\"\n innerClassName={contentMaxWidth}\n >\n <div className=\"w-full flex flex-row justify-between items-center gap-3\">\n {/* `min-w-0 shrink` lets the logo compress instead of overflowing:\n an <img> resolves `min-width:auto` to its intrinsic width, which\n would otherwise block flex-shrink and push the actions off-screen\n on narrow viewports. `object-left` keeps it left-aligned as it\n scales down. The actions never shrink. */}\n <BrandingLogo\n variant=\"main\"\n alt=\"Logo\"\n className={`${logoHeight} object-contain object-left min-w-0 shrink`}\n />\n <section\n className={`flex flex-row items-center shrink-0 ${sectionGap}`}\n >\n {tutorial?.visible && (\n <button\n type=\"button\"\n data-testid=\"app-header-tutorial\"\n onClick={tutorial.onClick}\n /* `primary` (não `text`) é o foreground de `primary-800` em\n todos os temas — os dois formam um par contrastante mesmo\n onde o dark inverte o header (paraiba/analytica). Usar\n `text-*` aqui inverteria com o modo enquanto o fundo do\n header não inverte, apagando o botão no dark. */\n className={`rounded-full border border-primary text-primary font-medium whitespace-nowrap cursor-pointer transition-colors hover:bg-primary-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${tutorialPadding}`}\n >\n {tutorial.label ?? 'Tutoriais'}\n </button>\n )}\n\n {showCalendar && (\n <div className=\"lg:hidden\">\n <CalendarCard\n content={calendarContent}\n isOpen={calendarOpen ?? activeStates.calendar ?? false}\n onOpenChange={handleCalendarOpenChange}\n />\n </div>\n )}\n\n <NotificationCard\n mode=\"center\"\n isActive={activeStates.notifications}\n onToggleActive={() => toggleActive('notifications')}\n onOpenChange={(open: boolean) => {\n syncDropdownState(\n open,\n activeStates.notifications,\n setActiveStates,\n 'notifications'\n );\n }}\n unreadCount={notifications.unreadCount}\n groupedNotifications={notifications.groupedNotifications}\n loading={notifications.loading}\n error={notifications.error}\n onRetry={notifications.refreshNotifications}\n onMarkAsReadById={notifications.markAsRead}\n onMarkAllAsRead={notifications.markAllAsRead}\n onDeleteById={notifications.deleteNotification}\n onNavigateById={(\n entityType,\n entityId,\n entityStatus,\n questionId\n ) => {\n if (entityType && entityId) {\n // Fecha o painel antes de navegar. Sem isso, quando o destino\n // é a própria página em que o usuário já está (ex.: ele\n // termina a atividade, cai no resultado, abre o sino e clica\n // em \"Ver resultado\" da notificação dessa mesma atividade),\n // o navigate para a rota atual não muda nada na tela, o\n // painel segue aberto e ele clica de novo — rage click\n // FRONTEND-ALUNO-WEB-F3, 91 ocorrências / 36 usuários em seis\n // semanas. Fechar o painel é o feedback visível que faltava,\n // e vale para qualquer destino.\n //\n // É um setState em handler de clique, não em render/efeito:\n // não reabre o loop do PR #435. E o onOpenChange que o\n // DropdownMenu dispara ao fechar vira no-op no\n // syncDropdownState, porque isActive já estará false.\n setActiveStates((prev) => ({\n ...prev,\n notifications: false,\n }));\n onNavigateByNotification?.(\n entityType,\n entityId,\n entityStatus,\n questionId\n );\n }\n }}\n getActionLabel={notifications.getActionLabel}\n onFetchNotifications={notifications.fetchNotifications}\n emptyStateImage={emptyNotificationsImage}\n emptyStateTitle={emptyNotificationsTitle}\n emptyStateDescription={emptyNotificationsDescription}\n />\n\n <DropdownMenu\n onOpenChange={(open: boolean) => {\n syncDropdownState(\n open,\n activeStates.profile,\n setActiveStates,\n 'profile'\n );\n }}\n >\n <DropdownMenuTrigger\n className=\"text-primary cursor-pointer\"\n asChild\n >\n <IconButton\n active={activeStates.profile}\n // O IconButton só tem ícone: sem rótulo próprio ele cai no\n // padrão \"Botão de ação\", que não diz o que abre.\n aria-label=\"Abrir menu de perfil\"\n onClick={() => toggleActive('profile')}\n icon={\n <UserIcon\n size={24}\n className={activeStates.profile ? 'opacity-80' : ''}\n />\n }\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className={profileDropdownWidth}\n side=\"bottom\"\n variant=\"profile\"\n align=\"end\"\n >\n <ProfileMenuHeader\n name={displayName}\n email={displayEmail}\n photoUrl={sessionInfo?.urlProfilePicture}\n />\n\n {showProfileInfo && (\n <ProfileMenuInfo\n schoolName={sessionInfo?.schoolName ?? ''}\n classYearName={sessionInfo?.className ?? ''}\n schoolYearName={sessionInfo?.schoolYearName ?? ''}\n />\n )}\n\n <ProfileMenuSection>\n <DropdownMenuItem\n variant=\"profile\"\n iconLeft={<UserPhosphorIcon />}\n iconRight={<CaretRightIcon />}\n onClick={handleMyDataClick}\n >\n <Text size=\"md\" color=\"text-text-700\">\n Meus dados\n </Text>\n </DropdownMenuItem>\n\n <ProfileToggleTheme />\n </ProfileMenuSection>\n\n <ProfileMenuFooter onClick={onLogout} />\n </DropdownMenuContent>\n </DropdownMenu>\n </section>\n </div>\n </PageContainer>\n </header>\n );\n};\n\nexport default AppHeader;\n"]}
|
|
@@ -26,7 +26,7 @@ var _chunkLGPTKTMPjs = require('./chunk-LGPTKTMP.js');
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
var
|
|
29
|
+
var _chunkY64LT6BLjs = require('./chunk-Y64LT6BL.js');
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
var _chunkONWBFEW5js = require('./chunk-ONWBFEW5.js');
|
|
@@ -111,25 +111,25 @@ function AuthorMeta({ authorName, authorPhoto, createdAt }) {
|
|
|
111
111
|
] });
|
|
112
112
|
}
|
|
113
113
|
function PostActionsMenu({ onEdit, onDelete }) {
|
|
114
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
114
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkY64LT6BLjs.DropdownMenu_default, { children: [
|
|
115
115
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
116
|
-
|
|
116
|
+
_chunkY64LT6BLjs.DropdownMenuTrigger,
|
|
117
117
|
{
|
|
118
118
|
"aria-label": "Abrir a\xE7\xF5es do post",
|
|
119
119
|
className: "p-1 rounded hover:bg-background-100 transition-colors shrink-0",
|
|
120
120
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.DotsThreeVerticalIcon, { size: 20, className: "text-text-600" })
|
|
121
121
|
}
|
|
122
122
|
),
|
|
123
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
123
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkY64LT6BLjs.DropdownMenuContent, { align: "end", children: [
|
|
124
124
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
125
|
-
|
|
125
|
+
_chunkY64LT6BLjs.DropdownMenuItem,
|
|
126
126
|
{
|
|
127
127
|
iconLeft: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.PencilSimpleIcon, { size: 16 }),
|
|
128
128
|
onClick: onEdit,
|
|
129
129
|
children: "Editar"
|
|
130
130
|
}
|
|
131
131
|
),
|
|
132
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
132
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkY64LT6BLjs.DropdownMenuItem, { iconLeft: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _react3.TrashIcon, { size: 16 }), onClick: onDelete, children: "Deletar" })
|
|
133
133
|
] })
|
|
134
134
|
] });
|
|
135
135
|
}
|
|
@@ -932,4 +932,4 @@ var Forum_default = Forum;
|
|
|
932
932
|
|
|
933
933
|
|
|
934
934
|
exports.Forum = Forum; exports.Forum_default = Forum_default;
|
|
935
|
-
//# sourceMappingURL=chunk-
|
|
935
|
+
//# sourceMappingURL=chunk-KYGFYAU7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-UEUGH2QZ.js","../src/components/Forum/Forum.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;AC1CA,8BAAiD;AACjD;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AAAA,+CACK;AA2ED,+CAAA;AAnDN,IAAM,UAAA,EAAY;AAAA,EAChB,SAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA;AAEA,SAAS,eAAA,CAAgB,OAAA,EAAyB;AAChD,EAAA,MAAM,EAAA,EAAI,IAAI,IAAA,CAAK,OAAO,CAAA;AAC1B,EAAA,MAAM,IAAA,EAAM,CAAA,CAAE,OAAA,CAAQ,CAAA;AACtB,EAAA,MAAM,MAAA,EAAQ,SAAA,CAAU,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA;AACpC,EAAA,MAAM,KAAA,EAAO,CAAA,CAAE,WAAA,CAAY,CAAA;AAC3B,EAAA,MAAM,MAAA,EAAQ,CAAA,CAAE,QAAA,CAAS,CAAA;AACzB,EAAA,MAAM,QAAA,EAAU,CAAA,CAAE,UAAA,CAAW,CAAA,CAAE,QAAA,CAAS,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AACzD,EAAA,OAAO,CAAA,EAAA;AACT;AAES;AACA,EAAA;AAMT;AAES;AACH,EAAA;AACG,EAAA;AACT;AAWS;AACH,EAAA;AACF,IAAA;AACG,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAAU,MAAA;AACZ,IAAA;AAEJ,EAAA;AAEE,EAAA;AAMJ;AAQS;AAEL,EAAA;AACE,oBAAA;AACA,oBAAA;AACE,sBAAA;AAA2D,QAAA;AAC5C,QAAA;AACf,MAAA;AACA,sBAAA;AAAsC,QAAA;AACjC,QAAA;AACL,MAAA;AACF,IAAA;AACF,EAAA;AAEJ;AAOS;AAEL,EAAA;AACE,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AAEA,QAAA;AAA2D,MAAA;AAC7D,IAAA;AACA,oBAAA;AACE,sBAAA;AAAC,QAAA;AAAA,QAAA;AACC,UAAA;AACA,UAAA;AACD,UAAA;AAAA,QAAA;AAED,MAAA;AACA,sBAAA;AAGF,IAAA;AACF,EAAA;AAEJ;AAuBS;AACP,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACwB;AAClB,EAAA;AACJ,IAAA;AACM,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AAGM,EAAA;AACA,EAAA;AACA,EAAA;AAGJ,EAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAEI,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACS,YAAA;AACD,YAAA;AACF,YAAA;AACI,YAAA;AACV,UAAA;AAED,QAAA;AACA,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACS,YAAA;AACD,YAAA;AACF,YAAA;AAC2C,YAAA;AACvC,YAAA;AAER,UAAA;AACH,QAAA;AACF,MAAA;AAGF,MAAA;AACG,QAAA;AAAA;AAEC,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AAEC,cAAA;AAC6C,cAAA;AACjC,YAAA;AAAA,YAAA;AACd,UAAA;AAAA,QAAA;AAEC,UAAA;AAAA,UAAA;AAAA,YAAA;AACa,YAAA;AACL,YAAA;AACwC,YAAA;AACrC,YAAA;AACC,YAAA;AACX,UAAA;AACF,QAAA;AAGF,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACS,YAAA;AACM,YAAA;AACwB,YAAA;AACL,UAAA;AACnC,QAAA;AACC,QAAA;AACE,UAAA;AAAA,UAAA;AAAA,YAAA;AACM,YAAA;AACD,YAAA;AACM,UAAA;AACZ,QAAA;AAGD,QAAA;AAEG,0BAAA;AAGA,0BAAA;AACE,4BAAA;AAAA,cAAA;AAAC,cAAA;AAAA,gBAAA;AACM,gBAAA;AACC,gBAAA;AACA,gBAAA;AACD,gBAAA;AACuB,gBAAA;AACiB,cAAA;AAAA,YAAA;AAC/C,4BAAA;AACA,cAAA;AAAC,cAAA;AAAA,gBAAA;AACM,gBAAA;AACC,gBAAA;AACA,gBAAA;AACD,gBAAA;AACuB,gBAAA;AACkB,cAAA;AAAA,YAAA;AAChD,UAAA;AACF,QAAA;AAGN,MAAA;AAAA,IAAA;AACF,EAAA;AAEJ;AAMS;AAEL,EAAA;AAEJ;AAES;AAEL,EAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAO,IAAA;AACT,EAAA;AAEJ;AA4BgB;AACd,EAAA;AACA,EAAA;AACA,EAAA;AACQ,EAAA;AACR,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACa;AACP,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAKA,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACA,EAAA;AACA,EAAA;AAKA,EAAA;AACA,EAAA;AACA,EAAA;AAKA,EAAA;AACL,IAAA;AACF,EAAA;AACO,EAAA;AACA,EAAA;AAGA,EAAA;AAID,EAAA;AACJ,IAAA;AACI,IAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,EAAA;AAEE,EAAA;AACJ,IAAA;AACE,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AAEA,EAAA;AACE,IAAA;AACE,EAAA;AAEE,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AAEM,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,EAAA;AAGE,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AAEI,UAAA;AACF,QAAA;AACH,MAAA;AACD,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAGK,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACI,MAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEK,EAAA;AACJ,IAAA;AACE,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACE,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AAGM,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAGK,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACD,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEK,EAAA;AACJ,IAAA;AACM,MAAA;AACJ,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AAEM,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACG,EAAA;AAEC,EAAA;AAMA,EAAA;AACC,IAAA;AACL,IAAA;AACI,IAAA;AACF,MAAA;AAEA,MAAA;AAAY,QAAA;AACD,UAAA;AAET,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACC,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEK,EAAA;AACC,IAAA;AACD,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,EAAA;AAIE,EAAA;AACA,IAAA;AAEA,IAAA;AACF,MAAA;AAOF,IAAA;AAEA,IAAA;AAGO,MAAA;AAAA,MAAA;AAEC,QAAA;AAGA,QAAA;AAAA,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACM,cAAA;AACK,cAAA;AAC2B,cAAA;AAErC,gCAAA;AAAA,kBAAA;AAAC,kBAAA;AAAA,oBAAA;AACmB,oBAAA;AACC,oBAAA;AACF,kBAAA;AAAA,gBAAA;AACnB,gCAAA;AAGA,gCAAA;AAEE,kCAAA;AAA8B,kCAAA;AACsB,gBAAA;AACtD,cAAA;AAAA,YAAA;AACF,UAAA;AAGC,UAAA;AACC,YAAA;AAAC,YAAA;AAAA,cAAA;AACwC,cAAA;AAEW,YAAA;AAEpD,UAAA;AAAA,QAAA;AAAA,MAAA;AA9BG,MAAA;AAkCX,IAAA;AAED,EAAA;AAEG,EAAA;AACA,IAAA;AAEC,IAAA;AAEL,IAAA;AAGI,sBAAA;AACE,wBAAA;AACE,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AAC2B,cAAA;AACC,cAAA;AACF,YAAA;AAC3B,UAAA;AACA,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACW,cAAA;AACe,gBAAA;AAC6B,cAAA;AACtD,YAAA;AACF,UAAA;AACC,UAAA;AACC,YAAA;AAAC,YAAA;AAAA,cAAA;AACoB,cAAA;AACf,cAAA;AACM,YAAA;AACZ,UAAA;AAAA,QAAA;AAGH,QAAA;AACE,UAAA;AAAA,UAAA;AAAA,YAAA;AACgD,YAAA;AAEW,UAAA;AAE5D,QAAA;AAEJ,MAAA;AAGA,sBAAA;AACE,wBAAA;AAGA,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACS,YAAA;AACD,YAAA;AACF,YAAA;AACkC,YAAA;AACxC,UAAA;AAED,QAAA;AACF,MAAA;AAGC,MAAA;AAIO,wBAAA;AACE,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACmB,cAAA;AACC,cAAA;AACF,YAAA;AACnB,UAAA;AACA,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACW,cAAA;AACe,gBAAA;AACqB,cAAA;AAC9C,YAAA;AACF,UAAA;AACC,UAAA;AACC,YAAA;AAAC,YAAA;AAAA,cAAA;AACY,cAAA;AACP,cAAA;AACM,YAAA;AACZ,UAAA;AAED,UAAA;AAGiB;AAAA,4BAAA;AAEd,cAAA;AAAC,cAAA;AAAA,gBAAA;AACM,gBAAA;AACK,gBAAA;AAC6B,gBAAA;AAEvC,kCAAA;AAAqB,kBAAA;AAAE,gBAAA;AAAA,cAAA;AAAA,YAAA;AAEzB,UAAA;AAAA;AAAA,4BAAA;AAIE,8BAAA;AAAsC,gBAAA;AAAA,gBAAA;AACxB,cAAA;AACd,8BAAA;AACA,gBAAA;AAAC,gBAAA;AAAA,kBAAA;AACM,kBAAA;AACK,kBAAA;AAC8B,kBAAA;AAEZ,gBAAA;AAAA,cAAA;AAC9B,YAAA;AACF,UAAA;AAEH,UAAA;AAI2D,YAAA;AAAA,YAAA;AAC1C,UAAA;AACd,QAAA;AAGL,QAAA;AACE,UAAA;AAAA,UAAA;AAAA,YAAA;AACwC,YAAA;AAEW,UAAA;AAEpD,QAAA;AAAA,MAAA;AAMZ,IAAA;AAED,EAAA;AAGD,EAAA;AACG,IAAA;AAGG,sBAAA;AACE,wBAAA;AAGA,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACS,YAAA;AACD,YAAA;AACF,YAAA;AACmC,YAAA;AACzC,UAAA;AAED,QAAA;AACF,MAAA;AAGA,sBAAA;AACE,wBAAA;AAIC,QAAA;AACH,MAAA;AACF,IAAA;AAAA;AAGA,sBAAA;AACE,wBAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACM,YAAA;AACK,YAAA;AACD,YAAA;AAET,8BAAA;AAAmD,8BAAA;AAGnD,YAAA;AAAA,UAAA;AACF,QAAA;AAEC,QAAA;AACH,MAAA;AAAA,IAAA;AAIF,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAwB,MAAA;AAC1B,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAQ,MAAA;AACV,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAQ,MAAA;AACV,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAAQ,MAAA;AACV,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AAKA,QAAA;AACA,QAAA;AAEI,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACS,cAAA;AACD,cAAA;AACF,cAAA;AACI,cAAA;AACV,YAAA;AAED,UAAA;AACA,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACS,cAAA;AACD,cAAA;AACF,cAAA;AACsB,cAAA;AAClB,cAAA;AAEoB,YAAA;AAC/B,UAAA;AAAA,QAAA;AAIJ,QAAA;AAAC,UAAA;AAAA,UAAA;AAAA,YAAA;AACM,YAAA;AACA,YAAA;AACA,YAAA;AACC,YAAA;AACM,YAAA;AACS,YAAA;AAEnB,cAAA;AACA,cAAA;AACA,cAAA;AAAgE,YAAA;AAClE,UAAA;AACF,QAAA;AAAA,MAAA;AACF,IAAA;AAGA,oBAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAEI,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACS,cAAA;AACD,cAAA;AACF,cAAA;AAC+B,cAAA;AACrC,YAAA;AAED,UAAA;AACA,0BAAA;AAAA,YAAA;AAAC,YAAA;AAAA,cAAA;AACS,cAAA;AACD,cAAA;AACF,cAAA;AACK,cAAA;AACD,cAAA;AACV,YAAA;AAED,UAAA;AAAA,QAAA;AAIJ,QAAA;AAEA,MAAA;AACF,IAAA;AACF,EAAA;AAEJ;AAEO;AD3IG;AACA;AACA;AACA;AACA","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-UEUGH2QZ.js","sourcesContent":[null,"import { useState, useEffect, useCallback } from 'react';\nimport {\n CaretLeftIcon,\n ChatCircleTextIcon,\n CheckIcon,\n DotsThreeVerticalIcon,\n ImageIcon,\n PencilSimpleIcon,\n TrashIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport TextArea from '../TextArea/TextArea';\nimport Modal from '../Modal/Modal';\nimport Radio from '../Radio/Radio';\nimport DropdownMenu, {\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '../DropdownMenu/DropdownMenu';\nimport { RichEditor } from '../RichEditor/RichEditor';\nimport ImageUpload from '../ImageUpload/ImageUpload';\nimport { SkeletonList, SkeletonCard } from '../Skeleton/Skeleton';\nimport { stripHtml, sanitizeHtmlForDisplay } from '../HtmlMathRenderer';\nimport { cn } from '../../utils/utils';\nimport type { ForumApiClient, ForumTopic, ForumReply } from '../../types/forum';\nimport { PROFILE_ROLES } from '../../types/chat';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTHS_PT = [\n 'Janeiro',\n 'Fevereiro',\n 'Março',\n 'Abril',\n 'Maio',\n 'Junho',\n 'Julho',\n 'Agosto',\n 'Setembro',\n 'Outubro',\n 'Novembro',\n 'Dezembro',\n];\n\nfunction formatForumDate(isoDate: string): string {\n const d = new Date(isoDate);\n const day = d.getDate();\n const month = MONTHS_PT[d.getMonth()];\n const year = d.getFullYear();\n const hours = d.getHours();\n const minutes = d.getMinutes().toString().padStart(2, '0');\n return `${day} ${month} ${year} • ${hours}:${minutes}`;\n}\n\nfunction getInitials(name: string): string {\n return name\n .split(' ')\n .slice(0, 2)\n .map((word) => word[0] ?? '')\n .join('')\n .toUpperCase();\n}\n\nfunction formatReplyCount(count: number): string {\n if (count === 1) return '1 resposta';\n return `${count} respostas`;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\ninterface AuthorAvatarProps {\n readonly name: string;\n readonly photoUrl: string | null;\n}\n\nfunction AuthorAvatar({ name, photoUrl }: AuthorAvatarProps) {\n if (photoUrl) {\n return (\n <img\n src={photoUrl}\n alt={name}\n className=\"w-8 h-8 rounded-full object-cover shrink-0\"\n />\n );\n }\n return (\n <div className=\"w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center shrink-0\">\n <Text size=\"xs\" weight=\"semibold\" color=\"text-white\">\n {getInitials(name)}\n </Text>\n </div>\n );\n}\n\ninterface AuthorMetaProps {\n readonly authorName: string;\n readonly authorPhoto: string | null;\n readonly createdAt: string;\n}\n\nfunction AuthorMeta({ authorName, authorPhoto, createdAt }: AuthorMetaProps) {\n return (\n <div className=\"flex items-center gap-2\">\n <AuthorAvatar name={authorName} photoUrl={authorPhoto} />\n <div className=\"flex items-center gap-1\">\n <Text size=\"xs\" weight=\"semibold\" color=\"text-primary-700\">\n Postado por {authorName}\n </Text>\n <Text size=\"xs\" color=\"text-text-600\">\n • {formatForumDate(createdAt)}\n </Text>\n </div>\n </div>\n );\n}\n\ninterface PostActionsMenuProps {\n readonly onEdit: () => void;\n readonly onDelete: () => void;\n}\n\nfunction PostActionsMenu({ onEdit, onDelete }: PostActionsMenuProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label=\"Abrir ações do post\"\n className=\"p-1 rounded hover:bg-background-100 transition-colors shrink-0\"\n >\n <DotsThreeVerticalIcon size={20} className=\"text-text-600\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n iconLeft={<PencilSimpleIcon size={16} />}\n onClick={onEdit}\n >\n Editar\n </DropdownMenuItem>\n <DropdownMenuItem iconLeft={<TrashIcon size={16} />} onClick={onDelete}>\n Deletar\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\ninterface PostContentModalProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly submitLabel: string;\n readonly content: string;\n readonly imageUrl: string | undefined;\n readonly isSubmitting: boolean;\n readonly onContentChange: (value: string) => void;\n readonly onClose: () => void;\n readonly onSubmit: () => void;\n readonly onUploadImage?: (file: File) => Promise<string>;\n readonly onImageUploaded: (url: string) => void;\n readonly maxLength: number;\n /** Use RichEditor with toolbar instead of plain TextArea */\n readonly richText?: boolean;\n /** Show \"Este fórum vale nota no boletim?\" question (professor only) */\n readonly showGradeQuestion?: boolean;\n readonly countsForGrade?: boolean;\n readonly onCountsForGradeChange?: (value: boolean) => void;\n}\n\nfunction PostContentModal({\n isOpen,\n title,\n submitLabel,\n content,\n imageUrl,\n isSubmitting,\n onContentChange,\n onClose,\n onSubmit,\n onUploadImage,\n onImageUploaded,\n maxLength,\n richText = false,\n showGradeQuestion = false,\n countsForGrade,\n onCountsForGradeChange,\n}: PostContentModalProps) {\n const handleImageSelect = useCallback(\n async (file: File) => {\n if (!onUploadImage) return;\n try {\n const url = await onUploadImage(file);\n onImageUploaded(url);\n } catch {\n // silently fail — consuming app should handle upload errors\n }\n },\n [onUploadImage, onImageUploaded]\n );\n\n // Determine if content is effectively empty or exceeds the limit\n const plainTextContent = richText ? stripHtml(content) : content;\n const isContentEmpty = !plainTextContent.trim();\n const exceedsMaxLength = plainTextContent.trim().length > maxLength;\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size=\"md\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={onClose}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n disabled={isContentEmpty || exceedsMaxLength || isSubmitting}\n onClick={onSubmit}\n >\n {submitLabel}\n </Button>\n </div>\n }\n >\n <div className=\"flex flex-col gap-3\">\n {richText ? (\n // key forces remount (and thus reset) whenever the modal opens/closes\n <RichEditor\n key={String(isOpen)}\n content={content}\n onChange={(data) => onContentChange(data.html)}\n placeholder=\"Escreva o conteúdo do seu post aqui.\"\n />\n ) : (\n <TextArea\n placeholder=\"Escreva o conteúdo do seu post aqui.\"\n value={content}\n onChange={(e) => onContentChange(e.target.value)}\n autoResize\n minHeight={120}\n maxLength={maxLength}\n />\n )}\n\n <ImageUpload\n variant=\"compact\"\n onFileSelect={handleImageSelect}\n onRemoveFile={() => onImageUploaded('')}\n buttonIcon={<ImageIcon size={16} />}\n />\n {imageUrl && (\n <img\n src={imageUrl}\n alt=\"Preview da imagem\"\n className=\"max-h-40 rounded-lg object-contain\"\n />\n )}\n\n {showGradeQuestion && (\n <div className=\"flex flex-col gap-2\">\n <Text size=\"sm\" weight=\"medium\">\n Este fórum vale nota no boletim?\n </Text>\n <div className=\"flex items-center gap-4\">\n <Radio\n name=\"countsForGrade\"\n value=\"yes\"\n label=\"Sim\"\n size=\"small\"\n checked={countsForGrade === true}\n onChange={() => onCountsForGradeChange?.(true)}\n />\n <Radio\n name=\"countsForGrade\"\n value=\"no\"\n label=\"Não\"\n size=\"small\"\n checked={countsForGrade === false}\n onChange={() => onCountsForGradeChange?.(false)}\n />\n </div>\n </div>\n )}\n </div>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nfunction TopicListSkeleton() {\n return (\n <SkeletonList items={3} showAvatar showTitle showDescription lines={2} />\n );\n}\n\nfunction TopicDetailSkeleton() {\n return (\n <SkeletonCard\n showAvatar\n showTitle\n showDescription\n showActions={false}\n lines={2}\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\nexport type ForumProps = {\n /** API client with forum CRUD methods */\n readonly apiClient: ForumApiClient;\n /** Current user's userInstitutionId — used to identify own posts */\n readonly currentUserId: string;\n /** User role — enables professor-specific features (e.g. grade question on create) */\n readonly userRole?: PROFILE_ROLES.STUDENT | PROFILE_ROLES.TEACHER;\n /** Forum page title */\n readonly title?: string;\n /** Forum page subtitle */\n readonly subtitle?: string;\n /** Callback to upload an image file and return its hosted URL */\n readonly onUploadImage?: (file: File) => Promise<string>;\n /**\n * Callback to evaluate a reply (teacher only).\n * If provided, an \"Avaliar\" button appears on each reply when the topic counts for grade.\n */\n readonly onEvaluateReply?: (replyId: string, grade: number) => Promise<void>;\n /** Additional CSS classes */\n readonly className?: string;\n};\n\nexport function Forum({\n apiClient,\n currentUserId,\n userRole = PROFILE_ROLES.STUDENT,\n title = 'Fórum',\n subtitle = 'Espaço para troca de conhecimento',\n onUploadImage,\n onEvaluateReply,\n className,\n}: ForumProps) {\n const isTeacher = userRole === PROFILE_ROLES.TEACHER;\n const [view, setView] = useState<'list' | 'detail'>('list');\n const [topics, setTopics] = useState<ForumTopic[]>([]);\n const [isLoadingTopics, setIsLoadingTopics] = useState(false);\n const [selectedTopic, setSelectedTopic] = useState<ForumTopic | null>(null);\n const [replies, setReplies] = useState<ForumReply[]>([]);\n const [isLoadingTopic, setIsLoadingTopic] = useState(false);\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n // Create post modal state\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createContent, setCreateContent] = useState('');\n const [createImageUrl, setCreateImageUrl] = useState<string | undefined>();\n const [createCountsForGrade, setCreateCountsForGrade] = useState<\n boolean | undefined\n >();\n\n // Reply modal state\n const [isReplyModalOpen, setIsReplyModalOpen] = useState(false);\n const [replyContent, setReplyContent] = useState('');\n const [replyImageUrl, setReplyImageUrl] = useState<string | undefined>();\n\n // Edit topic modal state\n const [editingTopic, setEditingTopic] = useState<ForumTopic | null>(null);\n const [editTopicContent, setEditTopicContent] = useState('');\n const [editTopicImageUrl, setEditTopicImageUrl] = useState<\n string | undefined\n >();\n\n // Edit reply modal state\n const [editingReply, setEditingReply] = useState<ForumReply | null>(null);\n const [editReplyContent, setEditReplyContent] = useState('');\n const [editReplyImageUrl, setEditReplyImageUrl] = useState<\n string | undefined\n >();\n\n // Evaluate reply modal state (teacher only)\n const [evaluatingReplyId, setEvaluatingReplyId] = useState<string | null>(\n null\n );\n const [gradeValue, setGradeValue] = useState<number | null>(null);\n const [isEditingGrade, setIsEditingGrade] = useState(false);\n\n // Delete confirmation state\n const [pendingDelete, setPendingDelete] = useState<\n { type: 'topic'; id: string } | { type: 'reply'; id: string } | null\n >(null);\n\n const fetchTopics = useCallback(async () => {\n setIsLoadingTopics(true);\n try {\n const response = await apiClient.getTopics({ limit: 50 });\n setTopics(response.topics);\n } finally {\n setIsLoadingTopics(false);\n }\n }, [apiClient]);\n\n const fetchTopic = useCallback(\n async (topicId: string) => {\n setIsLoadingTopic(true);\n try {\n const response = await apiClient.getTopic(topicId);\n setSelectedTopic(response.topic);\n setReplies(response.replies);\n } finally {\n setIsLoadingTopic(false);\n }\n },\n [apiClient]\n );\n\n useEffect(() => {\n fetchTopics();\n }, [fetchTopics]);\n\n const handleTopicClick = useCallback(\n (topic: ForumTopic) => {\n setSelectedTopic(topic);\n setView('detail');\n fetchTopic(topic.id);\n },\n [fetchTopic]\n );\n\n const handleBack = useCallback(() => {\n setView('list');\n setSelectedTopic(null);\n setReplies([]);\n fetchTopics();\n }, [fetchTopics]);\n\n // Create topic\n const handleCloseCreateModal = useCallback(() => {\n setIsCreateModalOpen(false);\n setCreateContent('');\n setCreateImageUrl(undefined);\n setCreateCountsForGrade(undefined);\n }, []);\n\n const handleCreateTopic = useCallback(async () => {\n if (!createContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.createTopic({\n content: createContent.trim(),\n ...(createImageUrl && { imageUrl: createImageUrl }),\n ...(isTeacher &&\n createCountsForGrade !== undefined && {\n countsForGrade: createCountsForGrade,\n }),\n });\n handleCloseCreateModal();\n await fetchTopics();\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n createContent,\n createImageUrl,\n createCountsForGrade,\n isTeacher,\n handleCloseCreateModal,\n fetchTopics,\n ]);\n\n // Edit topic\n const handleOpenEditTopic = useCallback((topic: ForumTopic) => {\n setEditingTopic(topic);\n setEditTopicContent(topic.content);\n setEditTopicImageUrl(topic.imageUrl ?? undefined);\n }, []);\n\n const handleCloseEditTopic = useCallback(() => {\n setEditingTopic(null);\n setEditTopicContent('');\n setEditTopicImageUrl(undefined);\n }, []);\n\n const handleEditTopic = useCallback(async () => {\n if (!editingTopic || !editTopicContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.updateTopic(editingTopic.id, {\n content: editTopicContent.trim(),\n imageUrl: editTopicImageUrl ?? null,\n });\n handleCloseEditTopic();\n if (view === 'list') {\n await fetchTopics();\n } else {\n await fetchTopic(editingTopic.id);\n }\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n editingTopic,\n editTopicContent,\n editTopicImageUrl,\n handleCloseEditTopic,\n view,\n fetchTopics,\n fetchTopic,\n ]);\n\n const handleDeleteTopic = useCallback(\n async (topicId: string) => {\n setIsSubmitting(true);\n try {\n await apiClient.deleteTopic(topicId);\n if (view === 'detail') {\n handleBack();\n } else {\n await fetchTopics();\n }\n } finally {\n setIsSubmitting(false);\n setPendingDelete(null);\n }\n },\n [apiClient, view, handleBack, fetchTopics]\n );\n\n // Reply\n const handleCloseReplyModal = useCallback(() => {\n setIsReplyModalOpen(false);\n setReplyContent('');\n setReplyImageUrl(undefined);\n }, []);\n\n const handleCreateReply = useCallback(async () => {\n if (!selectedTopic || !replyContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.createReply(selectedTopic.id, {\n content: replyContent.trim(),\n ...(replyImageUrl && { imageUrl: replyImageUrl }),\n });\n handleCloseReplyModal();\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n selectedTopic,\n replyContent,\n replyImageUrl,\n handleCloseReplyModal,\n fetchTopic,\n ]);\n\n // Edit reply\n const handleOpenEditReply = useCallback((reply: ForumReply) => {\n setEditingReply(reply);\n setEditReplyContent(reply.content);\n setEditReplyImageUrl(reply.imageUrl ?? undefined);\n }, []);\n\n const handleCloseEditReply = useCallback(() => {\n setEditingReply(null);\n setEditReplyContent('');\n setEditReplyImageUrl(undefined);\n }, []);\n\n const handleEditReply = useCallback(async () => {\n if (!editingReply || !editReplyContent.trim() || !selectedTopic) return;\n setIsSubmitting(true);\n try {\n await apiClient.updateReply(editingReply.id, {\n content: editReplyContent.trim(),\n imageUrl: editReplyImageUrl ?? null,\n });\n handleCloseEditReply();\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n editingReply,\n editReplyContent,\n editReplyImageUrl,\n handleCloseEditReply,\n selectedTopic,\n fetchTopic,\n ]);\n\n const handleDeleteReply = useCallback(\n async (replyId: string) => {\n if (!selectedTopic) return;\n setIsSubmitting(true);\n try {\n await apiClient.deleteReply(replyId);\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n setPendingDelete(null);\n }\n },\n [apiClient, selectedTopic, fetchTopic]\n );\n\n const handleCloseEvaluateModal = useCallback(() => {\n setEvaluatingReplyId(null);\n setGradeValue(null);\n setIsEditingGrade(false);\n }, []);\n\n const handleOpenEvaluate = useCallback((reply: ForumReply) => {\n setEvaluatingReplyId(reply.id);\n setGradeValue(null);\n setIsEditingGrade(false);\n }, []);\n\n const handleOpenEditGrade = useCallback((reply: ForumReply) => {\n setEvaluatingReplyId(reply.id);\n setGradeValue(reply.grade ?? null);\n setIsEditingGrade(true);\n }, []);\n\n const isValidGrade =\n gradeValue !== null &&\n Number.isInteger(gradeValue) &&\n gradeValue >= 0 &&\n gradeValue <= 10;\n\n const handleSubmitEvaluation = useCallback(async () => {\n if (!evaluatingReplyId || !isValidGrade || !onEvaluateReply) return;\n setIsSubmitting(true);\n try {\n await onEvaluateReply(evaluatingReplyId, gradeValue);\n // Optimistically update grade in local state\n setReplies((prev) =>\n prev.map((r) =>\n r.id === evaluatingReplyId ? { ...r, grade: gradeValue } : r\n )\n );\n handleCloseEvaluateModal();\n } finally {\n setIsSubmitting(false);\n }\n }, [\n evaluatingReplyId,\n gradeValue,\n isValidGrade,\n onEvaluateReply,\n handleCloseEvaluateModal,\n ]);\n\n const handleConfirmDelete = useCallback(async () => {\n if (!pendingDelete) return;\n if (pendingDelete.type === 'topic') {\n await handleDeleteTopic(pendingDelete.id);\n } else {\n await handleDeleteReply(pendingDelete.id);\n }\n }, [pendingDelete, handleDeleteTopic, handleDeleteReply]);\n\n /* ---- Extracted render blocks (avoids nested ternaries — SonarQube) ---- */\n\n const topicListContent = (() => {\n if (isLoadingTopics) return <TopicListSkeleton />;\n\n if (topics.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-16 border border-border-200 rounded-lg\">\n <Text size=\"md\" color=\"text-text-500\">\n Nenhum tópico ainda. Seja o primeiro a criar um post!\n </Text>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n {topics.map((topic) => (\n <div\n key={topic.id}\n className=\"flex items-start bg-white border border-border-100 rounded-lg pt-4 pr-2 pb-4 pl-2 shadow-sm\"\n >\n {/* Clickable content area */}\n <button\n type=\"button\"\n className=\"flex flex-col gap-3 flex-1 min-w-0 text-left hover:opacity-90 transition-opacity\"\n onClick={() => handleTopicClick(topic)}\n >\n <AuthorMeta\n authorName={topic.authorName}\n authorPhoto={topic.authorPhoto}\n createdAt={topic.createdAt}\n />\n <Text size=\"sm\" className=\"leading-normal line-clamp-3 ml-10\">\n {stripHtml(topic.content)}\n </Text>\n <div className=\"flex items-center gap-1 text-text-600 ml-10\">\n <ChatCircleTextIcon size={16} />\n <Text size=\"xs\">{formatReplyCount(topic.replyCount)}</Text>\n </div>\n </button>\n\n {/* Actions menu — only for own posts */}\n {topic.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditTopic(topic)}\n onDelete={() =>\n setPendingDelete({ type: 'topic', id: topic.id })\n }\n />\n )}\n </div>\n ))}\n </div>\n );\n })();\n\n const topicDetailContent = (() => {\n if (isLoadingTopic) return <TopicDetailSkeleton />;\n\n if (!selectedTopic) return null;\n\n return (\n <>\n {/* Original post */}\n <div className=\"flex items-start gap-2\">\n <div className=\"flex flex-col gap-3 flex-1 min-w-0\">\n <AuthorMeta\n authorName={selectedTopic.authorName}\n authorPhoto={selectedTopic.authorPhoto}\n createdAt={selectedTopic.createdAt}\n />\n <div\n className=\"text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none\"\n dangerouslySetInnerHTML={{\n __html: sanitizeHtmlForDisplay(selectedTopic.content),\n }}\n />\n {selectedTopic.imageUrl && (\n <img\n src={selectedTopic.imageUrl}\n alt=\"Imagem do post\"\n className=\"ml-10 max-w-lg rounded-lg\"\n />\n )}\n </div>\n {selectedTopic.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditTopic(selectedTopic)}\n onDelete={() =>\n setPendingDelete({ type: 'topic', id: selectedTopic.id })\n }\n />\n )}\n </div>\n\n {/* Replies header */}\n <div className=\"flex items-center justify-between\">\n <Text size=\"sm\" color=\"text-text-600\">\n {formatReplyCount(selectedTopic.replyCount)}\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setIsReplyModalOpen(true)}\n >\n Responder\n </Button>\n </div>\n\n {/* Replies list */}\n {replies.length > 0 && (\n <div className=\"flex flex-col divide-y divide-border-200\">\n {replies.map((reply) => (\n <div key={reply.id} className=\"flex items-start gap-2 py-4\">\n <div className=\"flex flex-col gap-3 flex-1 min-w-0\">\n <AuthorMeta\n authorName={reply.authorName}\n authorPhoto={reply.authorPhoto}\n createdAt={reply.createdAt}\n />\n <div\n className=\"text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none\"\n dangerouslySetInnerHTML={{\n __html: sanitizeHtmlForDisplay(reply.content),\n }}\n />\n {reply.imageUrl && (\n <img\n src={reply.imageUrl}\n alt=\"Imagem da resposta\"\n className=\"ml-10 max-w-lg rounded-lg\"\n />\n )}\n {isTeacher &&\n selectedTopic?.countsForGrade &&\n onEvaluateReply &&\n (reply.grade == null ? (\n /* Not yet graded — show Avaliar button */\n <button\n type=\"button\"\n className=\"flex items-center gap-1.5 text-text-600 text-sm hover:text-text-950 transition-colors w-fit ml-10\"\n onClick={() => handleOpenEvaluate(reply)}\n >\n <CheckIcon size={16} />\n Avaliar\n </button>\n ) : (\n /* Grade already set — teacher sees it with edit pencil */\n <div className=\"flex items-center gap-1.5 ml-10\">\n <Text size=\"sm\" color=\"text-text-600\">\n Nota {reply.grade}\n </Text>\n <button\n type=\"button\"\n className=\"text-text-600 hover:text-text-950 transition-colors\"\n onClick={() => handleOpenEditGrade(reply)}\n >\n <PencilSimpleIcon size={14} />\n </button>\n </div>\n ))}\n {!isTeacher &&\n reply.grade != null &&\n reply.userInstitutionId === currentUserId &&\n selectedTopic?.countsForGrade && (\n <Text size=\"sm\" color=\"text-text-600\" className=\"ml-10\">\n Nota {reply.grade}\n </Text>\n )}\n </div>\n {reply.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditReply(reply)}\n onDelete={() =>\n setPendingDelete({ type: 'reply', id: reply.id })\n }\n />\n )}\n </div>\n ))}\n </div>\n )}\n </>\n );\n })();\n\n return (\n <div className={cn('flex flex-col gap-4', className)}>\n {view === 'list' ? (\n <>\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <Text as=\"h1\" size=\"2xl\" weight=\"bold\" color=\"text-text-950\">\n {title}\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setIsCreateModalOpen(true)}\n >\n Criar post\n </Button>\n </div>\n\n {/* Subtitle + topic list */}\n <div className=\"flex flex-col gap-3\">\n <Text as=\"h2\" size=\"lg\" weight=\"semibold\" color=\"text-text-950\">\n {subtitle}\n </Text>\n\n {topicListContent}\n </div>\n </>\n ) : (\n /* Detail view */\n <>\n <button\n type=\"button\"\n className=\"flex items-center gap-2 hover:opacity-80 transition-opacity w-fit\"\n onClick={handleBack}\n >\n <CaretLeftIcon size={24} className=\"text-text-950\" />\n <Text size=\"2xl\" weight=\"bold\">\n Voltar\n </Text>\n </button>\n\n {topicDetailContent}\n </>\n )}\n\n {/* Create post modal */}\n <PostContentModal\n isOpen={isCreateModalOpen}\n title=\"Criar post\"\n submitLabel=\"Criar post\"\n content={createContent}\n imageUrl={createImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setCreateContent}\n onClose={handleCloseCreateModal}\n onSubmit={handleCreateTopic}\n onUploadImage={onUploadImage}\n onImageUploaded={setCreateImageUrl}\n maxLength={10000}\n richText\n showGradeQuestion={isTeacher}\n countsForGrade={createCountsForGrade}\n onCountsForGradeChange={setCreateCountsForGrade}\n />\n\n {/* Reply modal */}\n <PostContentModal\n isOpen={isReplyModalOpen}\n title=\"Responder\"\n submitLabel=\"Responder\"\n content={replyContent}\n imageUrl={replyImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setReplyContent}\n onClose={handleCloseReplyModal}\n onSubmit={handleCreateReply}\n onUploadImage={onUploadImage}\n onImageUploaded={setReplyImageUrl}\n maxLength={5000}\n richText\n />\n\n {/* Edit topic modal */}\n <PostContentModal\n isOpen={!!editingTopic}\n title=\"Editar\"\n submitLabel=\"Salvar\"\n content={editTopicContent}\n imageUrl={editTopicImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setEditTopicContent}\n onClose={handleCloseEditTopic}\n onSubmit={handleEditTopic}\n onUploadImage={onUploadImage}\n onImageUploaded={setEditTopicImageUrl}\n maxLength={10000}\n richText\n />\n\n {/* Edit reply modal */}\n <PostContentModal\n isOpen={!!editingReply}\n title=\"Editar\"\n submitLabel=\"Salvar\"\n content={editReplyContent}\n imageUrl={editReplyImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setEditReplyContent}\n onClose={handleCloseEditReply}\n onSubmit={handleEditReply}\n onUploadImage={onUploadImage}\n onImageUploaded={setEditReplyImageUrl}\n maxLength={5000}\n richText\n />\n\n {/* Evaluate / edit grade modal (teacher only) */}\n <Modal\n isOpen={!!evaluatingReplyId}\n onClose={handleCloseEvaluateModal}\n title={\n isEditingGrade\n ? 'Edite a nota da atividade'\n : 'Defina a nota da atividade'\n }\n size=\"md\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={handleCloseEvaluateModal}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n disabled={!isValidGrade || isSubmitting}\n onClick={handleSubmitEvaluation}\n >\n {isEditingGrade ? 'Salvar' : 'Avaliar'}\n </Button>\n </div>\n }\n >\n <Input\n type=\"number\"\n min={0}\n max={10}\n step={1}\n placeholder=\"Nota (0-10)\"\n value={gradeValue ?? ''}\n onChange={(e) => {\n const val = e.target.value;\n const parsed = Number(val);\n setGradeValue(val === '' || Number.isNaN(parsed) ? null : parsed);\n }}\n />\n </Modal>\n\n {/* Delete confirmation modal */}\n <Modal\n isOpen={!!pendingDelete}\n onClose={() => setPendingDelete(null)}\n title=\"Deseja deletar post?\"\n size=\"sm\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={() => setPendingDelete(null)}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"negative\"\n size=\"medium\"\n disabled={isSubmitting}\n onClick={handleConfirmDelete}\n >\n Deletar\n </Button>\n </div>\n }\n >\n <Text size=\"md\" color=\"text-text-600\">\n Essa ação é permanente e não poderá ser desfeita.\n </Text>\n </Modal>\n </div>\n );\n}\n\nexport default Forum;\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-KYGFYAU7.js","../src/components/Forum/Forum.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-KYGFYAU7.js","sourcesContent":[null,"import { useState, useEffect, useCallback } from 'react';\nimport {\n CaretLeftIcon,\n ChatCircleTextIcon,\n CheckIcon,\n DotsThreeVerticalIcon,\n ImageIcon,\n PencilSimpleIcon,\n TrashIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport TextArea from '../TextArea/TextArea';\nimport Modal from '../Modal/Modal';\nimport Radio from '../Radio/Radio';\nimport DropdownMenu, {\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '../DropdownMenu/DropdownMenu';\nimport { RichEditor } from '../RichEditor/RichEditor';\nimport ImageUpload from '../ImageUpload/ImageUpload';\nimport { SkeletonList, SkeletonCard } from '../Skeleton/Skeleton';\nimport { stripHtml, sanitizeHtmlForDisplay } from '../HtmlMathRenderer';\nimport { cn } from '../../utils/utils';\nimport type { ForumApiClient, ForumTopic, ForumReply } from '../../types/forum';\nimport { PROFILE_ROLES } from '../../types/chat';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTHS_PT = [\n 'Janeiro',\n 'Fevereiro',\n 'Março',\n 'Abril',\n 'Maio',\n 'Junho',\n 'Julho',\n 'Agosto',\n 'Setembro',\n 'Outubro',\n 'Novembro',\n 'Dezembro',\n];\n\nfunction formatForumDate(isoDate: string): string {\n const d = new Date(isoDate);\n const day = d.getDate();\n const month = MONTHS_PT[d.getMonth()];\n const year = d.getFullYear();\n const hours = d.getHours();\n const minutes = d.getMinutes().toString().padStart(2, '0');\n return `${day} ${month} ${year} • ${hours}:${minutes}`;\n}\n\nfunction getInitials(name: string): string {\n return name\n .split(' ')\n .slice(0, 2)\n .map((word) => word[0] ?? '')\n .join('')\n .toUpperCase();\n}\n\nfunction formatReplyCount(count: number): string {\n if (count === 1) return '1 resposta';\n return `${count} respostas`;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\ninterface AuthorAvatarProps {\n readonly name: string;\n readonly photoUrl: string | null;\n}\n\nfunction AuthorAvatar({ name, photoUrl }: AuthorAvatarProps) {\n if (photoUrl) {\n return (\n <img\n src={photoUrl}\n alt={name}\n className=\"w-8 h-8 rounded-full object-cover shrink-0\"\n />\n );\n }\n return (\n <div className=\"w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center shrink-0\">\n <Text size=\"xs\" weight=\"semibold\" color=\"text-white\">\n {getInitials(name)}\n </Text>\n </div>\n );\n}\n\ninterface AuthorMetaProps {\n readonly authorName: string;\n readonly authorPhoto: string | null;\n readonly createdAt: string;\n}\n\nfunction AuthorMeta({ authorName, authorPhoto, createdAt }: AuthorMetaProps) {\n return (\n <div className=\"flex items-center gap-2\">\n <AuthorAvatar name={authorName} photoUrl={authorPhoto} />\n <div className=\"flex items-center gap-1\">\n <Text size=\"xs\" weight=\"semibold\" color=\"text-primary-700\">\n Postado por {authorName}\n </Text>\n <Text size=\"xs\" color=\"text-text-600\">\n • {formatForumDate(createdAt)}\n </Text>\n </div>\n </div>\n );\n}\n\ninterface PostActionsMenuProps {\n readonly onEdit: () => void;\n readonly onDelete: () => void;\n}\n\nfunction PostActionsMenu({ onEdit, onDelete }: PostActionsMenuProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label=\"Abrir ações do post\"\n className=\"p-1 rounded hover:bg-background-100 transition-colors shrink-0\"\n >\n <DotsThreeVerticalIcon size={20} className=\"text-text-600\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n iconLeft={<PencilSimpleIcon size={16} />}\n onClick={onEdit}\n >\n Editar\n </DropdownMenuItem>\n <DropdownMenuItem iconLeft={<TrashIcon size={16} />} onClick={onDelete}>\n Deletar\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\ninterface PostContentModalProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly submitLabel: string;\n readonly content: string;\n readonly imageUrl: string | undefined;\n readonly isSubmitting: boolean;\n readonly onContentChange: (value: string) => void;\n readonly onClose: () => void;\n readonly onSubmit: () => void;\n readonly onUploadImage?: (file: File) => Promise<string>;\n readonly onImageUploaded: (url: string) => void;\n readonly maxLength: number;\n /** Use RichEditor with toolbar instead of plain TextArea */\n readonly richText?: boolean;\n /** Show \"Este fórum vale nota no boletim?\" question (professor only) */\n readonly showGradeQuestion?: boolean;\n readonly countsForGrade?: boolean;\n readonly onCountsForGradeChange?: (value: boolean) => void;\n}\n\nfunction PostContentModal({\n isOpen,\n title,\n submitLabel,\n content,\n imageUrl,\n isSubmitting,\n onContentChange,\n onClose,\n onSubmit,\n onUploadImage,\n onImageUploaded,\n maxLength,\n richText = false,\n showGradeQuestion = false,\n countsForGrade,\n onCountsForGradeChange,\n}: PostContentModalProps) {\n const handleImageSelect = useCallback(\n async (file: File) => {\n if (!onUploadImage) return;\n try {\n const url = await onUploadImage(file);\n onImageUploaded(url);\n } catch {\n // silently fail — consuming app should handle upload errors\n }\n },\n [onUploadImage, onImageUploaded]\n );\n\n // Determine if content is effectively empty or exceeds the limit\n const plainTextContent = richText ? stripHtml(content) : content;\n const isContentEmpty = !plainTextContent.trim();\n const exceedsMaxLength = plainTextContent.trim().length > maxLength;\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size=\"md\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={onClose}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n disabled={isContentEmpty || exceedsMaxLength || isSubmitting}\n onClick={onSubmit}\n >\n {submitLabel}\n </Button>\n </div>\n }\n >\n <div className=\"flex flex-col gap-3\">\n {richText ? (\n // key forces remount (and thus reset) whenever the modal opens/closes\n <RichEditor\n key={String(isOpen)}\n content={content}\n onChange={(data) => onContentChange(data.html)}\n placeholder=\"Escreva o conteúdo do seu post aqui.\"\n />\n ) : (\n <TextArea\n placeholder=\"Escreva o conteúdo do seu post aqui.\"\n value={content}\n onChange={(e) => onContentChange(e.target.value)}\n autoResize\n minHeight={120}\n maxLength={maxLength}\n />\n )}\n\n <ImageUpload\n variant=\"compact\"\n onFileSelect={handleImageSelect}\n onRemoveFile={() => onImageUploaded('')}\n buttonIcon={<ImageIcon size={16} />}\n />\n {imageUrl && (\n <img\n src={imageUrl}\n alt=\"Preview da imagem\"\n className=\"max-h-40 rounded-lg object-contain\"\n />\n )}\n\n {showGradeQuestion && (\n <div className=\"flex flex-col gap-2\">\n <Text size=\"sm\" weight=\"medium\">\n Este fórum vale nota no boletim?\n </Text>\n <div className=\"flex items-center gap-4\">\n <Radio\n name=\"countsForGrade\"\n value=\"yes\"\n label=\"Sim\"\n size=\"small\"\n checked={countsForGrade === true}\n onChange={() => onCountsForGradeChange?.(true)}\n />\n <Radio\n name=\"countsForGrade\"\n value=\"no\"\n label=\"Não\"\n size=\"small\"\n checked={countsForGrade === false}\n onChange={() => onCountsForGradeChange?.(false)}\n />\n </div>\n </div>\n )}\n </div>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nfunction TopicListSkeleton() {\n return (\n <SkeletonList items={3} showAvatar showTitle showDescription lines={2} />\n );\n}\n\nfunction TopicDetailSkeleton() {\n return (\n <SkeletonCard\n showAvatar\n showTitle\n showDescription\n showActions={false}\n lines={2}\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\nexport type ForumProps = {\n /** API client with forum CRUD methods */\n readonly apiClient: ForumApiClient;\n /** Current user's userInstitutionId — used to identify own posts */\n readonly currentUserId: string;\n /** User role — enables professor-specific features (e.g. grade question on create) */\n readonly userRole?: PROFILE_ROLES.STUDENT | PROFILE_ROLES.TEACHER;\n /** Forum page title */\n readonly title?: string;\n /** Forum page subtitle */\n readonly subtitle?: string;\n /** Callback to upload an image file and return its hosted URL */\n readonly onUploadImage?: (file: File) => Promise<string>;\n /**\n * Callback to evaluate a reply (teacher only).\n * If provided, an \"Avaliar\" button appears on each reply when the topic counts for grade.\n */\n readonly onEvaluateReply?: (replyId: string, grade: number) => Promise<void>;\n /** Additional CSS classes */\n readonly className?: string;\n};\n\nexport function Forum({\n apiClient,\n currentUserId,\n userRole = PROFILE_ROLES.STUDENT,\n title = 'Fórum',\n subtitle = 'Espaço para troca de conhecimento',\n onUploadImage,\n onEvaluateReply,\n className,\n}: ForumProps) {\n const isTeacher = userRole === PROFILE_ROLES.TEACHER;\n const [view, setView] = useState<'list' | 'detail'>('list');\n const [topics, setTopics] = useState<ForumTopic[]>([]);\n const [isLoadingTopics, setIsLoadingTopics] = useState(false);\n const [selectedTopic, setSelectedTopic] = useState<ForumTopic | null>(null);\n const [replies, setReplies] = useState<ForumReply[]>([]);\n const [isLoadingTopic, setIsLoadingTopic] = useState(false);\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n // Create post modal state\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createContent, setCreateContent] = useState('');\n const [createImageUrl, setCreateImageUrl] = useState<string | undefined>();\n const [createCountsForGrade, setCreateCountsForGrade] = useState<\n boolean | undefined\n >();\n\n // Reply modal state\n const [isReplyModalOpen, setIsReplyModalOpen] = useState(false);\n const [replyContent, setReplyContent] = useState('');\n const [replyImageUrl, setReplyImageUrl] = useState<string | undefined>();\n\n // Edit topic modal state\n const [editingTopic, setEditingTopic] = useState<ForumTopic | null>(null);\n const [editTopicContent, setEditTopicContent] = useState('');\n const [editTopicImageUrl, setEditTopicImageUrl] = useState<\n string | undefined\n >();\n\n // Edit reply modal state\n const [editingReply, setEditingReply] = useState<ForumReply | null>(null);\n const [editReplyContent, setEditReplyContent] = useState('');\n const [editReplyImageUrl, setEditReplyImageUrl] = useState<\n string | undefined\n >();\n\n // Evaluate reply modal state (teacher only)\n const [evaluatingReplyId, setEvaluatingReplyId] = useState<string | null>(\n null\n );\n const [gradeValue, setGradeValue] = useState<number | null>(null);\n const [isEditingGrade, setIsEditingGrade] = useState(false);\n\n // Delete confirmation state\n const [pendingDelete, setPendingDelete] = useState<\n { type: 'topic'; id: string } | { type: 'reply'; id: string } | null\n >(null);\n\n const fetchTopics = useCallback(async () => {\n setIsLoadingTopics(true);\n try {\n const response = await apiClient.getTopics({ limit: 50 });\n setTopics(response.topics);\n } finally {\n setIsLoadingTopics(false);\n }\n }, [apiClient]);\n\n const fetchTopic = useCallback(\n async (topicId: string) => {\n setIsLoadingTopic(true);\n try {\n const response = await apiClient.getTopic(topicId);\n setSelectedTopic(response.topic);\n setReplies(response.replies);\n } finally {\n setIsLoadingTopic(false);\n }\n },\n [apiClient]\n );\n\n useEffect(() => {\n fetchTopics();\n }, [fetchTopics]);\n\n const handleTopicClick = useCallback(\n (topic: ForumTopic) => {\n setSelectedTopic(topic);\n setView('detail');\n fetchTopic(topic.id);\n },\n [fetchTopic]\n );\n\n const handleBack = useCallback(() => {\n setView('list');\n setSelectedTopic(null);\n setReplies([]);\n fetchTopics();\n }, [fetchTopics]);\n\n // Create topic\n const handleCloseCreateModal = useCallback(() => {\n setIsCreateModalOpen(false);\n setCreateContent('');\n setCreateImageUrl(undefined);\n setCreateCountsForGrade(undefined);\n }, []);\n\n const handleCreateTopic = useCallback(async () => {\n if (!createContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.createTopic({\n content: createContent.trim(),\n ...(createImageUrl && { imageUrl: createImageUrl }),\n ...(isTeacher &&\n createCountsForGrade !== undefined && {\n countsForGrade: createCountsForGrade,\n }),\n });\n handleCloseCreateModal();\n await fetchTopics();\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n createContent,\n createImageUrl,\n createCountsForGrade,\n isTeacher,\n handleCloseCreateModal,\n fetchTopics,\n ]);\n\n // Edit topic\n const handleOpenEditTopic = useCallback((topic: ForumTopic) => {\n setEditingTopic(topic);\n setEditTopicContent(topic.content);\n setEditTopicImageUrl(topic.imageUrl ?? undefined);\n }, []);\n\n const handleCloseEditTopic = useCallback(() => {\n setEditingTopic(null);\n setEditTopicContent('');\n setEditTopicImageUrl(undefined);\n }, []);\n\n const handleEditTopic = useCallback(async () => {\n if (!editingTopic || !editTopicContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.updateTopic(editingTopic.id, {\n content: editTopicContent.trim(),\n imageUrl: editTopicImageUrl ?? null,\n });\n handleCloseEditTopic();\n if (view === 'list') {\n await fetchTopics();\n } else {\n await fetchTopic(editingTopic.id);\n }\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n editingTopic,\n editTopicContent,\n editTopicImageUrl,\n handleCloseEditTopic,\n view,\n fetchTopics,\n fetchTopic,\n ]);\n\n const handleDeleteTopic = useCallback(\n async (topicId: string) => {\n setIsSubmitting(true);\n try {\n await apiClient.deleteTopic(topicId);\n if (view === 'detail') {\n handleBack();\n } else {\n await fetchTopics();\n }\n } finally {\n setIsSubmitting(false);\n setPendingDelete(null);\n }\n },\n [apiClient, view, handleBack, fetchTopics]\n );\n\n // Reply\n const handleCloseReplyModal = useCallback(() => {\n setIsReplyModalOpen(false);\n setReplyContent('');\n setReplyImageUrl(undefined);\n }, []);\n\n const handleCreateReply = useCallback(async () => {\n if (!selectedTopic || !replyContent.trim()) return;\n setIsSubmitting(true);\n try {\n await apiClient.createReply(selectedTopic.id, {\n content: replyContent.trim(),\n ...(replyImageUrl && { imageUrl: replyImageUrl }),\n });\n handleCloseReplyModal();\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n selectedTopic,\n replyContent,\n replyImageUrl,\n handleCloseReplyModal,\n fetchTopic,\n ]);\n\n // Edit reply\n const handleOpenEditReply = useCallback((reply: ForumReply) => {\n setEditingReply(reply);\n setEditReplyContent(reply.content);\n setEditReplyImageUrl(reply.imageUrl ?? undefined);\n }, []);\n\n const handleCloseEditReply = useCallback(() => {\n setEditingReply(null);\n setEditReplyContent('');\n setEditReplyImageUrl(undefined);\n }, []);\n\n const handleEditReply = useCallback(async () => {\n if (!editingReply || !editReplyContent.trim() || !selectedTopic) return;\n setIsSubmitting(true);\n try {\n await apiClient.updateReply(editingReply.id, {\n content: editReplyContent.trim(),\n imageUrl: editReplyImageUrl ?? null,\n });\n handleCloseEditReply();\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n }\n }, [\n apiClient,\n editingReply,\n editReplyContent,\n editReplyImageUrl,\n handleCloseEditReply,\n selectedTopic,\n fetchTopic,\n ]);\n\n const handleDeleteReply = useCallback(\n async (replyId: string) => {\n if (!selectedTopic) return;\n setIsSubmitting(true);\n try {\n await apiClient.deleteReply(replyId);\n await fetchTopic(selectedTopic.id);\n } finally {\n setIsSubmitting(false);\n setPendingDelete(null);\n }\n },\n [apiClient, selectedTopic, fetchTopic]\n );\n\n const handleCloseEvaluateModal = useCallback(() => {\n setEvaluatingReplyId(null);\n setGradeValue(null);\n setIsEditingGrade(false);\n }, []);\n\n const handleOpenEvaluate = useCallback((reply: ForumReply) => {\n setEvaluatingReplyId(reply.id);\n setGradeValue(null);\n setIsEditingGrade(false);\n }, []);\n\n const handleOpenEditGrade = useCallback((reply: ForumReply) => {\n setEvaluatingReplyId(reply.id);\n setGradeValue(reply.grade ?? null);\n setIsEditingGrade(true);\n }, []);\n\n const isValidGrade =\n gradeValue !== null &&\n Number.isInteger(gradeValue) &&\n gradeValue >= 0 &&\n gradeValue <= 10;\n\n const handleSubmitEvaluation = useCallback(async () => {\n if (!evaluatingReplyId || !isValidGrade || !onEvaluateReply) return;\n setIsSubmitting(true);\n try {\n await onEvaluateReply(evaluatingReplyId, gradeValue);\n // Optimistically update grade in local state\n setReplies((prev) =>\n prev.map((r) =>\n r.id === evaluatingReplyId ? { ...r, grade: gradeValue } : r\n )\n );\n handleCloseEvaluateModal();\n } finally {\n setIsSubmitting(false);\n }\n }, [\n evaluatingReplyId,\n gradeValue,\n isValidGrade,\n onEvaluateReply,\n handleCloseEvaluateModal,\n ]);\n\n const handleConfirmDelete = useCallback(async () => {\n if (!pendingDelete) return;\n if (pendingDelete.type === 'topic') {\n await handleDeleteTopic(pendingDelete.id);\n } else {\n await handleDeleteReply(pendingDelete.id);\n }\n }, [pendingDelete, handleDeleteTopic, handleDeleteReply]);\n\n /* ---- Extracted render blocks (avoids nested ternaries — SonarQube) ---- */\n\n const topicListContent = (() => {\n if (isLoadingTopics) return <TopicListSkeleton />;\n\n if (topics.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-16 border border-border-200 rounded-lg\">\n <Text size=\"md\" color=\"text-text-500\">\n Nenhum tópico ainda. Seja o primeiro a criar um post!\n </Text>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n {topics.map((topic) => (\n <div\n key={topic.id}\n className=\"flex items-start bg-white border border-border-100 rounded-lg pt-4 pr-2 pb-4 pl-2 shadow-sm\"\n >\n {/* Clickable content area */}\n <button\n type=\"button\"\n className=\"flex flex-col gap-3 flex-1 min-w-0 text-left hover:opacity-90 transition-opacity\"\n onClick={() => handleTopicClick(topic)}\n >\n <AuthorMeta\n authorName={topic.authorName}\n authorPhoto={topic.authorPhoto}\n createdAt={topic.createdAt}\n />\n <Text size=\"sm\" className=\"leading-normal line-clamp-3 ml-10\">\n {stripHtml(topic.content)}\n </Text>\n <div className=\"flex items-center gap-1 text-text-600 ml-10\">\n <ChatCircleTextIcon size={16} />\n <Text size=\"xs\">{formatReplyCount(topic.replyCount)}</Text>\n </div>\n </button>\n\n {/* Actions menu — only for own posts */}\n {topic.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditTopic(topic)}\n onDelete={() =>\n setPendingDelete({ type: 'topic', id: topic.id })\n }\n />\n )}\n </div>\n ))}\n </div>\n );\n })();\n\n const topicDetailContent = (() => {\n if (isLoadingTopic) return <TopicDetailSkeleton />;\n\n if (!selectedTopic) return null;\n\n return (\n <>\n {/* Original post */}\n <div className=\"flex items-start gap-2\">\n <div className=\"flex flex-col gap-3 flex-1 min-w-0\">\n <AuthorMeta\n authorName={selectedTopic.authorName}\n authorPhoto={selectedTopic.authorPhoto}\n createdAt={selectedTopic.createdAt}\n />\n <div\n className=\"text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none\"\n dangerouslySetInnerHTML={{\n __html: sanitizeHtmlForDisplay(selectedTopic.content),\n }}\n />\n {selectedTopic.imageUrl && (\n <img\n src={selectedTopic.imageUrl}\n alt=\"Imagem do post\"\n className=\"ml-10 max-w-lg rounded-lg\"\n />\n )}\n </div>\n {selectedTopic.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditTopic(selectedTopic)}\n onDelete={() =>\n setPendingDelete({ type: 'topic', id: selectedTopic.id })\n }\n />\n )}\n </div>\n\n {/* Replies header */}\n <div className=\"flex items-center justify-between\">\n <Text size=\"sm\" color=\"text-text-600\">\n {formatReplyCount(selectedTopic.replyCount)}\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setIsReplyModalOpen(true)}\n >\n Responder\n </Button>\n </div>\n\n {/* Replies list */}\n {replies.length > 0 && (\n <div className=\"flex flex-col divide-y divide-border-200\">\n {replies.map((reply) => (\n <div key={reply.id} className=\"flex items-start gap-2 py-4\">\n <div className=\"flex flex-col gap-3 flex-1 min-w-0\">\n <AuthorMeta\n authorName={reply.authorName}\n authorPhoto={reply.authorPhoto}\n createdAt={reply.createdAt}\n />\n <div\n className=\"text-sm text-text-950 leading-normal ml-10 prose prose-sm max-w-none\"\n dangerouslySetInnerHTML={{\n __html: sanitizeHtmlForDisplay(reply.content),\n }}\n />\n {reply.imageUrl && (\n <img\n src={reply.imageUrl}\n alt=\"Imagem da resposta\"\n className=\"ml-10 max-w-lg rounded-lg\"\n />\n )}\n {isTeacher &&\n selectedTopic?.countsForGrade &&\n onEvaluateReply &&\n (reply.grade == null ? (\n /* Not yet graded — show Avaliar button */\n <button\n type=\"button\"\n className=\"flex items-center gap-1.5 text-text-600 text-sm hover:text-text-950 transition-colors w-fit ml-10\"\n onClick={() => handleOpenEvaluate(reply)}\n >\n <CheckIcon size={16} />\n Avaliar\n </button>\n ) : (\n /* Grade already set — teacher sees it with edit pencil */\n <div className=\"flex items-center gap-1.5 ml-10\">\n <Text size=\"sm\" color=\"text-text-600\">\n Nota {reply.grade}\n </Text>\n <button\n type=\"button\"\n className=\"text-text-600 hover:text-text-950 transition-colors\"\n onClick={() => handleOpenEditGrade(reply)}\n >\n <PencilSimpleIcon size={14} />\n </button>\n </div>\n ))}\n {!isTeacher &&\n reply.grade != null &&\n reply.userInstitutionId === currentUserId &&\n selectedTopic?.countsForGrade && (\n <Text size=\"sm\" color=\"text-text-600\" className=\"ml-10\">\n Nota {reply.grade}\n </Text>\n )}\n </div>\n {reply.userInstitutionId === currentUserId && (\n <PostActionsMenu\n onEdit={() => handleOpenEditReply(reply)}\n onDelete={() =>\n setPendingDelete({ type: 'reply', id: reply.id })\n }\n />\n )}\n </div>\n ))}\n </div>\n )}\n </>\n );\n })();\n\n return (\n <div className={cn('flex flex-col gap-4', className)}>\n {view === 'list' ? (\n <>\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <Text as=\"h1\" size=\"2xl\" weight=\"bold\" color=\"text-text-950\">\n {title}\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={() => setIsCreateModalOpen(true)}\n >\n Criar post\n </Button>\n </div>\n\n {/* Subtitle + topic list */}\n <div className=\"flex flex-col gap-3\">\n <Text as=\"h2\" size=\"lg\" weight=\"semibold\" color=\"text-text-950\">\n {subtitle}\n </Text>\n\n {topicListContent}\n </div>\n </>\n ) : (\n /* Detail view */\n <>\n <button\n type=\"button\"\n className=\"flex items-center gap-2 hover:opacity-80 transition-opacity w-fit\"\n onClick={handleBack}\n >\n <CaretLeftIcon size={24} className=\"text-text-950\" />\n <Text size=\"2xl\" weight=\"bold\">\n Voltar\n </Text>\n </button>\n\n {topicDetailContent}\n </>\n )}\n\n {/* Create post modal */}\n <PostContentModal\n isOpen={isCreateModalOpen}\n title=\"Criar post\"\n submitLabel=\"Criar post\"\n content={createContent}\n imageUrl={createImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setCreateContent}\n onClose={handleCloseCreateModal}\n onSubmit={handleCreateTopic}\n onUploadImage={onUploadImage}\n onImageUploaded={setCreateImageUrl}\n maxLength={10000}\n richText\n showGradeQuestion={isTeacher}\n countsForGrade={createCountsForGrade}\n onCountsForGradeChange={setCreateCountsForGrade}\n />\n\n {/* Reply modal */}\n <PostContentModal\n isOpen={isReplyModalOpen}\n title=\"Responder\"\n submitLabel=\"Responder\"\n content={replyContent}\n imageUrl={replyImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setReplyContent}\n onClose={handleCloseReplyModal}\n onSubmit={handleCreateReply}\n onUploadImage={onUploadImage}\n onImageUploaded={setReplyImageUrl}\n maxLength={5000}\n richText\n />\n\n {/* Edit topic modal */}\n <PostContentModal\n isOpen={!!editingTopic}\n title=\"Editar\"\n submitLabel=\"Salvar\"\n content={editTopicContent}\n imageUrl={editTopicImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setEditTopicContent}\n onClose={handleCloseEditTopic}\n onSubmit={handleEditTopic}\n onUploadImage={onUploadImage}\n onImageUploaded={setEditTopicImageUrl}\n maxLength={10000}\n richText\n />\n\n {/* Edit reply modal */}\n <PostContentModal\n isOpen={!!editingReply}\n title=\"Editar\"\n submitLabel=\"Salvar\"\n content={editReplyContent}\n imageUrl={editReplyImageUrl}\n isSubmitting={isSubmitting}\n onContentChange={setEditReplyContent}\n onClose={handleCloseEditReply}\n onSubmit={handleEditReply}\n onUploadImage={onUploadImage}\n onImageUploaded={setEditReplyImageUrl}\n maxLength={5000}\n richText\n />\n\n {/* Evaluate / edit grade modal (teacher only) */}\n <Modal\n isOpen={!!evaluatingReplyId}\n onClose={handleCloseEvaluateModal}\n title={\n isEditingGrade\n ? 'Edite a nota da atividade'\n : 'Defina a nota da atividade'\n }\n size=\"md\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={handleCloseEvaluateModal}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n disabled={!isValidGrade || isSubmitting}\n onClick={handleSubmitEvaluation}\n >\n {isEditingGrade ? 'Salvar' : 'Avaliar'}\n </Button>\n </div>\n }\n >\n <Input\n type=\"number\"\n min={0}\n max={10}\n step={1}\n placeholder=\"Nota (0-10)\"\n value={gradeValue ?? ''}\n onChange={(e) => {\n const val = e.target.value;\n const parsed = Number(val);\n setGradeValue(val === '' || Number.isNaN(parsed) ? null : parsed);\n }}\n />\n </Modal>\n\n {/* Delete confirmation modal */}\n <Modal\n isOpen={!!pendingDelete}\n onClose={() => setPendingDelete(null)}\n title=\"Deseja deletar post?\"\n size=\"sm\"\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n action=\"secondary\"\n size=\"medium\"\n onClick={() => setPendingDelete(null)}\n >\n Cancelar\n </Button>\n <Button\n variant=\"solid\"\n action=\"negative\"\n size=\"medium\"\n disabled={isSubmitting}\n onClick={handleConfirmDelete}\n >\n Deletar\n </Button>\n </div>\n }\n >\n <Text size=\"md\" color=\"text-text-600\">\n Essa ação é permanente e não poderá ser desfeita.\n </Text>\n </Modal>\n </div>\n );\n}\n\nexport default Forum;\n"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
DropdownMenuContent,
|
|
9
9
|
DropdownMenuTrigger,
|
|
10
10
|
DropdownMenu_default
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X2YQT5BS.mjs";
|
|
12
12
|
import {
|
|
13
13
|
Modal_default
|
|
14
14
|
} from "./chunk-SDXANFNM.mjs";
|
|
@@ -87,4 +87,4 @@ export {
|
|
|
87
87
|
CalendarCard,
|
|
88
88
|
CalendarCard_default
|
|
89
89
|
};
|
|
90
|
-
//# sourceMappingURL=chunk-
|
|
90
|
+
//# sourceMappingURL=chunk-MUVQIQAG.mjs.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkY64LT6BLjs = require('./chunk-Y64LT6BL.js');
|
|
7
7
|
|
|
8
8
|
// src/components/Search/Search.tsx
|
|
9
9
|
var _X = require('@phosphor-icons/react/dist/csr/X');
|
|
@@ -66,7 +66,7 @@ var Search = _react.forwardRef.call(void 0,
|
|
|
66
66
|
const [dropdownOpen, setDropdownOpen] = _react.useState.call(void 0, false);
|
|
67
67
|
const [forceClose, setForceClose] = _react.useState.call(void 0, false);
|
|
68
68
|
const justSelectedRef = _react.useRef.call(void 0, false);
|
|
69
|
-
const dropdownStore = _react.useRef.call(void 0,
|
|
69
|
+
const dropdownStore = _react.useRef.call(void 0, _chunkY64LT6BLjs.createDropdownStore.call(void 0, )).current;
|
|
70
70
|
const dropdownRef = _react.useRef.call(void 0, null);
|
|
71
71
|
const inputElRef = _react.useRef.call(void 0, null);
|
|
72
72
|
const debounceTimer = _react.useRef.call(void 0, null);
|
|
@@ -237,15 +237,15 @@ var Search = _react.forwardRef.call(void 0,
|
|
|
237
237
|
}
|
|
238
238
|
) })
|
|
239
239
|
] }),
|
|
240
|
-
showDropdown && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
241
|
-
|
|
240
|
+
showDropdown && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkY64LT6BLjs.DropdownMenu_default, { open: showDropdown, onOpenChange: setDropdownOpen, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
241
|
+
_chunkY64LT6BLjs.DropdownMenuContent,
|
|
242
242
|
{
|
|
243
243
|
id: dropdownId,
|
|
244
244
|
className: "w-full mt-1",
|
|
245
245
|
style: { maxHeight: dropdownMaxHeight },
|
|
246
246
|
align: "start",
|
|
247
247
|
children: filteredOptions.length > 0 ? filteredOptions.map((option) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
248
|
-
|
|
248
|
+
_chunkY64LT6BLjs.DropdownMenuItem,
|
|
249
249
|
{
|
|
250
250
|
onClick: () => handleSelectOption(option),
|
|
251
251
|
className: "text-text-700 text-base leading-6 cursor-pointer",
|
|
@@ -266,4 +266,4 @@ var Search_default = Search;
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
exports.Search_default = Search_default;
|
|
269
|
-
//# sourceMappingURL=chunk-
|
|
269
|
+
//# sourceMappingURL=chunk-MYQ2LJWO.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7HW4PZLV.js","../src/components/Search/Search.tsx"],"names":["readOnly"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-7HW4PZLV.js","sourcesContent":[null,"import { XIcon } from '@phosphor-icons/react/dist/csr/X';\nimport { MagnifyingGlassIcon } from '@phosphor-icons/react/dist/csr/MagnifyingGlass';\nimport {\n InputHTMLAttributes,\n forwardRef,\n useState,\n useId,\n useMemo,\n useEffect,\n useRef,\n ChangeEvent,\n MouseEvent,\n KeyboardEvent,\n} from 'react';\nimport DropdownMenu, {\n DropdownMenuContent,\n DropdownMenuItem,\n createDropdownStore,\n} from '../DropdownMenu/DropdownMenu';\n\n/**\n * Search component props interface\n */\ntype SearchProps = {\n /** List of options to show in dropdown */\n options: string[];\n /** Callback when an option is selected from dropdown */\n onSelect?: (value: string) => void;\n /** Callback when search input changes */\n onSearch?: (query: string) => void;\n /** Control dropdown visibility externally */\n showDropdown?: boolean;\n /** Callback when dropdown open state changes */\n onDropdownChange?: (open: boolean) => void;\n /** Maximum height of dropdown in pixels */\n dropdownMaxHeight?: number;\n /** Text to show when no results are found */\n noResultsText?: string;\n /** Debounce delay in ms for onSearch. Default 0 (no debounce). */\n debounceMs?: number;\n /** Additional CSS classes to apply to the input */\n className?: string;\n /** Additional CSS classes to apply to the container */\n containerClassName?: string;\n /** Callback when clear button is clicked */\n onClear?: () => void;\n} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'onSelect'>;\n\n/**\n * Search component for Analytica Ensino platforms\n *\n * A specialized search input component with dropdown suggestions.\n * Features filtering, keyboard navigation, and customizable options.\n *\n * @param options - Array of search options to display in dropdown\n * @param onSelect - Callback when an option is selected\n * @param onSearch - Callback when search query changes\n * @param placeholder - Placeholder text for the input\n * @param noResultsText - Text to show when no results are found\n * @param dropdownMaxHeight - Maximum height of dropdown in pixels\n * @param className - Additional CSS classes for the input\n * @param containerClassName - Additional CSS classes for the container\n * @param props - All other standard input HTML attributes\n * @returns A styled search input with dropdown functionality\n *\n * @example\n * ```tsx\n * // Basic search\n * <Search\n * options={['Filosofia', 'Física', 'Matemática']}\n * placeholder=\"Buscar matéria...\"\n * onSelect={(value) => console.log('Selected:', value)}\n * />\n *\n * // With custom filtering\n * <Search\n * options={materias}\n * onSearch={(query) => setFilteredMaterias(filterMaterias(query))}\n * noResultsText=\"Nenhum resultado encontrado\"\n * />\n * ```\n */\n\n/**\n * Filter options based on search query\n */\nconst filterOptions = (options: string[], query: string): string[] => {\n if (!query || query.length < 1) return [];\n\n return options.filter((option) =>\n option.toLowerCase().includes(query.toLowerCase())\n );\n};\n\n/**\n * Updates input value and creates appropriate change event\n */\nconst updateInputValue = (\n value: string,\n ref:\n | { current: HTMLInputElement | null }\n | ((instance: HTMLInputElement | null) => void)\n | null,\n onChange?: (event: ChangeEvent<HTMLInputElement>) => void\n) => {\n if (!onChange) return;\n\n if (ref && 'current' in ref && ref.current) {\n ref.current.value = value;\n const event = new Event('input', { bubbles: true });\n Object.defineProperty(event, 'target', {\n writable: false,\n value: ref.current,\n });\n onChange(event as unknown as ChangeEvent<HTMLInputElement>);\n } else {\n // Fallback for cases where ref is not available\n const event = {\n target: { value },\n currentTarget: { value },\n } as ChangeEvent<HTMLInputElement>;\n onChange(event);\n }\n};\n\nconst Search = forwardRef<HTMLInputElement, SearchProps>(\n (\n {\n options = [],\n onSelect,\n onSearch,\n showDropdown: controlledShowDropdown,\n onDropdownChange,\n dropdownMaxHeight = 240,\n noResultsText = 'Nenhum resultado encontrado',\n className = '',\n containerClassName = '',\n disabled,\n readOnly,\n id,\n onClear,\n value,\n onChange,\n placeholder = 'Buscar...',\n onKeyDown: userOnKeyDown,\n debounceMs = 0,\n ...props\n },\n ref\n ) => {\n // Dropdown state and logic\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const [forceClose, setForceClose] = useState(false);\n const justSelectedRef = useRef(false);\n const dropdownStore = useRef(createDropdownStore()).current;\n const dropdownRef = useRef<HTMLDivElement>(null);\n const inputElRef = useRef<HTMLInputElement>(null);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n };\n }, []);\n\n // Filter options based on input value\n const filteredOptions = useMemo(() => {\n if (!options.length) {\n return [];\n }\n const filtered = filterOptions(options, (value as string) || '');\n return filtered;\n }, [options, value]);\n\n // Control dropdown visibility\n const showDropdown =\n !forceClose &&\n (controlledShowDropdown ??\n (dropdownOpen && value && String(value).length > 0));\n\n // Helper to keep all consumers in sync\n const setOpenAndNotify = (open: boolean) => {\n setDropdownOpen(open);\n dropdownStore.setState({ open });\n onDropdownChange?.(open);\n };\n\n // Handle dropdown visibility changes\n useEffect(() => {\n // Don't reopen dropdown if we just selected an option\n if (justSelectedRef.current) {\n justSelectedRef.current = false;\n return;\n }\n // Respect forceClose even if value is non-empty\n if (forceClose) {\n setOpenAndNotify(false);\n return;\n }\n\n const shouldShow = Boolean(value && String(value).length > 0);\n setOpenAndNotify(shouldShow);\n }, [value, forceClose, onDropdownChange, dropdownStore]);\n\n // Handle option selection\n const handleSelectOption = (option: string) => {\n justSelectedRef.current = true; // Prevent immediate dropdown reopen\n setForceClose(true); // Force dropdown to close immediately\n onSelect?.(option);\n setOpenAndNotify(false);\n\n // Update input value if onChange is provided\n updateInputValue(option, ref, onChange);\n };\n\n // Handle click outside dropdown\n useEffect(() => {\n const handleClickOutside = (event: globalThis.MouseEvent) => {\n if (\n dropdownRef.current &&\n !dropdownRef.current.contains(event.target as Node)\n ) {\n setOpenAndNotify(false);\n }\n };\n\n if (showDropdown) {\n document.addEventListener('click', handleClickOutside);\n }\n\n return () => {\n document.removeEventListener('click', handleClickOutside);\n };\n }, [showDropdown, dropdownStore, onDropdownChange]);\n\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `search-${generatedId}`;\n const dropdownId = `${inputId}-dropdown`;\n\n // Handle clear button\n const handleClear = () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = null;\n }\n if (onClear) {\n onClear();\n } else {\n updateInputValue('', ref, onChange);\n }\n };\n\n // Handle clear button click - mantém foco no input\n const handleClearClick = (e: MouseEvent) => {\n e.preventDefault(); // Evita que o input perca foco\n e.stopPropagation(); // Para propagação do evento\n handleClear();\n };\n\n // Handle search icon click - focus on input\n const handleSearchIconClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setTimeout(() => {\n inputElRef.current?.focus();\n }, 0);\n };\n\n // Handle input change\n const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {\n setForceClose(false); // Allow dropdown to open when user types\n onChange?.(e);\n if (debounceMs > 0) {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n const value = e.target.value;\n debounceTimer.current = setTimeout(() => {\n onSearch?.(value);\n }, debounceMs);\n } else {\n onSearch?.(e.target.value);\n }\n };\n\n // Handle keyboard events\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n // Let consumer run first; if they prevent default, skip our logic\n userOnKeyDown?.(e);\n if (e.defaultPrevented) return;\n\n if (e.key === 'Enter') {\n e.preventDefault();\n\n // If dropdown is open and there are filtered options, select the first one\n if (showDropdown && filteredOptions.length > 0) {\n handleSelectOption(filteredOptions[0]);\n } else if (value) {\n // If no dropdown or no options, execute search\n onSearch?.(String(value));\n setForceClose(true);\n setOpenAndNotify(false);\n }\n }\n };\n\n // Helper function for input state classes\n const getInputStateClasses = (disabled?: boolean, readOnly?: boolean) => {\n if (disabled) return 'cursor-not-allowed opacity-40';\n if (readOnly) return 'cursor-default focus:outline-none !text-text-900';\n return 'hover:border-border-400';\n };\n\n // Determine which icon to show\n const hasValue = String(value ?? '').length > 0;\n const showClearButton = hasValue && !disabled && !readOnly;\n const showSearchIcon = !hasValue && !disabled && !readOnly;\n\n return (\n <div\n ref={dropdownRef}\n className={`w-full max-w-lg md:w-[488px] ${containerClassName}`}\n >\n {/* Search Input Container */}\n <div className=\"relative flex items-center\">\n {/* Search Input Field */}\n <input\n ref={(node) => {\n // Forward to parent\n if (ref) {\n if (typeof ref === 'function') ref(node);\n else\n (ref as { current: HTMLInputElement | null }).current = node;\n }\n // Keep our own handle\n inputElRef.current = node;\n }}\n id={inputId}\n type=\"text\"\n className={`w-full py-0 px-4 pr-10 font-normal text-text-900 focus:outline-primary-950 border rounded-full bg-background focus:bg-primary-50 border-border-300 focus:border-2 focus:border-primary-950 h-10 placeholder:text-text-600 ${getInputStateClasses(disabled, readOnly)} ${className}`}\n value={value}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n readOnly={readOnly}\n placeholder={placeholder}\n aria-expanded={showDropdown ? 'true' : undefined}\n aria-haspopup={options.length > 0 ? 'listbox' : undefined}\n aria-controls={showDropdown ? dropdownId : undefined}\n aria-autocomplete=\"list\"\n role={options.length > 0 ? 'combobox' : undefined}\n {...props}\n />\n\n {/* Right Icon - Clear Button */}\n {showClearButton && (\n <div className=\"absolute right-3 top-1/2 transform -translate-y-1/2\">\n <button\n type=\"button\"\n className=\"p-0 border-0 bg-transparent cursor-pointer\"\n onMouseDown={handleClearClick}\n aria-label=\"Limpar busca\"\n >\n <span className=\"w-6 h-6 text-text-800 flex items-center justify-center hover:text-text-600 transition-colors\">\n <XIcon />\n </span>\n </button>\n </div>\n )}\n\n {/* Right Icon - Search Icon */}\n {showSearchIcon && (\n <div className=\"absolute right-3 top-1/2 transform -translate-y-1/2\">\n <button\n type=\"button\"\n className=\"p-0 border-0 bg-transparent cursor-pointer\"\n onMouseDown={handleSearchIconClick}\n aria-label=\"Buscar\"\n >\n <span className=\"w-6 h-6 text-text-800 flex items-center justify-center hover:text-text-600 transition-colors\">\n <MagnifyingGlassIcon />\n </span>\n </button>\n </div>\n )}\n </div>\n\n {/* Search Dropdown */}\n {showDropdown && (\n <DropdownMenu open={showDropdown} onOpenChange={setDropdownOpen}>\n <DropdownMenuContent\n id={dropdownId}\n className=\"w-full mt-1\"\n style={{ maxHeight: dropdownMaxHeight }}\n align=\"start\"\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option) => (\n <DropdownMenuItem\n key={option}\n onClick={() => handleSelectOption(option)}\n className=\"text-text-700 text-base leading-6 cursor-pointer\"\n >\n {option}\n </DropdownMenuItem>\n ))\n ) : (\n <div className=\"px-3 py-3 text-text-700 text-base\">\n {noResultsText}\n </div>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n );\n }\n);\n\nSearch.displayName = 'Search';\n\nexport default Search;\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-MYQ2LJWO.js","../src/components/Search/Search.tsx"],"names":["readOnly"],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-MYQ2LJWO.js","sourcesContent":[null,"import { XIcon } from '@phosphor-icons/react/dist/csr/X';\nimport { MagnifyingGlassIcon } from '@phosphor-icons/react/dist/csr/MagnifyingGlass';\nimport {\n InputHTMLAttributes,\n forwardRef,\n useState,\n useId,\n useMemo,\n useEffect,\n useRef,\n ChangeEvent,\n MouseEvent,\n KeyboardEvent,\n} from 'react';\nimport DropdownMenu, {\n DropdownMenuContent,\n DropdownMenuItem,\n createDropdownStore,\n} from '../DropdownMenu/DropdownMenu';\n\n/**\n * Search component props interface\n */\ntype SearchProps = {\n /** List of options to show in dropdown */\n options: string[];\n /** Callback when an option is selected from dropdown */\n onSelect?: (value: string) => void;\n /** Callback when search input changes */\n onSearch?: (query: string) => void;\n /** Control dropdown visibility externally */\n showDropdown?: boolean;\n /** Callback when dropdown open state changes */\n onDropdownChange?: (open: boolean) => void;\n /** Maximum height of dropdown in pixels */\n dropdownMaxHeight?: number;\n /** Text to show when no results are found */\n noResultsText?: string;\n /** Debounce delay in ms for onSearch. Default 0 (no debounce). */\n debounceMs?: number;\n /** Additional CSS classes to apply to the input */\n className?: string;\n /** Additional CSS classes to apply to the container */\n containerClassName?: string;\n /** Callback when clear button is clicked */\n onClear?: () => void;\n} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'onSelect'>;\n\n/**\n * Search component for Analytica Ensino platforms\n *\n * A specialized search input component with dropdown suggestions.\n * Features filtering, keyboard navigation, and customizable options.\n *\n * @param options - Array of search options to display in dropdown\n * @param onSelect - Callback when an option is selected\n * @param onSearch - Callback when search query changes\n * @param placeholder - Placeholder text for the input\n * @param noResultsText - Text to show when no results are found\n * @param dropdownMaxHeight - Maximum height of dropdown in pixels\n * @param className - Additional CSS classes for the input\n * @param containerClassName - Additional CSS classes for the container\n * @param props - All other standard input HTML attributes\n * @returns A styled search input with dropdown functionality\n *\n * @example\n * ```tsx\n * // Basic search\n * <Search\n * options={['Filosofia', 'Física', 'Matemática']}\n * placeholder=\"Buscar matéria...\"\n * onSelect={(value) => console.log('Selected:', value)}\n * />\n *\n * // With custom filtering\n * <Search\n * options={materias}\n * onSearch={(query) => setFilteredMaterias(filterMaterias(query))}\n * noResultsText=\"Nenhum resultado encontrado\"\n * />\n * ```\n */\n\n/**\n * Filter options based on search query\n */\nconst filterOptions = (options: string[], query: string): string[] => {\n if (!query || query.length < 1) return [];\n\n return options.filter((option) =>\n option.toLowerCase().includes(query.toLowerCase())\n );\n};\n\n/**\n * Updates input value and creates appropriate change event\n */\nconst updateInputValue = (\n value: string,\n ref:\n | { current: HTMLInputElement | null }\n | ((instance: HTMLInputElement | null) => void)\n | null,\n onChange?: (event: ChangeEvent<HTMLInputElement>) => void\n) => {\n if (!onChange) return;\n\n if (ref && 'current' in ref && ref.current) {\n ref.current.value = value;\n const event = new Event('input', { bubbles: true });\n Object.defineProperty(event, 'target', {\n writable: false,\n value: ref.current,\n });\n onChange(event as unknown as ChangeEvent<HTMLInputElement>);\n } else {\n // Fallback for cases where ref is not available\n const event = {\n target: { value },\n currentTarget: { value },\n } as ChangeEvent<HTMLInputElement>;\n onChange(event);\n }\n};\n\nconst Search = forwardRef<HTMLInputElement, SearchProps>(\n (\n {\n options = [],\n onSelect,\n onSearch,\n showDropdown: controlledShowDropdown,\n onDropdownChange,\n dropdownMaxHeight = 240,\n noResultsText = 'Nenhum resultado encontrado',\n className = '',\n containerClassName = '',\n disabled,\n readOnly,\n id,\n onClear,\n value,\n onChange,\n placeholder = 'Buscar...',\n onKeyDown: userOnKeyDown,\n debounceMs = 0,\n ...props\n },\n ref\n ) => {\n // Dropdown state and logic\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const [forceClose, setForceClose] = useState(false);\n const justSelectedRef = useRef(false);\n const dropdownStore = useRef(createDropdownStore()).current;\n const dropdownRef = useRef<HTMLDivElement>(null);\n const inputElRef = useRef<HTMLInputElement>(null);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n };\n }, []);\n\n // Filter options based on input value\n const filteredOptions = useMemo(() => {\n if (!options.length) {\n return [];\n }\n const filtered = filterOptions(options, (value as string) || '');\n return filtered;\n }, [options, value]);\n\n // Control dropdown visibility\n const showDropdown =\n !forceClose &&\n (controlledShowDropdown ??\n (dropdownOpen && value && String(value).length > 0));\n\n // Helper to keep all consumers in sync\n const setOpenAndNotify = (open: boolean) => {\n setDropdownOpen(open);\n dropdownStore.setState({ open });\n onDropdownChange?.(open);\n };\n\n // Handle dropdown visibility changes\n useEffect(() => {\n // Don't reopen dropdown if we just selected an option\n if (justSelectedRef.current) {\n justSelectedRef.current = false;\n return;\n }\n // Respect forceClose even if value is non-empty\n if (forceClose) {\n setOpenAndNotify(false);\n return;\n }\n\n const shouldShow = Boolean(value && String(value).length > 0);\n setOpenAndNotify(shouldShow);\n }, [value, forceClose, onDropdownChange, dropdownStore]);\n\n // Handle option selection\n const handleSelectOption = (option: string) => {\n justSelectedRef.current = true; // Prevent immediate dropdown reopen\n setForceClose(true); // Force dropdown to close immediately\n onSelect?.(option);\n setOpenAndNotify(false);\n\n // Update input value if onChange is provided\n updateInputValue(option, ref, onChange);\n };\n\n // Handle click outside dropdown\n useEffect(() => {\n const handleClickOutside = (event: globalThis.MouseEvent) => {\n if (\n dropdownRef.current &&\n !dropdownRef.current.contains(event.target as Node)\n ) {\n setOpenAndNotify(false);\n }\n };\n\n if (showDropdown) {\n document.addEventListener('click', handleClickOutside);\n }\n\n return () => {\n document.removeEventListener('click', handleClickOutside);\n };\n }, [showDropdown, dropdownStore, onDropdownChange]);\n\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `search-${generatedId}`;\n const dropdownId = `${inputId}-dropdown`;\n\n // Handle clear button\n const handleClear = () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = null;\n }\n if (onClear) {\n onClear();\n } else {\n updateInputValue('', ref, onChange);\n }\n };\n\n // Handle clear button click - mantém foco no input\n const handleClearClick = (e: MouseEvent) => {\n e.preventDefault(); // Evita que o input perca foco\n e.stopPropagation(); // Para propagação do evento\n handleClear();\n };\n\n // Handle search icon click - focus on input\n const handleSearchIconClick = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setTimeout(() => {\n inputElRef.current?.focus();\n }, 0);\n };\n\n // Handle input change\n const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {\n setForceClose(false); // Allow dropdown to open when user types\n onChange?.(e);\n if (debounceMs > 0) {\n if (debounceTimer.current) clearTimeout(debounceTimer.current);\n const value = e.target.value;\n debounceTimer.current = setTimeout(() => {\n onSearch?.(value);\n }, debounceMs);\n } else {\n onSearch?.(e.target.value);\n }\n };\n\n // Handle keyboard events\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n // Let consumer run first; if they prevent default, skip our logic\n userOnKeyDown?.(e);\n if (e.defaultPrevented) return;\n\n if (e.key === 'Enter') {\n e.preventDefault();\n\n // If dropdown is open and there are filtered options, select the first one\n if (showDropdown && filteredOptions.length > 0) {\n handleSelectOption(filteredOptions[0]);\n } else if (value) {\n // If no dropdown or no options, execute search\n onSearch?.(String(value));\n setForceClose(true);\n setOpenAndNotify(false);\n }\n }\n };\n\n // Helper function for input state classes\n const getInputStateClasses = (disabled?: boolean, readOnly?: boolean) => {\n if (disabled) return 'cursor-not-allowed opacity-40';\n if (readOnly) return 'cursor-default focus:outline-none !text-text-900';\n return 'hover:border-border-400';\n };\n\n // Determine which icon to show\n const hasValue = String(value ?? '').length > 0;\n const showClearButton = hasValue && !disabled && !readOnly;\n const showSearchIcon = !hasValue && !disabled && !readOnly;\n\n return (\n <div\n ref={dropdownRef}\n className={`w-full max-w-lg md:w-[488px] ${containerClassName}`}\n >\n {/* Search Input Container */}\n <div className=\"relative flex items-center\">\n {/* Search Input Field */}\n <input\n ref={(node) => {\n // Forward to parent\n if (ref) {\n if (typeof ref === 'function') ref(node);\n else\n (ref as { current: HTMLInputElement | null }).current = node;\n }\n // Keep our own handle\n inputElRef.current = node;\n }}\n id={inputId}\n type=\"text\"\n className={`w-full py-0 px-4 pr-10 font-normal text-text-900 focus:outline-primary-950 border rounded-full bg-background focus:bg-primary-50 border-border-300 focus:border-2 focus:border-primary-950 h-10 placeholder:text-text-600 ${getInputStateClasses(disabled, readOnly)} ${className}`}\n value={value}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n readOnly={readOnly}\n placeholder={placeholder}\n aria-expanded={showDropdown ? 'true' : undefined}\n aria-haspopup={options.length > 0 ? 'listbox' : undefined}\n aria-controls={showDropdown ? dropdownId : undefined}\n aria-autocomplete=\"list\"\n role={options.length > 0 ? 'combobox' : undefined}\n {...props}\n />\n\n {/* Right Icon - Clear Button */}\n {showClearButton && (\n <div className=\"absolute right-3 top-1/2 transform -translate-y-1/2\">\n <button\n type=\"button\"\n className=\"p-0 border-0 bg-transparent cursor-pointer\"\n onMouseDown={handleClearClick}\n aria-label=\"Limpar busca\"\n >\n <span className=\"w-6 h-6 text-text-800 flex items-center justify-center hover:text-text-600 transition-colors\">\n <XIcon />\n </span>\n </button>\n </div>\n )}\n\n {/* Right Icon - Search Icon */}\n {showSearchIcon && (\n <div className=\"absolute right-3 top-1/2 transform -translate-y-1/2\">\n <button\n type=\"button\"\n className=\"p-0 border-0 bg-transparent cursor-pointer\"\n onMouseDown={handleSearchIconClick}\n aria-label=\"Buscar\"\n >\n <span className=\"w-6 h-6 text-text-800 flex items-center justify-center hover:text-text-600 transition-colors\">\n <MagnifyingGlassIcon />\n </span>\n </button>\n </div>\n )}\n </div>\n\n {/* Search Dropdown */}\n {showDropdown && (\n <DropdownMenu open={showDropdown} onOpenChange={setDropdownOpen}>\n <DropdownMenuContent\n id={dropdownId}\n className=\"w-full mt-1\"\n style={{ maxHeight: dropdownMaxHeight }}\n align=\"start\"\n >\n {filteredOptions.length > 0 ? (\n filteredOptions.map((option) => (\n <DropdownMenuItem\n key={option}\n onClick={() => handleSelectOption(option)}\n className=\"text-text-700 text-base leading-6 cursor-pointer\"\n >\n {option}\n </DropdownMenuItem>\n ))\n ) : (\n <div className=\"px-3 py-3 text-text-700 text-base\">\n {noResultsText}\n </div>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n );\n }\n);\n\nSearch.displayName = 'Search';\n\nexport default Search;\n"]}
|
|
@@ -112,7 +112,7 @@ import {
|
|
|
112
112
|
} from "./chunk-WJQCWDGQ.mjs";
|
|
113
113
|
import {
|
|
114
114
|
SimulationsDetailModal
|
|
115
|
-
} from "./chunk-
|
|
115
|
+
} from "./chunk-CWGYVJTQ.mjs";
|
|
116
116
|
import {
|
|
117
117
|
createUseSimulations
|
|
118
118
|
} from "./chunk-MHRJKJBN.mjs";
|
|
@@ -291,7 +291,7 @@ import {
|
|
|
291
291
|
} from "./chunk-DCCEYMYO.mjs";
|
|
292
292
|
import {
|
|
293
293
|
Search_default
|
|
294
|
-
} from "./chunk-
|
|
294
|
+
} from "./chunk-E4MLDRLD.mjs";
|
|
295
295
|
import {
|
|
296
296
|
ProgressBar_default
|
|
297
297
|
} from "./chunk-LDBWF6EA.mjs";
|
|
@@ -333,7 +333,7 @@ import {
|
|
|
333
333
|
DropdownMenuItem,
|
|
334
334
|
DropdownMenuTrigger,
|
|
335
335
|
DropdownMenu_default
|
|
336
|
-
} from "./chunk-
|
|
336
|
+
} from "./chunk-X2YQT5BS.mjs";
|
|
337
337
|
import {
|
|
338
338
|
MicIconReadingFluency,
|
|
339
339
|
Modal_default,
|
|
@@ -20668,4 +20668,4 @@ export {
|
|
|
20668
20668
|
ExamDetailsPage,
|
|
20669
20669
|
ExamDetailsPage_default
|
|
20670
20670
|
};
|
|
20671
|
-
//# sourceMappingURL=chunk-
|
|
20671
|
+
//# sourceMappingURL=chunk-NAOHJW6H.mjs.map
|
|
@@ -18,7 +18,7 @@ var _chunkBJMKBBN3js = require('./chunk-BJMKBBN3.js');
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
var
|
|
21
|
+
var _chunkY64LT6BLjs = require('./chunk-Y64LT6BL.js');
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
var _chunkONWBFEW5js = require('./chunk-ONWBFEW5.js');
|
|
@@ -146,25 +146,25 @@ var SingleNotificationCard = ({
|
|
|
146
146
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2 w-full", children: [
|
|
147
147
|
!isRead && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-[7px] h-[7px] bg-info-300 rounded-full flex-shrink-0" }),
|
|
148
148
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-bold text-sm leading-4 text-text-950 flex-grow", children: title }),
|
|
149
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
149
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkY64LT6BLjs.DropdownMenu_default, { children: [
|
|
150
150
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
151
|
-
|
|
151
|
+
_chunkY64LT6BLjs.DropdownMenuTrigger,
|
|
152
152
|
{
|
|
153
153
|
className: "flex-shrink-0 inline-flex items-center justify-center font-medium bg-transparent text-text-950 cursor-pointer hover:bg-info-50 w-6 h-6 rounded-lg",
|
|
154
154
|
"aria-label": "Menu de a\xE7\xF5es",
|
|
155
155
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _DotsThreeVertical.DotsThreeVerticalIcon, { size: 24 })
|
|
156
156
|
}
|
|
157
157
|
),
|
|
158
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
158
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkY64LT6BLjs.DropdownMenuContent, { align: "end", className: "min-w-[160px]", children: [
|
|
159
159
|
!isRead && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
160
|
-
|
|
160
|
+
_chunkY64LT6BLjs.DropdownMenuItem,
|
|
161
161
|
{
|
|
162
162
|
onClick: handleMarkAsRead,
|
|
163
163
|
className: "text-text-950",
|
|
164
164
|
children: "Marcar como lida"
|
|
165
165
|
}
|
|
166
166
|
),
|
|
167
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
167
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkY64LT6BLjs.DropdownMenuItem, { onClick: handleDelete, className: "text-error-600", children: "Deletar" })
|
|
168
168
|
] })
|
|
169
169
|
] })
|
|
170
170
|
] }),
|
|
@@ -446,11 +446,11 @@ var NotificationCenter = ({
|
|
|
446
446
|
}
|
|
447
447
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
448
448
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
449
|
-
|
|
449
|
+
_chunkY64LT6BLjs.DropdownMenu_default,
|
|
450
450
|
{
|
|
451
451
|
...typeof isActive === "boolean" ? { open: isActive, onOpenChange: handleOpenChange } : { onOpenChange: handleOpenChange },
|
|
452
452
|
children: [
|
|
453
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
453
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkY64LT6BLjs.DropdownMenuTrigger, { className: "text-primary cursor-pointer", asChild: true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
454
454
|
_chunkBVUPM6EYjs.IconButton_default,
|
|
455
455
|
{
|
|
456
456
|
active: isActive,
|
|
@@ -473,7 +473,7 @@ var NotificationCenter = ({
|
|
|
473
473
|
}
|
|
474
474
|
) }),
|
|
475
475
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
476
|
-
|
|
476
|
+
_chunkY64LT6BLjs.DropdownMenuContent,
|
|
477
477
|
{
|
|
478
478
|
className: "min-w-[320px] max-w-[400px] max-h-[500px] overflow-hidden",
|
|
479
479
|
side: "bottom",
|
|
@@ -653,4 +653,4 @@ var NotificationCard_default = NotificationCard;
|
|
|
653
653
|
|
|
654
654
|
|
|
655
655
|
exports.LegacyNotificationCard = LegacyNotificationCard; exports.NotificationCard_default = NotificationCard_default;
|
|
656
|
-
//# sourceMappingURL=chunk-
|
|
656
|
+
//# sourceMappingURL=chunk-QHXJB5QM.js.map
|