vignette/apps/web/src/components/shell/Sidebar.tsx

98 lines
3.4 KiB
TypeScript

import { NavLink } from "react-router-dom";
import { Icon, type IconName } from "../ui/Icon";
import type { Role } from "../../lib/auth";
export interface NavItem {
to: string;
label: string;
icon: IconName;
/** NavLink end (정확 매칭) */
end?: boolean;
}
/**
* 역할별 네비 항목. 권한 없는 항목은 애초에 목록에 없음(DOM 제거, §6.3).
* disabled 회색 처리 안 함.
*/
const NAV_BY_ROLE: Record<Role, NavItem[]> = {
learner: [
{ to: "/learn", label: "대시보드", icon: "home", end: true },
{ to: "/learn/practice", label: "학습", icon: "session" },
{ to: "/learn/history", label: "기록", icon: "review" },
{ to: "/settings", label: "설정", icon: "settings" },
],
teacher: [
{ to: "/teach", label: "콘솔", icon: "users", end: true },
{ to: "/teach/analysis", label: "학생 분석", icon: "review" },
{ to: "/teach/personas", label: "페르소나", icon: "review" },
{ to: "/settings", label: "설정", icon: "settings" },
],
admin: [
{ to: "/admin", label: "운영 홈", icon: "shield", end: true },
{ to: "/admin/ai", label: "AI 운영", icon: "session" },
{ to: "/admin/users", label: "사용자", icon: "users" },
{ to: "/admin/access", label: "권한", icon: "settings" },
{ to: "/admin/tickets", label: "티켓", icon: "review" },
{ to: "/teach", label: "교수 콘솔", icon: "users", end: true },
{ to: "/teach/analysis", label: "학생 분석", icon: "review" },
{ to: "/teach/personas", label: "페르소나", icon: "review" },
{ to: "/learn", label: "학습자 홈", icon: "home", end: true },
{ to: "/learn/practice", label: "학습", icon: "session" },
{ to: "/learn/history", label: "기록", icon: "review" },
{ to: "/settings", label: "설정", icon: "settings" },
],
};
const ADMIN_ENTRY: NavItem = {
to: "/admin",
label: "운영 콘솔",
icon: "shield",
end: true,
};
export function navItemsFor(role: Role, showAdminEntry = false): NavItem[] {
const items = NAV_BY_ROLE[role];
if (role === "admin" || !showAdminEntry) return items;
return [ADMIN_ENTRY, ...items];
}
export interface SidebarProps {
role: Role;
/** 기본 역할 화면에서도 관리자 권한 사용자가 운영 콘솔로 돌아갈 수 있게 한다. */
showAdminEntry?: boolean;
/** 상단 그룹 라벨 (기본 "메뉴") */
groupLabel?: string;
}
/** 좌측 네비 (240px). 활성 = accent-tint 알약. 강조바 금지. §6.3 */
export function Sidebar({ role, showAdminEntry = false, groupLabel = "메뉴" }: SidebarProps) {
const items = navItemsFor(role, showAdminEntry);
return (
<nav className="vg-nav" aria-label="주 메뉴">
<span className="vg-nav__label">{groupLabel}</span>
{items.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.end}
className={({ isActive }) =>
"vg-nav__item" + (isActive ? " is-active" : "")
}
>
<span className="vg-nav__ic">
<Icon name={item.icon} size={18} strokeWidth={1.75} />
</span>
<span>{item.label}</span>
</NavLink>
))}
<span className="vg-nav__spacer" />
<div className="vg-nav__foot">
<p className="vg-nav__ethic">
. · .
</p>
</div>
</nav>
);
}