feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG
web (Vite+React19+TS, Cloudflare Pages 배포): - 디자인토큰(세이지틸/테라코타 SSOT), 앱셸, 공통 UI 프리미티브 - 7화면: 로그인/학습자홈/상담세션/회기리뷰/교수자/관리자/설정 - ClientAvatar: SVG 반구상 흉상 4상태 + RMS 립싱크 + 6파라미터 정서 - 회기리뷰는 외부 레퍼런스 디자인을 Vignette 토큰으로 리스킨 api (FastAPI): - 게이트웨이 /v1/generate·/v1/stream 어댑터(상주풀/EngineSession 보존) - services: 페르소나 L0~L6 빌더 / 결정론 상태머신 / 가드레일 / 턴 오케스트레이터 / 회기간 메모리 / 평가AI / 음성 / RAG - store: DB off 폴백(in-memory), sessions 실구현 검증: - web: node22 tsc+vite build 통과(node23 segfault 회피), Pages 배포 200 - api: app.main import 통과 - 핫픽스: Topbar initials undefined-safe (undefined.trim 크래시) - E2E: 서연(P1) 상담 1턴 — 좋은/나쁜 상담에 차등 반응 실증
This commit is contained in:
parent
859ab26314
commit
24b1b7a6e1
84 changed files with 19645 additions and 107 deletions
73
apps/web/src/components/shell/Sidebar.tsx
Normal file
73
apps/web/src/components/shell/Sidebar.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
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: "/settings", label: "설정", icon: "settings" },
|
||||
],
|
||||
teacher: [
|
||||
{ to: "/teach", label: "콘솔", icon: "users", end: true },
|
||||
{ to: "/settings", label: "설정", icon: "settings" },
|
||||
],
|
||||
admin: [
|
||||
{ to: "/admin", label: "운영", icon: "shield", end: true },
|
||||
{ to: "/settings", label: "설정", icon: "settings" },
|
||||
],
|
||||
};
|
||||
|
||||
export function navItemsFor(role: Role): NavItem[] {
|
||||
return NAV_BY_ROLE[role];
|
||||
}
|
||||
|
||||
export interface SidebarProps {
|
||||
role: Role;
|
||||
/** 상단 그룹 라벨 (기본 "메뉴") */
|
||||
groupLabel?: string;
|
||||
}
|
||||
|
||||
/** 좌측 네비 (240px). 활성 = accent-tint 알약. 강조바 금지. §6.3 */
|
||||
export function Sidebar({ role, groupLabel = "메뉴" }: SidebarProps) {
|
||||
const items = navItemsFor(role);
|
||||
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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue