151 lines
7.5 KiB
TypeScript
151 lines
7.5 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useSidebar } from "@/hooks/use-sidebar";
|
|
import { useActiveClass } from "@/hooks/use-active-class";
|
|
import { cn } from "@/lib/utils";
|
|
import type { UserRole } from "@/lib/types";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
|
|
// Module keys that can be toggled
|
|
const TOGGLEABLE_MODULES = ["announcements", "directory", "timeline", "assignments", "votes", "schedule", "resources", "fund"];
|
|
|
|
const navItems = [
|
|
{ href: "/dashboard", label: "首页", icon: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6", moduleKey: undefined },
|
|
{ href: "/announcements", label: "公告", icon: "M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z", moduleKey: "announcements" },
|
|
{ href: "/directory", label: "花名册", icon: "M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z", moduleKey: "directory" },
|
|
{ href: "/timeline", label: "班级动态", icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z", moduleKey: "timeline" },
|
|
{ href: "/assignments", label: "作业", icon: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z", moduleKey: "assignments" },
|
|
{ href: "/votes", label: "投票", icon: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4", moduleKey: "votes" },
|
|
{ href: "/schedule", label: "排期表", icon: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z", moduleKey: "schedule" },
|
|
{ href: "/resources", label: "资源库", icon: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z", moduleKey: "resources" },
|
|
{ href: "/fund", label: "班费管理", icon: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z", moduleKey: "fund" },
|
|
{ href: "/profile", label: "个人资料", icon: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z", moduleKey: undefined },
|
|
];
|
|
|
|
const adminItems = [
|
|
{ href: "/admin/members", label: "成员管理", icon: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z", roles: ["super_admin", "class_admin"] as UserRole[] },
|
|
{ href: "/admin/classes", label: "班级管理", icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4", roles: ["super_admin"] as UserRole[] },
|
|
{ href: "/admin/modules", label: "模块管理", icon: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z", roles: ["super_admin", "class_admin"] as UserRole[] },
|
|
];
|
|
|
|
export function Sidebar() {
|
|
const pathname = usePathname();
|
|
const { isOpen, close } = useSidebar();
|
|
const { user } = useAuth();
|
|
const { enabledModules } = useActiveClass();
|
|
const visibleAdminItems = user
|
|
? adminItems.filter((item) => item.roles.includes(user.role))
|
|
: [];
|
|
|
|
// Default to all modules enabled if not loaded yet
|
|
const defaultModules = ["announcements", "directory", "timeline", "assignments", "votes", "schedule", "resources", "fund"];
|
|
const enabledSet = new Set(enabledModules ?? defaultModules);
|
|
|
|
// Filter navItems based on enabled modules (items without moduleKey are always visible)
|
|
const visibleNavItems = navItems.filter(
|
|
(item) => !item.moduleKey || enabledSet.has(item.moduleKey)
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{/* Mobile backdrop */}
|
|
{isOpen && (
|
|
<div
|
|
className="fixed inset-0 z-40 bg-black/50 md:hidden"
|
|
onClick={close}
|
|
/>
|
|
)}
|
|
|
|
<aside
|
|
className={cn(
|
|
"w-64 bg-white border-r border-gray-200 h-screen flex flex-col shrink-0",
|
|
// Mobile: overlay mode
|
|
"fixed z-50 top-0 left-0 transition-transform duration-200 md:relative md:z-auto",
|
|
isOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"
|
|
)}
|
|
>
|
|
<div className="p-6 border-b border-gray-200">
|
|
<h1 className="text-xl font-bold text-gray-900">HKU ICB</h1>
|
|
<p className="text-xs text-gray-500 mt-1">班级资源平台</p>
|
|
</div>
|
|
|
|
<nav className="flex-1 p-4 space-y-1">
|
|
{visibleNavItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
onClick={close}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors",
|
|
pathname === item.href || pathname.startsWith(item.href + "/")
|
|
? "bg-gray-900 text-white"
|
|
: "text-gray-600 hover:bg-gray-100"
|
|
)}
|
|
>
|
|
<svg
|
|
className="w-5 h-5 shrink-0"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={1.5}
|
|
d={item.icon}
|
|
/>
|
|
</svg>
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
|
|
{visibleAdminItems.length > 0 && (
|
|
<>
|
|
<div className="pt-4 pb-2">
|
|
<p className="px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
管理
|
|
</p>
|
|
</div>
|
|
{visibleAdminItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
onClick={close}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors",
|
|
pathname.startsWith(item.href)
|
|
? "bg-gray-900 text-white"
|
|
: "text-gray-600 hover:bg-gray-100"
|
|
)}
|
|
>
|
|
<svg
|
|
className="w-5 h-5 shrink-0"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={1.5}
|
|
d={item.icon}
|
|
/>
|
|
</svg>
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</>
|
|
)}
|
|
</nav>
|
|
|
|
<div className="p-4 border-t border-gray-200">
|
|
<p className="text-xs text-gray-400">© {new Date().getFullYear()} HKU ICB</p>
|
|
<p className="text-xs text-gray-400 mt-0.5">By 周龙 @ FBM05</p>
|
|
</div>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|