feat(coach): AI 코칭 버튼 및 버블 팝업 대화형 조언 기능 구현
This commit is contained in:
parent
8a34dd2afc
commit
70d7cb34a3
36 changed files with 4521 additions and 1264 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useLayoutEffect, useRef, type ReactNode } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Topbar } from "./Topbar";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { canAccessRole, designRoleOf, useAuth, type Role } from "../../lib/auth";
|
||||
|
|
@ -78,6 +78,79 @@ export function AppShell({ children, className, contextLabel, hideNav, bleed, wi
|
|||
return () => window.removeEventListener("pageshow", handlePageShow);
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// ── 전역 단축키 핸들러 ──
|
||||
useEffect(() => {
|
||||
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.repeat) return;
|
||||
const target = e.target instanceof HTMLElement ? e.target : null;
|
||||
const isInput = target?.closest("input, textarea, select, [contenteditable='true']");
|
||||
|
||||
const isAlt = e.altKey && !e.ctrlKey && !e.metaKey;
|
||||
const isCtrl = (e.ctrlKey || e.metaKey) && !e.altKey;
|
||||
|
||||
// Ctrl+W 또는 Escape: 활성 모달 닫기
|
||||
if ((isCtrl && e.key.toLowerCase() === "w") || e.key === "Escape") {
|
||||
const openModalCloseBtn = document.querySelector<HTMLButtonElement>(
|
||||
"[role='dialog'] button:has(svg), [role='dialog'] .sx-panel-close-btn, .vg-dialog__close, [role='dialog'] button[aria-label*='닫기']"
|
||||
);
|
||||
if (openModalCloseBtn) {
|
||||
e.preventDefault();
|
||||
openModalCloseBtn.click();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Alt+H: 홈으로 이동
|
||||
if (isAlt && e.key.toLowerCase() === "h") {
|
||||
e.preventDefault();
|
||||
const homePath = user?.role === "admin" ? "/admin" : user?.role === "teacher" ? "/professor" : "/learn";
|
||||
navigate(homePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+N 또는 Ctrl+N 또는 Ctrl+T (입력창 밖): 새 회기 시작 / 페르소나 선택
|
||||
if ((isAlt && e.key.toLowerCase() === "n") || (!isInput && isCtrl && (e.key.toLowerCase() === "n" || e.key.toLowerCase() === "t"))) {
|
||||
e.preventDefault();
|
||||
navigate("/learn");
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+K: 학습 기록으로 이동
|
||||
if (isAlt && e.key.toLowerCase() === "k") {
|
||||
e.preventDefault();
|
||||
navigate("/learn/history");
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+S: 설정 화면으로 이동
|
||||
if (isAlt && e.key.toLowerCase() === "s") {
|
||||
e.preventDefault();
|
||||
navigate("/settings");
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt+T: 다크/라이트 테마 전환
|
||||
if (isAlt && e.key.toLowerCase() === "t") {
|
||||
e.preventDefault();
|
||||
const themeBtn = document.querySelector<HTMLButtonElement>(".vg-topbar__theme-btn, [data-testid='theme-toggle']");
|
||||
if (themeBtn) {
|
||||
themeBtn.click();
|
||||
} else {
|
||||
const currentTheme = document.documentElement.getAttribute("data-theme") || "light";
|
||||
const nextTheme = currentTheme === "dark" ? "light" : "dark";
|
||||
document.documentElement.setAttribute("data-theme", nextTheme);
|
||||
localStorage.setItem("vignette_theme", nextTheme);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleGlobalKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleGlobalKeyDown);
|
||||
}, [navigate, user]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue