From fa0175058ae6806c627224f9b681c3210d88011d Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Tue, 14 Apr 2026 09:19:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=EC=B9=B4=EC=9A=B4=ED=8A=B8?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20=E2=80=94=20=EB=AC=B8=EC=84=9C=ED=95=A8?= =?UTF-8?q?/=EB=A9=94=EB=AA=A8/=EB=89=B4=EC=8A=A4/=EC=8A=B9=EC=9D=B8?= =?UTF-8?q?=EB=8C=80=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전체 문서 1개 카드를 6개로 분리: 문서함, 메모, 뉴스, 승인대기, 법령알림, 시스템. 단일 FILTER 쿼리로 효율적 카운트. 각 카드 클릭 시 해당 페이지로 이동. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/api/dashboard.py | 27 ++++++++-- frontend/src/lib/stores/system.ts | 3 ++ frontend/src/routes/+page.svelte | 88 +++++++++++++++++-------------- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/app/api/dashboard.py b/app/api/dashboard.py index d583df4..5c9246d 100644 --- a/app/api/dashboard.py +++ b/app/api/dashboard.py @@ -44,6 +44,10 @@ class DashboardResponse(BaseModel): pipeline_status: list[PipelineStatus] failed_count: int total_documents: int + # 카운트 분리: 문서함(비-note/비-news) / 메모(memo+note) / 뉴스(news) + documents_count: int = 0 + memos_count: int = 0 + news_count: int = 0 @router.get("/", response_model=DashboardResponse) @@ -108,9 +112,23 @@ async def get_dashboard( ) failed_count = failed_result.scalar() or 0 - # 전체 문서 수 - total_result = await session.execute(select(func.count(Document.id))) - total_documents = total_result.scalar() or 0 + # 전체 문서 수 + 카테고리별 분리 (단일 쿼리) + # 문서함: 비-note, 비-news / 메모: memo+note / 뉴스: news 유입 경로 기준 + count_result = await session.execute( + text(""" + SELECT + COUNT(*) AS total, + COUNT(*) FILTER (WHERE source_channel != 'news' AND file_type != 'note') AS documents, + COUNT(*) FILTER (WHERE source_channel = 'memo' AND file_type = 'note') AS memos, + COUNT(*) FILTER (WHERE source_channel = 'news') AS news + FROM documents WHERE deleted_at IS NULL + """) + ) + counts = count_result.one() + total_documents = counts[0] + documents_count = counts[1] + memos_count = counts[2] + news_count = counts[3] return DashboardResponse( today_added=today_added, @@ -135,4 +153,7 @@ async def get_dashboard( ], failed_count=failed_count, total_documents=total_documents, + documents_count=documents_count, + memos_count=memos_count, + news_count=news_count, ) diff --git a/frontend/src/lib/stores/system.ts b/frontend/src/lib/stores/system.ts index 15c44c0..3cfbb5b 100644 --- a/frontend/src/lib/stores/system.ts +++ b/frontend/src/lib/stores/system.ts @@ -35,6 +35,9 @@ export interface DashboardSummary { pipeline_status: PipelineStatus[]; failed_count: number; total_documents: number; + documents_count: number; + memos_count: number; + news_count: number; } const POLL_INTERVAL_MS = 60_000; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 2bbd44a..0732bb0 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -15,7 +15,7 @@ import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Skeleton from '$lib/components/ui/Skeleton.svelte'; import FormatIcon from '$lib/components/FormatIcon.svelte'; - import { Calendar, Inbox, Scale, FileText, Activity } from 'lucide-svelte'; + import { Calendar, Inbox, Scale, FileText, Activity, StickyNote, Newspaper } from 'lucide-svelte'; // ─── 파생 값들 ─────────────────────────────────────────────── // legacy store + runes 혼합: 템플릿에서 $dashboardSummary로 자동 구독. @@ -175,70 +175,78 @@ {:else if summary}