From b3124928a6127c4880f09b0637e1839e4c0c26e4 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 8 Apr 2026 12:13:36 +0900 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Phase=20C=20=E2=80=94=20=EB=8C=80?= =?UTF-8?q?=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EC=9C=84=EC=A0=AF=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EB=93=9C=20+=20dashboardSummary=20=EA=B3=B5=EC=9C=A0?= =?UTF-8?q?=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dashboardSummary store 구독으로 SystemStatusDot과 fetch 1회 공유 (60초 폴링) - Svelte 5 runes + Card/EmptyState/Skeleton/FormatIcon 프리미티브 - 12-col 그리드 (sm 1열 / md 2열 / lg 표 그대로): * 행1: stat 4장 (전체/Inbox/법령/시스템 상태) * 행2: 파이프라인 가로 막대 차트(8) + 오늘 도메인 누적바(4) * 행3: 최근 문서(8) + CalDAV stub(4) - 신규 util: domainSlug.ts — ai_domain → bg-domain-{slug} + 라벨 매핑 - 새 코드에 bg-[var(--*)] 0건 (lint:tokens 통과) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/lib/utils/domainSlug.ts | 66 ++++ frontend/src/routes/+page.svelte | 456 ++++++++++++++++++++++----- 2 files changed, 448 insertions(+), 74 deletions(-) create mode 100644 frontend/src/lib/utils/domainSlug.ts diff --git a/frontend/src/lib/utils/domainSlug.ts b/frontend/src/lib/utils/domainSlug.ts new file mode 100644 index 0000000..fab0932 --- /dev/null +++ b/frontend/src/lib/utils/domainSlug.ts @@ -0,0 +1,66 @@ +// ai_domain 값을 @theme의 bg-domain-{slug} 토큰 슬러그로 매핑. +// 백엔드는 'Knowledge/Philosophy', 'Knowledge/Industrial_Safety', 'Reference', null +// 형태를 모두 보낼 수 있으므로 leaf만 잘라 lowercase하고 industrial_safety→safety로 재매핑. +// +// Phase C: 대시보드 today_by_domain 누적바, recent_documents accent에서 사용. +// 추후 phase에서도 도메인 색상 칩이 필요해지면 여기로 일원화. + +export type DomainSlug = + | 'philosophy' + | 'language' + | 'engineering' + | 'safety' + | 'programming' + | 'general' + | 'reference'; + +const LEAF_TO_SLUG: Record = { + philosophy: 'philosophy', + language: 'language', + engineering: 'engineering', + industrial_safety: 'safety', + safety: 'safety', + programming: 'programming', + general: 'general', + reference: 'reference', +}; + +/** 'Knowledge/Philosophy' → 'philosophy', 'Reference' → 'reference', null → null */ +export function domainSlug(domain: string | null | undefined): DomainSlug | null { + if (!domain) return null; + const leaf = domain.includes('/') ? domain.split('/').pop()! : domain; + return LEAF_TO_SLUG[leaf.toLowerCase()] ?? null; +} + +const SLUG_BG_CLASS: Record = { + philosophy: 'bg-domain-philosophy', + language: 'bg-domain-language', + engineering: 'bg-domain-engineering', + safety: 'bg-domain-safety', + programming: 'bg-domain-programming', + general: 'bg-domain-general', + reference: 'bg-domain-reference', +}; + +/** bg-domain-{slug} 클래스. 미지정/미매핑은 bg-default. */ +export function domainBgClass(domain: string | null | undefined): string { + const slug = domainSlug(domain); + return slug ? SLUG_BG_CLASS[slug] : 'bg-default'; +} + +const SLUG_LABEL: Record = { + philosophy: 'Philosophy', + language: 'Language', + engineering: 'Engineering', + safety: 'Industrial Safety', + programming: 'Programming', + general: 'General', + reference: 'Reference', +}; + +/** 표시용 라벨. domain이 null이면 '미분류'. */ +export function domainLabel(domain: string | null | undefined): string { + const slug = domainSlug(domain); + if (!slug) return domain ?? '미분류'; + return SLUG_LABEL[slug]; +} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 752d522..2bbd44a 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,88 +1,396 @@ -
-
+

대시보드

{#if loading} -
+ +
{#each Array(4) as _} -
- {/each} -
- {:else if dashboard} - -
- -
-

전체 문서

-

{dashboard.total_documents}

-

오늘 +{dashboard.today_added}

-
- - -
-

Inbox 미분류

-

0}>{dashboard.inbox_count}

- {#if dashboard.inbox_count > 0} - 분류하기 - {/if} -
- - -
-

법령 알림

-

{dashboard.law_alerts}

-

오늘 변경

-
- - -
-

파이프라인

- {#if dashboard.failed_count > 0} -

{dashboard.failed_count} 실패

- {:else} -

정상

- {/if} -
-
- - -
-

최근 문서

- {#if dashboard.recent_documents.length > 0} -
- {#each dashboard.recent_documents as doc} - -
- {doc.file_format} - {doc.title || '제목 없음'} -
- {doc.ai_domain || ''} -
- {/each} +
+ + + + +
- {:else} -

문서가 없습니다

- {/if} + {/each} +
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ {:else if summary} +
+ +
+ +
+

전체 문서

+ +
+

+ {summary.total_documents.toLocaleString()} +

+

+ 오늘 +{summary.today_added} +

+
+
+ +
+ +
+

Inbox 미분류

+ +
+

+ {summary.inbox_count} +

+ {#if summary.inbox_count > 0} + + 분류하기 → + + {:else} +

대기 없음

+ {/if} +
+
+ +
+ +
+

법령 알림

+ +
+

{summary.law_alerts}

+

오늘 변경

+
+
+ +
+ +
+

시스템 상태

+ +
+ {#if systemView} +

+ {systemView.label} +

+

{systemView.sub}

+ {/if} +
+
+ + +
+ +
+

파이프라인 상태

+ 최근 24시간 +
+ {#if pipelineRows.length > 0} +
+ {#each pipelineRows as row (row.stage)} +
+
+ {row.label} + + 대기 {row.pending} · + 처리 {row.processing} · + 실패 0}>{row.failed} + +
+
+ {#if row.pending > 0} +
+ {/if} + {#if row.processing > 0} +
+ {/if} + {#if row.failed > 0} +
+ {/if} +
+
+ {/each} +
+ {:else} + + {/if} +
+
+ +
+ +
+

오늘 도메인 분포

+ + {domainData.total}건 + +
+ {#if domainData.rows.length > 0} + +
+ {#each domainData.rows as r (r.label)} +
+ {/each} +
+
    + {#each domainData.rows as r (r.label)} +
  • + + + {r.label} + + {r.count} +
  • + {/each} +
+ {:else} + + {/if} +
+
+ + +
+ +

최근 문서

+ {#if summary.recent_documents.length > 0} + + {:else} + + {/if} +
+
+ +
+ +

오늘의 할 일

+ +
+
{/if}