{"version":3,"sources":["../src/components/AppHeader/AppHeader.tsx"],"sourcesContent":["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 { NotificationEntityType } 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  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  ) => 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              onDeleteById={notifications.deleteNotification}\n              onNavigateById={(entityType, entityId) => {\n                if (entityType && entityId) {\n                  onNavigateByNotification?.(entityType, entityId);\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 className=\"text-primary cursor-pointer\">\n                <IconButton\n                  active={activeStates.profile}\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"],"mappings":"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","names":[]}