From a93d1689d8dd2d340b1e86e03c0ade9350a9780e Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 23 Apr 2026 15:35:54 +0900 Subject: [PATCH] =?UTF-8?q?feat(documents):=20=C2=A72=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=A0=84=EC=9A=A9=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20+=20=EC=8A=B9=EC=9D=B8=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit plan: ~/.claude/plans/luminous-sprouting-hamster.md §2 - GET /api/documents/stats/category-counts — Sidebar/Dashboard 용 카테고리별 문서 건수 + library_pending_suggestions - DocumentResponse 에 category / ai_suggestion 필드 노출 (§1 과 동일 수정, rebase 시 합쳐짐) - SuggestionReview.svelte 신규 — ai_suggestion.proposed_category='library' 제안 카드 리스트. 단건 승인/반려 + 체크박스 대량 승인. 409 stale 시 warning toast + 자동 refetch - /library 상단에 SuggestionReview 배치 (자료실 + 승인 대기함 겸). 승인/반려 후 tree/docs/facet 재조회 - Sidebar 재구성: 카테고리 내비(문서/자료실/뉴스/메모/검색) + 자료실 pending 배지. /api/documents/stats/category-counts 바인딩. audio/video 자리는 §3 주석 예약 Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/documents.py | 54 +++ frontend/src/lib/components/Sidebar.svelte | 208 ++++++++--- .../lib/components/SuggestionReview.svelte | 323 ++++++++++++++++++ frontend/src/routes/library/+page.svelte | 17 + 4 files changed, 557 insertions(+), 45 deletions(-) create mode 100644 frontend/src/lib/components/SuggestionReview.svelte diff --git a/app/api/documents.py b/app/api/documents.py index e645065..8407168 100644 --- a/app/api/documents.py +++ b/app/api/documents.py @@ -315,6 +315,60 @@ async def list_library_documents( ) +# ─── Section 2: 카테고리 집계 (Sidebar / Dashboard) ─── +# +# documents.category (§1 에서 추가) 가 1차 진입점. 이 엔드포인트는 Sidebar 배지 및 +# /dashboard 카테고리 카드 용. ai_suggestion.proposed_category='library' 인 +# 승인 대기 건수는 /library 의 pending 배지로 별도 표시. + + +@router.get("/stats/category-counts") +async def get_category_counts( + user: Annotated[User, Depends(get_current_user)], + session: Annotated[AsyncSession, Depends(get_session)], +): + """카테고리별 문서 건수 + 승인 대기 (library 제안) 건수. + + Response: + { + "counts": { "document": 640, "library": 12, "news": 311, ... }, + "library_pending_suggestions": 17 + } + + - 전제: §1 의 documents.category enum + ai_suggestion JSONB 가 이미 적용됨 + - category IS NULL 인 문서는 counts 에서 제외 (§1 백필 전 드문 상태) + """ + from sqlalchemy import text as sql_text + + count_rows = await session.execute( + sql_text(""" + SELECT category::text AS category, COUNT(*) AS cnt + FROM documents + WHERE deleted_at IS NULL + AND category IS NOT NULL + GROUP BY category + """) + ) + counts: dict[str, int] = {row.category: row.cnt for row in count_rows} + + pending_scalar = ( + await session.execute( + sql_text(""" + SELECT COUNT(*) + FROM documents + WHERE deleted_at IS NULL + AND ai_suggestion IS NOT NULL + AND ai_suggestion->>'proposed_category' = 'library' + """) + ) + ).scalar() + + return { + "counts": counts, + "library_pending_suggestions": int(pending_scalar or 0), + } + + @router.get("/", response_model=DocumentListResponse) async def list_documents( user: Annotated[User, Depends(get_current_user)], diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte index 0742713..e37ea69 100644 --- a/frontend/src/lib/components/Sidebar.svelte +++ b/frontend/src/lib/components/Sidebar.svelte @@ -1,14 +1,40 @@ + +
+
+
+ +

자동 분류 제안

+ {#if !loading} + + {total}건 + + {/if} +
+
+ {#if docs.length > 1} + + {/if} + {#if selected.size > 0} + + {/if} + +
+
+ + {#if loading} +
+ + 제안 불러오는 중… +
+ {:else if docs.length === 0} +
+ 대기 중인 제안이 없습니다. 발주서·세금계산서 등이 분류되면 여기 쌓입니다. +
+ {:else} + + {/if} +
diff --git a/frontend/src/routes/library/+page.svelte b/frontend/src/routes/library/+page.svelte index 15d6781..c79afa2 100644 --- a/frontend/src/routes/library/+page.svelte +++ b/frontend/src/routes/library/+page.svelte @@ -27,6 +27,15 @@ import Modal from '$lib/components/ui/Modal.svelte'; import ConfirmDialog from '$lib/components/ui/ConfirmDialog.svelte'; import Skeleton from '$lib/components/ui/Skeleton.svelte'; + import SuggestionReview from '$lib/components/SuggestionReview.svelte'; + + // §2: /library 는 "자료실 + 승인 대기함" 2역할을 겸한다. + // 승인/반려 후 자료실 목록·트리·facet count 를 재조회. + function handleSuggestionChange() { + loadTree(); + loadDocs(); + loadFacetCounts(); + } // ─── 상태 ─── @@ -445,6 +454,14 @@ {/if} + +
+ +
+