Phase 0.5: React UI 기반 계층 구축 — API 클라이언트 + TanStack Query + shadcn UI

1. 서버/UI 상태 분리 (선택지 B 채택)
   - ontologySlice: entities/relations 등 서버 상태 제거 → OntologyDraft(편집 폼)만
   - crawlSlice: progress/stats/logs 제거 → CrawlUiState(토글/확장 상태)만
   - 서버 상태는 전부 TanStack Query로 이전

2. API 클라이언트 계층 (src/lib/api/)
   - client.ts: fetch 래퍼 + Zod 스키마 검증 + ApiError
   - projects.ts: projectsApi.list/detail/create + 도메인 스키마

3. TanStack Query 훅 (src/hooks/)
   - useProjects, useProject, useCreateProject
   - queryKeys 팩토리로 키 일관성

4. shadcn 스타일 UI 프리미티브 (src/components/ui/)
   - button, card, skeleton
   - lib/utils.ts: cn() helper (clsx + tailwind-merge)

5. 레이아웃 (src/components/layout/)
   - AppShell: 축소 가능 Sidebar + Header + Outlet
   - App.tsx 라우트를 AppShell로 중첩

6. DashboardPage End-to-end 연결
   - useProjects()로 백엔드 /projects 호출
   - loading skeleton / error+retry / empty state / 카드 그리드 4가지 상태 모두 처리

7. i18n locale 파일 추가
   - public/locales/ko/common.json (한국어)
   - public/locales/en/common.json (영문 fallback)
   - 키: nav.*, dashboard.*, common.*, app.*

8. 레거시 정리
   - vanilla JS/CSS 24개 → src/legacy/로 git mv
   - tailwind content를 *.{ts,tsx}로 좁혀 legacy 제외

다음 단계: Phase 1 — OnboardingPage 파일 업로드 + useCreateProject 연결

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
lasta
2026-05-14 14:17:26 +09:00
parent 1be2c7d3a3
commit 37cad40472
41 changed files with 669 additions and 232 deletions

View File

@@ -0,0 +1,83 @@
import "./styles/index.css";
import { mountShell } from "./shell.js";
import { mountSidebar, renderProjects, renderSourceOptions } from "./sidebar.js";
import {
mountWorkspace,
renderOverview,
renderOntology,
loadOntologyRegistry,
loadEntities,
loadExtractionLogs,
loadResearchSessions,
loadGraphQuery,
loadTags,
} from "./workspace.js";
import { loadGraph } from "./graph.js";
import { loadWorkbench } from "./workbench.js";
import { loadPipeline } from "./pipeline.js";
import { mountGlobalSearch } from "./search.js";
import { mountInspector, refreshInspector } from "./inspector.js";
import { api } from "./api.js";
import { state } from "./state.js";
import { toast } from "./utils.js";
import { onLangChange } from "./i18n.js";
const root = document.getElementById("app");
const hosts = mountShell(root);
mountSidebar(hosts.sidebarHost, {
onProjectChanged: loadProjects,
onCrawlUpdate: () => refreshInspector(),
refreshAll,
});
mountWorkspace(hosts.workspaceHost);
mountInspector(hosts.inspectorHost);
mountGlobalSearch();
document.getElementById("refreshBtn").addEventListener("click", loadProjects);
window.addEventListener("project:select", (event) => selectProject(event.detail));
window.addEventListener("workspace:refresh", refreshAll);
onLangChange(() => refreshInspector());
loadProjects().catch((error) => toast(error.message));
async function loadProjects() {
state.projects = await api("/projects");
if (!state.selectedProject && state.projects.length) {
state.selectedProject = state.projects[0].name;
}
renderProjects();
if (state.selectedProject) {
await selectProject(state.selectedProject);
}
}
async function selectProject(projectName) {
state.selectedProject = projectName;
state.projectDetail = await api(`/projects/${encodeURIComponent(projectName)}`);
state.ontology = await api(`/ontology/${encodeURIComponent(state.projectDetail.domain)}`);
renderProjects();
renderSourceOptions(state.projectDetail);
renderOverview();
renderOntology();
refreshInspector();
await refreshAll();
}
async function refreshAll() {
if (!state.selectedProject) return;
const loaders = [
["pipeline", loadPipeline],
["ontology registry", loadOntologyRegistry],
["entities", loadEntities],
["claims workbench", loadWorkbench],
["extraction logs", loadExtractionLogs],
["research sessions", loadResearchSessions],
["graph query", loadGraphQuery],
["tags", loadTags],
["graph", loadGraph],
];
await Promise.all(loaders.map(async ([label, fn]) => {
try { await fn(); } catch (error) { console.warn(`${label} load failed`, error); }
}));
refreshInspector();
}