import { getAuthRoleLabel } from '@/business/auth/selectors'; import { APP_ROUTE_PATHS } from '@/shared/constants/routes'; import { DEFAULT_CAMPUS_LABEL } from '@/shared/constants/campusDisplay'; import type { CampusInfo, UserRole, } from '@/shared/types/app'; import type { ScopeTier } from '@/business/scope/selectors'; import type { TopBarNotification } from '@/business/top-bar/types'; import type { CommunicationEventDto } from '@/shared/types/communications'; import type { PolicyViewModel } from '@/business/policies/types'; import type { SafetyProtocolViewModel } from '@/business/safety-protocols/types'; import type { PolicyAcknowledgmentDto } from '@/shared/types/policyDocuments'; import { isPolicyDocumentAcknowledged } from '@/business/policies/selectors'; import { ESA_FUNDING_POLICY_TAG } from '@/shared/constants/esaFunding'; export function getTopBarInitials(name: string): string { return name .trim() .split(/\s+/u) .filter(Boolean) .map((part) => part[0]) .join('') .slice(0, 2) .toUpperCase(); } export function getTopBarCampusLabel(campusInfo?: CampusInfo): string { return campusInfo?.fullName ?? DEFAULT_CAMPUS_LABEL; } export function getTopBarRoleLabel(role: UserRole): string { return getAuthRoleLabel(role); } export function countUnreadTopBarNotifications( notifications: readonly TopBarNotification[], ): number { return notifications.filter((notification) => notification.unread).length; } export function canQueryDailyStaffAttendanceForScope(tier: ScopeTier): boolean { return tier === 'organization' || tier === 'school' || tier === 'campus'; } export function shouldShowDailyStaffAttendanceNotification(input: { readonly canMonitorDailyAttendance: boolean; readonly tier: ScopeTier; readonly loading: boolean; readonly hasError: boolean; readonly staffCount: number; readonly recordsCount: number; }): boolean { return input.canMonitorDailyAttendance && canQueryDailyStaffAttendanceForScope(input.tier) && !input.loading && !input.hasError && input.staffCount > 0 && input.recordsCount < input.staffCount; } const ZONE_CHECKIN_NOTIFICATION_ID = 'zone-checkin-today'; const SAFETY_QUIZ_NOTIFICATION_ID = 'safety-quiz-weekly'; const SIGN_OF_WEEK_NOTIFICATION_ID = 'sign-of-week'; const WEEKLY_SIGN_SELECTION_NOTIFICATION_ID = 'weekly-sign-selection'; const WEEKLY_FRAME_NOTIFICATION_ID = 'weekly-frame-content'; const DAILY_ATTENDANCE_NOTIFICATION_ID = 'daily-attendance-content'; const EI_SELF_ASSESSMENT_NOTIFICATION_ID = 'ei-self-assessment'; const EI_PERSONALITY_QUIZ_NOTIFICATION_ID = 'ei-personality-quiz'; /** * Builds the top-bar notification list from derived app state. Personal * completion reminders are driven by backend-backed status queries; there is * no persisted notifications store yet. */ export function buildTopBarNotifications(input: { readonly needsZoneCheckIn: boolean; readonly needsSafetyQuiz?: boolean; readonly needsSignOfWeek?: boolean; readonly needsWeeklySignSelection?: boolean; readonly needsWeeklyFrameContent?: boolean; readonly needsDailyAttendanceContent?: boolean; readonly needsEiSelfAssessment?: boolean; readonly needsPersonalityQuiz?: boolean; readonly communicationEvents?: readonly CommunicationEventDto[]; readonly acknowledgedCommunicationEventIds?: ReadonlySet; readonly handbookPolicies?: readonly PolicyViewModel[]; readonly safetyProtocols?: readonly SafetyProtocolViewModel[]; readonly policyAcknowledgments?: readonly PolicyAcknowledgmentDto[]; }): readonly TopBarNotification[] { const notifications: TopBarNotification[] = []; if (input.needsZoneCheckIn) { notifications.push({ id: ZONE_CHECKIN_NOTIFICATION_ID, text: "You haven't logged your Emotional Zone today", time: 'Today', unread: true, href: APP_ROUTE_PATHS.zones, }); } if (input.needsSafetyQuiz) { notifications.push({ id: SAFETY_QUIZ_NOTIFICATION_ID, text: "You haven't completed this week's QBS safety quiz", time: 'This week', unread: true, href: APP_ROUTE_PATHS.qbs, }); } if (input.needsSignOfWeek) { notifications.push({ id: SIGN_OF_WEEK_NOTIFICATION_ID, text: "You haven't learned this week's sign", time: 'This week', unread: true, href: APP_ROUTE_PATHS.signs, }); } if (input.needsWeeklySignSelection) { notifications.push({ id: WEEKLY_SIGN_SELECTION_NOTIFICATION_ID, text: "Select this week's Sign of the Week", time: 'This week', unread: true, href: APP_ROUTE_PATHS.signs, }); } if (input.needsWeeklyFrameContent) { notifications.push({ id: WEEKLY_FRAME_NOTIFICATION_ID, text: "Publish this week's F.R.A.M.E. entry", time: 'This week', unread: true, href: APP_ROUTE_PATHS.frame, }); } if (input.needsDailyAttendanceContent) { notifications.push({ id: DAILY_ATTENDANCE_NOTIFICATION_ID, text: "Today's staff attendance is incomplete", time: 'Today', unread: true, href: APP_ROUTE_PATHS.attendance, }); } if (input.needsEiSelfAssessment) { notifications.push({ id: EI_SELF_ASSESSMENT_NOTIFICATION_ID, text: "You haven't completed your EI self-assessment", time: 'This week', unread: true, href: APP_ROUTE_PATHS.ei, }); } if (input.needsPersonalityQuiz) { notifications.push({ id: EI_PERSONALITY_QUIZ_NOTIFICATION_ID, text: "You haven't completed your personality type quiz", time: 'Once', unread: true, href: APP_ROUTE_PATHS.ei, }); } for (const event of input.communicationEvents ?? []) { if (input.acknowledgedCommunicationEventIds?.has(event.id)) { continue; } notifications.push({ id: `internal-alert-${event.id}`, text: `Internal alert: ${event.title}`, time: new Date(event.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', }), unread: true, href: APP_ROUTE_PATHS.internalComm, }); } for (const policy of input.handbookPolicies ?? []) { if (isPolicyDocumentAcknowledged(input.policyAcknowledgments ?? [], policy.id, policy.version)) { continue; } const esaUpdatedSection = policy.tag === ESA_FUNDING_POLICY_TAG ? getEsaUpdatedSection(policy) : null; notifications.push({ id: `handbook-policy-${policy.id}-v${policy.version}`, text: policy.tag === ESA_FUNDING_POLICY_TAG ? `Updated ESA funding section: ${esaUpdatedSection ?? policy.title}` : `Unread handbook policy: ${policy.title}`, time: `Version ${policy.version}`, unread: true, href: policy.tag === ESA_FUNDING_POLICY_TAG ? APP_ROUTE_PATHS.esa : APP_ROUTE_PATHS.handbook, }); } for (const protocol of input.safetyProtocols ?? []) { if (isPolicyDocumentAcknowledged(input.policyAcknowledgments ?? [], protocol.id, protocol.version)) { continue; } notifications.push({ id: `safety-protocol-${protocol.id}-v${protocol.version}`, text: `Unread safety protocol: ${protocol.title}`, time: `Version ${protocol.version}`, unread: true, href: APP_ROUTE_PATHS.safety, }); } return notifications; } function getEsaUpdatedSection(policy: PolicyViewModel): string | null { const marker = 'Updated section:'; const line = policy.content .split('\n') .map((item) => item.trim()) .find((item) => item.startsWith(marker)); if (!line) { return null; } const sectionName = line.slice(marker.length).trim(); return sectionName.length > 0 ? sectionName : null; }