feat: 튜토리얼 UX 개선 - 네비게이션 및 프롬프트 피드백 강화

🔧 주요 개선사항:

1. Topbar 네비게이션 문제 해결
   - 튜토리얼 페이지에서 메뉴 항목 클릭 시 올바른 라우팅 구현
   - Tutorial 메뉴 클릭 시 페이지 리로드 기능 추가 (컴포넌트 리마운트)
   - 라우팅 우선, 스크롤 폴백 패턴 적용

2. PromptInput 플레이스홀더 개선
   - 튜토리얼 실행 후 실제 사용된 프롬프트를 플레이스홀더에 표시
   - 명확한 프롬프트 → 실행 → 결과 추적 가능
   - 새 튜토리얼 선택 시 이전 프롬프트 초기화

3. 새로운 튜토리얼 시스템 구축
   - TutorialSheetViewer: 단계별 튜토리얼 플로우 구현
   - TutorialCard: 개별 튜토리얼 카드 컴포넌트
   - TutorialExecutor: 튜토리얼 실행 엔진
   - TutorialDataGenerator: 10개 Excel 함수 데이터 생성

📁 변경된 파일들:
- src/App.tsx: 네비게이션 핸들러 추가
- src/components/ui/topbar.tsx: 라우팅 기반 네비게이션 구현
- src/components/sheet/PromptInput.tsx: 동적 플레이스홀더 추가
- src/components/TutorialSheetViewer.tsx: 튜토리얼 전용 뷰어 구현
- src/types/tutorial.ts: 튜토리얼 타입 정의
- .cursor/rules/tutorial-navigation-fix.mdc: 구현 패턴 문서화

 검증 완료:
- 모든 topbar 메뉴 정상 네비게이션
- 튜토리얼 페이지 리로드 기능 작동
- 실행된 프롬프트 플레이스홀더 표시
- AI 워크플로우 시뮬레이션 완성
This commit is contained in:
sheetEasy AI Team
2025-07-01 15:47:26 +09:00
parent 535281f0fb
commit 2f3515985d
15 changed files with 2367 additions and 13 deletions

View File

@@ -19,6 +19,7 @@ import { useAppStore } from "../../stores/useAppStore";
import { rangeToAddress } from "../../utils/cellUtils";
import { CellSelectionHandler } from "../../utils/cellSelectionHandler";
import { aiProcessor } from "../../utils/aiProcessor";
import { TutorialExecutor } from "../../utils/tutorialExecutor";
import type { HistoryEntry } from "../../types/ai";
// 전역 고유 키 생성
@@ -74,7 +75,7 @@ const getGlobalState = (): GlobalUniverState => {
* Presets 기반 강화된 전역 Univer 관리자
* 공식 문서 권장사항에 따라 createUniver 사용
*/
const UniverseManager = {
export const UniverseManager = {
// 전역 인스턴스 생성 (완전 단일 인스턴스 보장)
async createInstance(container: HTMLElement): Promise<any> {
const state = getGlobalState();
@@ -339,6 +340,9 @@ const TestSheetViewer: React.FC = () => {
// CellSelectionHandler 인스턴스 생성
const cellSelectionHandler = useRef(new CellSelectionHandler());
// TutorialExecutor 인스턴스 생성
const tutorialExecutor = useRef(new TutorialExecutor());
// 히스토리 관련 상태 추가
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
const [history, setHistory] = useState<HistoryEntry[]>([]);
@@ -587,6 +591,9 @@ const TestSheetViewer: React.FC = () => {
// 셀 선택 핸들러 초기화 - SRP에 맞춰 별도 클래스로 분리
cellSelectionHandler.current.initialize(univer);
// TutorialExecutor에 Univer API 설정
tutorialExecutor.current.setUniverAPI(univerAPI);
setIsInitialized(true);
} else {
console.warn("⚠️ univerAPI가 제공되지 않음");
@@ -862,6 +869,92 @@ const TestSheetViewer: React.FC = () => {
};
}, []);
// 튜토리얼 자동 실행 로직
useEffect(() => {
const { tutorialSession } = appStore;
// 튜토리얼이 선택되었고 아직 실행되지 않은 경우
if (
tutorialSession.activeTutorial &&
tutorialSession.execution?.status === "준비중" &&
tutorialSession.isAutoMode &&
!tutorialExecutor.current.isCurrentlyExecuting()
) {
console.log(
"🎯 튜토리얼 자동 실행 시작:",
tutorialSession.activeTutorial.metadata.title,
);
const executeTutorial = async () => {
try {
// 상태를 실행중으로 업데이트
appStore.updateTutorialExecution("실행중", 1);
// 튜토리얼 실행
const result = await tutorialExecutor.current.startTutorial(
tutorialSession.activeTutorial!,
{
autoExecute: true,
stepDelay: 1500,
highlightDuration: 2000,
showFormula: true,
enableAnimation: true,
},
);
if (result.success) {
// 성공 시 상태 업데이트
appStore.updateTutorialExecution("완료", 3);
// 프롬프트 자동 입력
if (tutorialSession.activeTutorial!.prompt) {
setPrompt(tutorialSession.activeTutorial!.prompt);
setShowPromptInput(true);
}
console.log("✅ 튜토리얼 실행 완료:", result);
} else {
appStore.updateTutorialExecution(
"오류",
undefined,
"튜토리얼 실행 실패",
);
}
} catch (error) {
console.error("❌ 튜토리얼 실행 오류:", error);
appStore.updateTutorialExecution(
"오류",
undefined,
error instanceof Error ? error.message : "알 수 없는 오류",
);
}
};
// Univer가 초기화된 후 실행
if (getGlobalState().univerAPI) {
executeTutorial();
} else {
// Univer API가 준비될 때까지 대기
const checkInterval = setInterval(() => {
if (getGlobalState().univerAPI) {
clearInterval(checkInterval);
executeTutorial();
}
}, 500);
// 10초 후 타임아웃
setTimeout(() => {
clearInterval(checkInterval);
appStore.updateTutorialExecution(
"오류",
undefined,
"Univer API 초기화 타임아웃",
);
}, 10000);
}
}
}, [appStore.tutorialSession, appStore]);
return (
<div className="min-h-screen bg-gray-50 flex flex-col relative">
{/* 헤더 - App.tsx와 동일한 스타일 적용 */}