Files
AI/crawler_platform/app/web/frontend/src/legacy/utils.js
lasta 37cad40472 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>
2026-05-14 14:17:26 +09:00

43 lines
1.2 KiB
JavaScript

import { t } from "./i18n.js";
export const $ = (id) => document.getElementById(id);
export function csv(value) {
return String(value || "").split(",").map((item) => item.trim()).filter(Boolean);
}
export function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
export function chip(value) {
return `<span class="chip">${escapeHtml(value)}</span>`;
}
export function table(headers, rows) {
if (!rows.length) {
return `<table><tbody><tr><td>${escapeHtml(t("table.no_data"))}</td></tr></tbody></table>`;
}
return `
<table>
<thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr></thead>
<tbody>${rows.map((row) => `<tr>${row.map((cell) => `<td>${String(cell ?? "")}</td>`).join("")}</tr>`).join("")}</tbody>
</table>
`;
}
let toastTimer = null;
export function toast(message) {
const node = document.getElementById("toast");
if (!node) return;
node.textContent = message;
node.classList.add("show");
if (toastTimer) window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(() => node.classList.remove("show"), 2400);
}