import { useEffect, useMemo, useState } from "react"; import { Layout } from "antd"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { api } from "../api"; import { clearTokens } from "../auth"; import Toast from "../components/Toast/Toast"; import ModernSidebar, { SidebarGroup } from "../components/ModernSidebar/ModernSidebar"; import AppHeader from "./AppHeader"; import { getIcon } from "../utils/icons"; import "./AppLayout.css"; type MenuNode = { id: number; name: string; code: string; type: string; level: number; path?: string | null; icon?: string | null; avatar?: string | null; children?: MenuNode[]; }; export default function AppLayout() { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const [collapsed, setCollapsed] = useState(false); const [menus, setMenus] = useState([]); const [displayName, setDisplayName] = useState("Admin"); const [username, setUsername] = useState(""); const [userRole, setUserRole] = useState("Admin"); const [avatar, setAvatar] = useState(null); const [platformName, setPlatformName] = useState("NexDocus"); useEffect(() => { // Fetch platform name api.listParams().then((params) => { const p = params.find((x: any) => x.param_key.toUpperCase() === "PLATFORM_NAME"); if (p) { setPlatformName(p.param_value); } }).catch(() => {}); // ignore error const fetchUser = () => { api.me().then((res) => { setDisplayName(res.display_name || res.username); setUsername(res.username); setAvatar(res.avatar || null); setUserRole(res.roles && res.roles.length > 0 ? res.roles.join(" / ") : "User"); }).catch(() => { clearTokens(); navigate("/login"); }); }; fetchUser(); window.addEventListener('user-refresh', fetchUser); return () => window.removeEventListener('user-refresh', fetchUser); }, [navigate]); useEffect(() => { const fetchMenu = () => { api .getMenuTree() .then((res) => { const serverMenus = res as MenuNode[]; setMenus(serverMenus); }) .catch(() => Toast.error(t('common.error'))); }; fetchMenu(); window.addEventListener('menu-refresh', fetchMenu); return () => window.removeEventListener('menu-refresh', fetchMenu); }, [t]); useEffect(() => { if (!menus.length) return; if (location.pathname !== "/" && location.pathname !== "/home") return; const firstGroup = menus[0]; const firstItem = firstGroup?.children?.[0]; if (!firstItem) return; const target = firstItem.path || firstItem.code; if (target) { navigate(target, { replace: true }); } }, [menus, location.pathname, navigate]); // Helper to translate menu names const translateMenuName = (name: string, code: string) => { // Try to find translation by code (e.g. 'system.user' -> 'menu.userManage') // This requires a mapping or convention. // For now, let's try a simple mapping based on the code or just return name if not found. // Example codes: 'dashboard', 'workspace', 'system' // Map code to translation key const codeMap: Record = { 'home': 'menu.home', 'dashboard': 'menu.dashboard', 'workspace': 'menu.workspace', 'meeting.realtime': 'menu.realtimeMeeting', 'meeting.history': 'menu.historyMeeting', 'voice.profile': 'menu.voiceProfile', 'knowledge': 'menu.knowledgeBase', 'knowledge.manage': 'menu.kbManage', 'hotwords': 'menu.hotwords', 'system': 'menu.system', 'system.settings': 'menu.systemSettings', 'system.user': 'menu.userManage', 'system.role': 'menu.roleManage', 'system.permission': 'menu.permissionManage', 'system.dict': 'menu.dictManage', 'system.param': 'menu.paramManage', 'system.log': 'menu.logManage', 'monitor.task': 'menu.taskMonitor', 'ai_agent.prompts': 'menu.promptManage', 'ai_agent.models': 'menu.modelManage' }; if (codeMap[code]) { return t(codeMap[code]); } return name; }; const menuGroups: SidebarGroup[] = useMemo(() => { return menus.map((group) => ({ title: translateMenuName(group.name, group.code), items: (group.children || []).map((child) => ({ key: child.path || child.code, label: translateMenuName(child.name, child.code), icon: getIcon(child.icon), path: child.path })) })); }, [menus, t]); const handleLogout = () => { clearTokens(); navigate("/login"); }; return ( navigate(key)} onLogout={handleLogout} onProfileClick={() => navigate('/profile')} /> navigate('/profile')} /> ); }