98 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|