import { useAuth } from "@/_core/hooks/useAuth"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, useSidebar, } from "@/components/ui/sidebar"; import { useIsMobile } from "@/hooks/useMobile"; import { LayoutDashboard, LogOut, PanelLeft, Target, Video, Award, Activity, FileVideo, Trophy, Flame, Camera, CircleDot, BookOpen, Bell, Microscope, ScrollText, Shield } from "lucide-react"; import { CSSProperties, useEffect, useRef, useState } from "react"; import { useLocation, Redirect } from "wouter"; import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton'; import { TaskCenter } from "./TaskCenter"; type MenuItem = { icon: typeof LayoutDashboard; label: string; path: string; group: "main" | "analysis" | "stats" | "learn"; adminOnly?: boolean; }; const menuItems: MenuItem[] = [ { icon: LayoutDashboard, label: "仪表盘", path: "/dashboard", group: "main" }, { icon: Target, label: "训练计划", path: "/training", group: "main" }, { icon: Flame, label: "成就系统", path: "/checkin", group: "main" }, { icon: Camera, label: "实时分析", path: "/live-camera", group: "analysis" }, { icon: CircleDot, label: "在线录制", path: "/recorder", group: "analysis" }, { icon: Video, label: "视频分析", path: "/analysis", group: "analysis" }, { icon: FileVideo, label: "视频库", path: "/videos", group: "analysis" }, { icon: Activity, label: "训练进度", path: "/progress", group: "stats" }, { icon: Award, label: "NTRP评分", path: "/rating", group: "stats" }, { icon: Trophy, label: "排行榜", path: "/leaderboard", group: "stats" }, { icon: BookOpen, label: "教程库", path: "/tutorials", group: "learn" }, { icon: Bell, label: "训练提醒", path: "/reminders", group: "learn" }, { icon: ScrollText, label: "更新日志", path: "/changelog", group: "learn" }, { icon: ScrollText, label: "系统日志", path: "/logs", group: "learn" }, { icon: Microscope, label: "视觉测试", path: "/vision-lab", group: "learn", adminOnly: true }, { icon: Shield, label: "管理系统", path: "/admin", group: "learn", adminOnly: true }, ]; const mobileNavItems = [ { icon: LayoutDashboard, label: "首页", path: "/dashboard" }, { icon: Target, label: "计划", path: "/training" }, { icon: CircleDot, label: "录制", path: "/recorder" }, { icon: FileVideo, label: "视频", path: "/videos" }, { icon: Activity, label: "进度", path: "/progress" }, ]; const SIDEBAR_WIDTH_KEY = "sidebar-width"; const DEFAULT_WIDTH = 260; const MIN_WIDTH = 200; const MAX_WIDTH = 400; export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { const [sidebarWidth, setSidebarWidth] = useState(() => { const saved = localStorage.getItem(SIDEBAR_WIDTH_KEY); return saved ? parseInt(saved, 10) : DEFAULT_WIDTH; }); const { loading, user } = useAuth(); useEffect(() => { localStorage.setItem(SIDEBAR_WIDTH_KEY, sidebarWidth.toString()); }, [sidebarWidth]); if (loading) { return } if (!user) { return ; } return ( {children} ); } type DashboardLayoutContentProps = { children: React.ReactNode; setSidebarWidth: (width: number) => void; }; function DashboardLayoutContent({ children, setSidebarWidth, }: DashboardLayoutContentProps) { const { user, logout } = useAuth(); const [location, setLocation] = useLocation(); const { state, toggleSidebar } = useSidebar(); const isCollapsed = state === "collapsed"; const [isResizing, setIsResizing] = useState(false); const sidebarRef = useRef(null); const visibleMenuItems = menuItems.filter(item => !item.adminOnly || user?.role === "admin"); const activeMenuItem = visibleMenuItems.find(item => item.path === location); const isMobile = useIsMobile(); useEffect(() => { if (isCollapsed) { setIsResizing(false); } }, [isCollapsed]); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isResizing) return; const sidebarLeft = sidebarRef.current?.getBoundingClientRect().left ?? 0; const newWidth = e.clientX - sidebarLeft; if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) { setSidebarWidth(newWidth); } }; const handleMouseUp = () => { setIsResizing(false); }; if (isResizing) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; } return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.cursor = ""; document.body.style.userSelect = ""; }; }, [isResizing, setSidebarWidth]); return ( <>
{!isCollapsed ? (
Tennis Hub
) : null}
{/* Main group */} {visibleMenuItems.filter(i => i.group === "main").map(item => { const isActive = location === item.path; return ( setLocation(item.path)} tooltip={item.label} className={`h-10 transition-all font-normal`} > {item.label} ); })} {/* Divider */} {!isCollapsed &&
} {!isCollapsed &&

分析与录制

} {visibleMenuItems.filter(i => i.group === "analysis").map(item => { const isActive = location === item.path; return ( setLocation(item.path)} tooltip={item.label} className={`h-10 transition-all font-normal`} > {item.label} ); })} {/* Divider */} {!isCollapsed &&
} {!isCollapsed &&

统计与排名

} {visibleMenuItems.filter(i => i.group === "stats").map(item => { const isActive = location === item.path; return ( setLocation(item.path)} tooltip={item.label} className={`h-10 transition-all font-normal`} > {item.label} ); })} {/* Divider */} {!isCollapsed &&
} {!isCollapsed &&

学习与提醒

} {visibleMenuItems.filter(i => i.group === "learn").map(item => { const isActive = location === item.path; return ( setLocation(item.path)} tooltip={item.label} className={`h-10 transition-all font-normal`} > {item.label} ); })}
退出登录
{ if (isCollapsed) return; setIsResizing(true); }} style={{ zIndex: 50 }} />
{isMobile && (
{activeMenuItem?.label ?? "Tennis Hub"}
)}
{children}
{isMobile && ( )}
); }