import React, { useState } from 'react'; import { NavLink, Outlet, useNavigate, Link } from 'react-router-dom'; import { LayoutDashboard, Building2, FolderKanban, Coins, ShieldAlert, Settings, LogOut, Menu, X, ChevronRight, ChevronDown, ShieldCheck, Database, Home, User, Users, GitPullRequest, MapPin, } from 'lucide-react'; import { useAuth } from '../../auth/AuthProvider'; import { BrandLogo } from '../ui/BrandLogo'; import { HelpTooltip } from '../ui/HelpTooltip'; import { useQuery } from '@tanstack/react-query'; import { getUsers } from '../../services/api'; import { getUserEmail, registerUserEmail } from '../../lib/formatters'; interface SubNavItem { name: string; path: string; icon: React.ElementType; help?: string; } interface NavItem { name: string; path: string; icon: React.ElementType; help: string; subItems?: SubNavItem[]; } const navItems: NavItem[] = [ { name: 'Executive Dashboard', path: '/dashboard', icon: LayoutDashboard, help: 'Strategic macro metrics, portfolio value, disbursed funding, ISO 31000 risk heatmap, and projects requiring immediate intervention.', }, { name: 'Governance & Structure', path: '/governance', icon: Building2, help: 'Master records for legal entities, holding structures, investment portfolios, and thematic programmes funded under statutory mandates.', }, { name: 'Projects & Milestones', path: '/projects', icon: FolderKanban, help: 'Project Work Breakdown Structure (WBS), contractual deliverables, scheduled milestones, evidence verification, and execution tracking.', subItems: [ { name: 'Beneficiaries', path: '/projects/beneficiaries', icon: Users, help: 'Manage target beneficiaries, demographics, and statutory compliance items satisfying beneficiaries.', }, { name: 'Change Management', path: '/projects/change-management', icon: GitPullRequest, help: 'Track project issues, contractual change requests, and resolve changes in-place.', }, ], }, { name: 'Site Visits', path: '/site-visits', icon: MapPin, help: 'Log physical site visits, field observations, inspector findings, and project feedback.', }, { name: 'Grant & Financials', path: '/grants', icon: Coins, help: 'Financial management including statutory awards, multi-year budget line allocations, tranches, and scheduled payment disbursements.', }, { name: 'M&E, Evidence & Risks', path: '/monitoring', icon: ShieldAlert, help: 'Monitoring & Evaluation (M&E) framework, indicator tracking, ISO 31000 risk registers, mitigation plans, and compliance audits.', }, { name: 'Settings & Communications', path: '/settings', icon: Settings, help: 'User account provisioning, statutory sync, tenant environment parameters, and communication invite templates.', }, ]; export const AppLayout: React.FC = () => { const { user, claims, sub, tenantId, hashedTenantId, logout } = useAuth(); const [sidebarOpen, setSidebarOpen] = useState(true); const [userMenuOpen, setUserMenuOpen] = useState(false); const navigate = useNavigate(); const handleLogout = async () => { await logout(); }; // Pre-populate user directory cache so all components and modals resolve user emails instantly useQuery({ queryKey: ['users-directory'], queryFn: () => getUsers(1, 200), staleTime: 5 * 60 * 1000, }); // Ensure current logged-in user is registered in the email cache const effectiveEmail = ((user?.email || claims?.email || claims?.['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] || claims?.upn) as string); if (sub && effectiveEmail) { registerUserEmail(sub, effectiveEmail); } if (user?.id && effectiveEmail) { registerUserEmail(user.id, effectiveEmail); } // Extract name and email directly from validated token claims const tokenUserName = user?.name || (claims.name as string) || (claims.preferred_username as string) || (claims.given_name ? `${claims.given_name} ${claims.family_name || ''}`.trim() : '') || 'Authorized User'; const tokenUserEmail = user?.email || (claims.email as string) || (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) || (claims.upn as string) || (sub ? getUserEmail(sub) : ''); // Extract tenant name directly from the user claim named 'tenant' const tenantName = (claims?.tenant as string) || user?.tenant || (claims?.['tenant'] as string) || user?.organisationName || (claims?.organisation as string) || 'Department of Science, Innovation & Technology (DSI)'; return (
{tokenUserName}
{tokenUserEmail}
{tokenUserName}
{tokenUserEmail || (sub ? getUserEmail(sub) : '')}