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} + +
+ +
+