import { useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { MODULES } from '@/shared/constants/appData'; import { APP_ROUTE_PATHS } from '@/shared/constants/routes'; import { DEFAULT_CAMPUS_LABEL, findCampusByNameOrCode, } from '@/shared/constants/campusDisplay'; import { getModuleIdByRoutePath, getModuleRoutePath, } from '@/shared/constants/moduleRoutes'; import { DEFAULT_PRODUCT_ROLE } from '@/shared/constants/roles'; import type { ModuleId, UserRole } from '@/shared/types/app'; import { getAccessibleModuleId, getScopedModules, getScopedModuleRouteRedirectPath, getSidebarCampusInitial, getSidebarRoleLabel, shouldShowMobileSidebarOverlay, withRoleAwareModuleNames, } from '@/business/app-shell/selectors'; import { useCampusCatalog } from '@/business/campuses/hooks'; import { useScopeContext } from '@/shared/app/scope-context'; import type { AppShellState, SidebarPage, SidebarProps, UseAppShellOptions, } from '@/business/app-shell/types'; import type { ActiveTenant } from '@/shared/types/scope'; interface ScopeRouteState { readonly __scope?: ActiveTenant | null; readonly [key: string]: unknown; } function sameScopeTenant(a: ActiveTenant | null, b: ActiveTenant | null): boolean { if (a === b) return true; if (!a || !b) return a === b; return a.id === b.id && a.level === b.level; } function getUserRole(options: UseAppShellOptions): UserRole { return options.profile?.role ?? DEFAULT_PRODUCT_ROLE; } function getUserName(options: UseAppShellOptions): string { return options.profile?.full_name ?? ''; } function getUserCampus(options: UseAppShellOptions): string { return options.profile?.campus ?? DEFAULT_CAMPUS_LABEL; } export function useAppShell(options: UseAppShellOptions): AppShellState { const location = useLocation(); const navigate = useNavigate(); const { tier, selectedTenant, resetScope, setScopeTenant } = useScopeContext(); const campusCatalog = useCampusCatalog(); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [zoneCheckIn, setZoneCheckIn] = useState(null); const userRole = getUserRole(options); const userName = getUserName(options); const userCampus = getUserCampus(options); const effectiveTier = selectedTenant ? selectedTenant.level : tier; const isDrilled = selectedTenant !== null; const effectiveScopeKey = selectedTenant ? `${selectedTenant.level}:${selectedTenant.id}` : `own:${tier}`; const previousScopeKey = useRef(effectiveScopeKey); const previousPathname = useRef(location.pathname); const scopedModules = getScopedModules(MODULES, options.user, effectiveTier, isDrilled); const currentRouteModule = getModuleIdByRoutePath(location.pathname); const currentModule = getAccessibleModuleId(scopedModules, currentRouteModule, options.user); const activeModule = getAccessibleModuleId(scopedModules, currentModule, options.user); const campusInfo = findCampusByNameOrCode(campusCatalog.campuses, userCampus); const mobileOverlayVisible = shouldShowMobileSidebarOverlay(options.isMobile, mobileSidebarOpen); const routeState = (location.state as ScopeRouteState | null) ?? null; const routeScopeTenant = routeState?.__scope ?? null; useEffect(() => { const scopeChanged = previousScopeKey.current !== effectiveScopeKey; const pathnameChanged = previousPathname.current !== location.pathname; previousScopeKey.current = effectiveScopeKey; previousPathname.current = location.pathname; if (pathnameChanged && !scopeChanged && !sameScopeTenant(routeScopeTenant, selectedTenant)) { if (routeScopeTenant) { setScopeTenant(routeScopeTenant); } else { resetScope(); } return; } if (location.pathname === APP_ROUTE_PATHS.platformDashboard && tier === 'global' && selectedTenant !== null) { if (scopeChanged) { const drilledRedirectPath = getScopedModuleRouteRedirectPath( MODULES, location.pathname, options.user, effectiveTier, isDrilled, ); if (drilledRedirectPath && drilledRedirectPath !== location.pathname) { navigate(drilledRedirectPath, { replace: true, state: { ...routeState, __scope: selectedTenant }, }); } return; } } if (scopeChanged) { const redirectPath = getScopedModuleRouteRedirectPath( MODULES, location.pathname, options.user, effectiveTier, isDrilled, ); if (redirectPath && redirectPath !== location.pathname) { navigate(redirectPath, { replace: true, state: { ...routeState, __scope: selectedTenant }, }); return; } } if (!sameScopeTenant(routeScopeTenant, selectedTenant)) { navigate( { pathname: location.pathname, search: location.search, hash: location.hash, }, { replace: true, state: { ...routeState, __scope: selectedTenant }, }, ); } }, [ effectiveScopeKey, location.hash, effectiveTier, isDrilled, location.pathname, location.search, navigate, options.user, resetScope, routeScopeTenant, routeState, selectedTenant, setScopeTenant, tier, ]); const setCurrentModule = (id: ModuleId) => { const targetModule = scopedModules.find((module) => module.id === id); navigate( targetModule?.routePath ?? scopedModules[0]?.routePath ?? getModuleRoutePath(id), { state: { ...routeState, __scope: selectedTenant }, }, ); if (options.isMobile) { setMobileSidebarOpen(false); } }; const toggleSidebar = () => { if (options.isMobile) { setMobileSidebarOpen((current) => !current); return; } setSidebarCollapsed((current) => !current); }; const sidebarProps: SidebarProps = { currentModule: activeModule, setCurrentModule, user: options.user, userRole, collapsed: options.isMobile ? false : sidebarCollapsed, setCollapsed: setSidebarCollapsed, campusInfo, }; const topBarProps = { user: options.user, userRole, userName, campusInfo, toggleSidebar, setCurrentModule, }; const shellOutletContext = { user: options.user, userRole, userName, userCampus, zoneCheckIn, setZoneCheckIn, setCurrentModule, }; const footerProps = { userName, userRole, modules: scopedModules, setCurrentModule, }; return { activeModule, currentModule: activeModule, userRole, userName, userCampus, campusInfo, sidebarCollapsed, mobileSidebarOpen, mobileOverlayVisible, zoneCheckIn, sidebarProps, topBarProps, shellOutletContext, footerProps, setSidebarCollapsed, setMobileSidebarOpen, setZoneCheckIn, setCurrentModule, }; } export function useSidebarPage({ currentModule, setCurrentModule, user, userRole, collapsed, setCollapsed, campusInfo, }: SidebarProps): SidebarPage { const { tier, selectedTenant } = useScopeContext(); const effectiveTier = selectedTenant ? selectedTenant.level : tier; const isDrilled = selectedTenant !== null; return { currentModule, user, userRole, collapsed, campusInfo, modules: withRoleAwareModuleNames( getScopedModules(MODULES, user, effectiveTier, isDrilled), userRole, ), roleLabel: getSidebarRoleLabel(userRole), campusInitial: getSidebarCampusInitial(campusInfo), setCurrentModule, toggleCollapsed: () => setCollapsed(!collapsed), }; }