import { useMemo, useState } from 'react'; import { buildTopBarNotifications, countUnreadTopBarNotifications, getTopBarCampusLabel, getTopBarInitials, getTopBarRoleLabel, } from '@/business/top-bar/selectors'; import { buildTopBarSearchResults, type TopBarContentItem, type TopBarSearchResult, } from '@/business/top-bar/search'; import { useCommunicationEvents, } from '@/business/communications/hooks'; import { getScopedModules } from '@/business/app-shell/selectors'; import { useContentCatalogPayload } from '@/business/content-catalog/hooks'; import { usePolicies, usePolicyAcknowledgments } from '@/business/policies/hooks'; import { useCurrentPersonalityResult } from '@/business/personality/queryHooks'; import { useMySafetyQuizStatus } from '@/business/safety-quiz/hooks'; import { canPersistPersonalScopeResults } from '@/business/scope/selectors'; import { useSafetyProtocols } from '@/business/safety-protocols/hooks'; import { hasPermission } from '@/business/auth/permissions'; import { CONTENT_CATALOG_TYPES } from '@/shared/constants/contentCatalog'; import { MODULES } from '@/shared/constants/appData'; import { PERSONALITY_QUIZ_KINDS } from '@/shared/constants/personality'; import type { ModuleId, SignItem, Strategy, ZoneInfo, } from '@/shared/types/app'; import type { TopBarPage, UseTopBarPageOptions, } from '@/business/top-bar/types'; import { useTodayZoneCheckIn } from '@/business/zone-checkin/hooks'; import { canZoneCheckIn, shouldNudgeZoneCheckIn } from '@/business/zone-checkin/selectors'; import { useScopeContext } from '@/shared/app/scope-context'; import { getCurrentSafetyQuizWeek } from '@/business/safety-quiz/selectors'; const EMPTY_STRATEGIES: readonly Strategy[] = []; const EMPTY_SIGNS: readonly SignItem[] = []; const EMPTY_ZONES: readonly ZoneInfo[] = []; export function useTopBarPage({ user, userRole, userName, campusInfo, toggleSidebar, setCurrentModule, profile, signOut: signOutAction, }: UseTopBarPageOptions): TopBarPage { const [showProfileMenu, setShowProfileMenu] = useState(false); const [showNotifications, setShowNotifications] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [signOutError, setSignOutError] = useState(null); const { tier, ownTenant, selectedTenant } = useScopeContext(); const canPersistPersonalResults = canPersistPersonalScopeResults(ownTenant, selectedTenant); const effectiveTier = selectedTenant ? selectedTenant.level : tier; const scopedModules = useMemo( () => getScopedModules(MODULES, user, effectiveTier, selectedTenant !== null), [effectiveTier, selectedTenant, user], ); const accessibleModuleIds = useMemo( () => new Set(scopedModules.map((module) => module.id)), [scopedModules], ); const canUseZoneCheckIn = canPersistPersonalResults && canZoneCheckIn(user); const zoneCheckIn = useTodayZoneCheckIn({ enabled: canUseZoneCheckIn }); const needsZoneCheckIn = shouldNudgeZoneCheckIn( canUseZoneCheckIn ? user : null, zoneCheckIn.isLoading, zoneCheckIn.isCheckedInToday, ); const canReceiveSafetyQuizNotification = canPersistPersonalResults && hasPermission(user, 'TAKE_QUIZ') && accessibleModuleIds.has('qbs') && (effectiveTier === 'school' || effectiveTier === 'campus' || effectiveTier === 'class'); const safetyQuizWeek = getCurrentSafetyQuizWeek(new Date()); const safetyQuizStatus = useMySafetyQuizStatus( safetyQuizWeek, canReceiveSafetyQuizNotification, ); const needsSafetyQuiz = canReceiveSafetyQuizNotification && !safetyQuizStatus.isLoading && safetyQuizStatus.data?.completed !== true; const canReceiveEmotionalIntelligenceNotifications = canPersistPersonalResults && hasPermission(user, 'TAKE_QUIZ') && accessibleModuleIds.has('ei'); const selfAssessmentStatus = useCurrentPersonalityResult( PERSONALITY_QUIZ_KINDS.selfAssessment, canReceiveEmotionalIntelligenceNotifications, ); const personalityQuizStatus = useCurrentPersonalityResult( PERSONALITY_QUIZ_KINDS.personalityType, canReceiveEmotionalIntelligenceNotifications, ); const needsEiSelfAssessment = canReceiveEmotionalIntelligenceNotifications && !selfAssessmentStatus.isLoading && !selfAssessmentStatus.data; const needsPersonalityQuiz = canReceiveEmotionalIntelligenceNotifications && !personalityQuizStatus.isLoading && !personalityQuizStatus.data; const communicationEvents = useCommunicationEvents(); const acknowledgedCommunicationEventIds = useMemo(() => new Set(), []); const canReceivePolicyNotifications = canPersistPersonalResults && hasPermission(user, 'ACK_POLICY'); const canReadHandbook = canReceivePolicyNotifications && accessibleModuleIds.has('handbook'); const canReadSafetyProtocols = canReceivePolicyNotifications && accessibleModuleIds.has('safety'); const policyAcknowledgments = usePolicyAcknowledgments(canReceivePolicyNotifications && ( canReadHandbook || canReadSafetyProtocols )); const handbookPolicies = usePolicies(canReadHandbook); const safetyProtocols = useSafetyProtocols(canReadSafetyProtocols); const notifications = buildTopBarNotifications({ needsZoneCheckIn, needsSafetyQuiz, needsEiSelfAssessment, needsPersonalityQuiz, communicationEvents: communicationEvents.data ?? [], acknowledgedCommunicationEventIds, handbookPolicies: handbookPolicies.data ?? [], safetyProtocols: safetyProtocols.data ?? [], policyAcknowledgments: policyAcknowledgments.data ?? [], }); // Header search = accessible modules (local) + their product content from the // content catalog. Content is fetched lazily — only once the user types, and // only for modules the user can access. const hasQuery = searchQuery.trim().length > 0; const moduleNameById = useMemo( () => new Map(MODULES.map((module) => [module.id, module.name])), [], ); const strategiesQuery = useContentCatalogPayload( CONTENT_CATALOG_TYPES.classroomStrategies, EMPTY_STRATEGIES, { enabled: hasQuery && accessibleModuleIds.has('classroom') }, ); const signsQuery = useContentCatalogPayload( CONTENT_CATALOG_TYPES.signLanguageItems, EMPTY_SIGNS, { enabled: hasQuery && accessibleModuleIds.has('signs') }, ); const zonesQuery = useContentCatalogPayload( CONTENT_CATALOG_TYPES.regulationZones, EMPTY_ZONES, { enabled: hasQuery && accessibleModuleIds.has('zones') }, ); const contentItems = useMemo(() => { const items: TopBarContentItem[] = []; const add = (moduleId: ModuleId, id: string, label: string) => { items.push({ id, label, moduleId, moduleName: moduleNameById.get(moduleId) ?? '' }); }; strategiesQuery.payload.forEach((s) => add('classroom', `strategy-${s.id}`, s.title)); signsQuery.payload.forEach((s) => add('signs', `sign-${s.id}`, s.word)); zonesQuery.payload.forEach((z) => add('zones', `zone-${z.color}`, z.name)); return items; }, [strategiesQuery.payload, signsQuery.payload, zonesQuery.payload, moduleNameById]); const searchResults = useMemo( () => buildTopBarSearchResults(scopedModules, user, searchQuery, contentItems), [scopedModules, user, searchQuery, contentItems], ); function selectSearchResult(result: TopBarSearchResult) { setSearchQuery(''); setCurrentModule(result.moduleId); } async function signOut() { setShowProfileMenu(false); setSignOutError(null); const result = await signOutAction(); if (result.error) { setSignOutError(result.error); } } return { userRole, userName, avatar: user?.avatar ?? null, campusInfo, profileRoleLabel: profile ? getTopBarRoleLabel(profile.role) : getTopBarRoleLabel(userRole), roleLabel: getTopBarRoleLabel(userRole), initials: getTopBarInitials(userName), campusLabel: getTopBarCampusLabel(campusInfo), showProfileMenu, showNotifications, searchQuery, searchResults, signOutError, notifications, unreadCount: countUnreadTopBarNotifications(notifications), toggleSidebar, toggleProfileMenu: () => setShowProfileMenu((current) => !current), closeProfileMenu: () => setShowProfileMenu(false), toggleNotifications: () => setShowNotifications((current) => !current), closeNotifications: () => setShowNotifications(false), setSearchQuery, selectSearchResult, signOut, }; }