/** * Standardized Page Header Component * Used across all Planner and Writer module pages * Includes: Page title, last updated, site/sector info, and selectors */ import React, { ReactNode, useEffect, useRef } from 'react'; import { useSiteStore } from '../../store/siteStore'; import { useSectorStore } from '../../store/sectorStore'; import SiteAndSectorSelector from './SiteAndSectorSelector'; import { trackLoading } from './LoadingStateMonitor'; import { useErrorHandler } from '../../hooks/useErrorHandler'; interface PageHeaderProps { title: string; lastUpdated?: Date; showRefresh?: boolean; onRefresh?: () => void; className?: string; badge?: { icon: ReactNode; color: 'blue' | 'green' | 'purple' | 'orange' | 'red' | 'indigo'; }; hideSiteSector?: boolean; // Hide site/sector selector and info for global pages navigation?: ReactNode; // Module navigation tabs } export default function PageHeader({ title, lastUpdated, showRefresh = false, onRefresh, className = "", badge, hideSiteSector = false, navigation, }: PageHeaderProps) { const { activeSite } = useSiteStore(); const { activeSector, loadSectorsForSite } = useSectorStore(); const { addError } = useErrorHandler('PageHeader'); const lastSiteId = useRef(null); const isLoadingSector = useRef(false); // Load sectors when active site changes - only for pages that need site/sector context useEffect(() => { // Skip sector loading for pages that hide site/sector selector (account/billing pages) if (hideSiteSector) return; const currentSiteId = activeSite?.id ?? null; // Only load if: // 1. We have a site ID // 2. The site is active (inactive sites can't have accessible sectors) // 3. It's different from the last one we loaded // 4. We're not already loading if (currentSiteId && activeSite?.is_active && currentSiteId !== lastSiteId.current && !isLoadingSector.current) { lastSiteId.current = currentSiteId; isLoadingSector.current = true; trackLoading('sector-loading', true); // Add timeout to prevent infinite loading const timeoutId = setTimeout(() => { if (isLoadingSector.current) { console.error('PageHeader: Sector loading timeout after 35 seconds'); trackLoading('sector-loading', false); isLoadingSector.current = false; addError(new Error('Sector loading timeout - check network connection'), 'PageHeader.loadSectorsForSite'); } }, 35000); loadSectorsForSite(currentSiteId) .catch((error) => { // Don't log 403/404 errors as they're expected for inactive sites if (error.status !== 403 && error.status !== 404) { console.error('PageHeader: Error loading sectors:', error); addError(error, 'PageHeader.loadSectorsForSite'); } }) .finally(() => { clearTimeout(timeoutId); trackLoading('sector-loading', false); isLoadingSector.current = false; }); } else if (currentSiteId && !activeSite?.is_active) { // Site is inactive - clear sectors and reset lastSiteId lastSiteId.current = null; const { useSectorStore } = require('../../store/sectorStore'); useSectorStore.getState().clearActiveSector(); } }, [activeSite?.id, activeSite?.is_active, hideSiteSector, loadSectorsForSite, addError]); const badgeColors = { blue: 'bg-blue-600 dark:bg-blue-500', green: 'bg-green-600 dark:bg-green-500', purple: 'bg-purple-600 dark:bg-purple-500', orange: 'bg-orange-600 dark:bg-orange-500', red: 'bg-red-600 dark:bg-red-500', indigo: 'bg-indigo-600 dark:bg-indigo-500', }; return (
{/* Left side: Title, badge, and site/sector info */}
{badge && (
{badge.icon && typeof badge.icon === 'object' && 'type' in badge.icon ? React.cloneElement(badge.icon as React.ReactElement, { className: 'text-white size-5' }) : badge.icon}
)}

{title}

{!hideSiteSector && (
{lastUpdated && ( <>

Last updated: {lastUpdated.toLocaleTimeString()}

)} {activeSite && ( <> {lastUpdated && }

Site: {activeSite.name}

)} {activeSector && ( <>

Sector: {activeSector.name}

)} {!activeSector && activeSite && ( <>

Sector: All Sectors

)}
)} {hideSiteSector && lastUpdated && (

Last updated: {lastUpdated.toLocaleTimeString()}

)}
{/* Right side: Navigation bar stacked above site/sector selector */}
{navigation &&
{navigation}
}
{!hideSiteSector && } {showRefresh && onRefresh && ( )}
); }