diff --git a/frontend/src/routes/library/+page.svelte b/frontend/src/routes/library/+page.svelte index 7e1a8f4..b9890d0 100644 --- a/frontend/src/routes/library/+page.svelte +++ b/frontend/src/routes/library/+page.svelte @@ -52,6 +52,19 @@ const ODF_FORMATS = ['ods', 'odt', 'odp', 'odoc', 'osheet']; const DEFAULT_LIBRARY_PATH = '미분류'; const MAX_DEPTH = 5; + const FACET_LABELS = { company: '회사', topic: '주제', year: '연도', doctype: '문서유형' }; + const FACET_KEYS = ['facet_company', 'facet_topic', 'facet_year', 'facet_doctype']; + + // ─── Facet 상태 ─── + + let facetCounts = $state({ company: [], topic: [], year: [], doctype: [] }); + let facetLoading = $state(false); + + let activeFacetCompany = $derived($page.url.searchParams.get('facet_company')); + let activeFacetTopic = $derived($page.url.searchParams.get('facet_topic')); + let activeFacetYear = $derived($page.url.searchParams.get('facet_year')); + let activeFacetDoctype = $derived($page.url.searchParams.get('facet_doctype')); + let hasAnyFacet = $derived(activeFacetCompany || activeFacetTopic || activeFacetYear || activeFacetDoctype); // ─── 카테고리 CRUD 상태 ─── @@ -90,6 +103,10 @@ if (activePath) params.set('path', activePath); if (activeSort) params.set('sort', activeSort); if (activeQ) params.set('q', activeQ); + if (activeFacetCompany) params.set('facet_company', activeFacetCompany); + if (activeFacetTopic) params.set('facet_topic', activeFacetTopic); + if (activeFacetYear) params.set('facet_year', activeFacetYear); + if (activeFacetDoctype) params.set('facet_doctype', activeFacetDoctype); params.set('page', String(activePage)); params.set('page_size', '20'); const result = await api(`/documents/library?${params}`); @@ -102,17 +119,42 @@ } } + async function loadFacetCounts() { + facetLoading = true; + try { + const params = new URLSearchParams(); + if (activePath) params.set('library_path', activePath); + if (activeQ) params.set('q', activeQ); + if (activeFacetCompany) params.set('facet_company', activeFacetCompany); + if (activeFacetTopic) params.set('facet_topic', activeFacetTopic); + if (activeFacetYear) params.set('facet_year', activeFacetYear); + if (activeFacetDoctype) params.set('facet_doctype', activeFacetDoctype); + facetCounts = await api(`/library/facet-counts?${params}`); + } catch { + /* facet 실패해도 문서 목록은 정상 */ + } finally { + facetLoading = false; + } + } + onMount(() => { loadTree(); }); - // URL 파라미터 변경 시 문서 목록 재로드 + // 문서 목록: 모든 URL 파라미터 변경 시 재로드 $effect(() => { // eslint-disable-next-line no-unused-expressions - activePath, activeSort, activeQ, activePage; + activePath, activeSort, activeQ, activePage, activeFacetCompany, activeFacetTopic, activeFacetYear, activeFacetDoctype; loadDocs(); }); + // facet counts: path/q/facet 변경 시만 재집계 (page/sort 제외) + $effect(() => { + // eslint-disable-next-line no-unused-expressions + activePath, activeQ, activeFacetCompany, activeFacetTopic, activeFacetYear, activeFacetDoctype; + loadFacetCounts(); + }); + // 선택된 경로의 부모 자동 펼치기 $effect(() => { if (activePath) { @@ -159,6 +201,25 @@ goto(`/library?${params}`, { noScroll: true }); } + function toggleFacet(key, value) { + const params = new URLSearchParams($page.url.searchParams); + const current = params.get(key); + if (current === String(value)) { + params.delete(key); + } else { + params.set(key, String(value)); + } + params.delete('page'); + goto(`/library?${params}`, { noScroll: true }); + } + + function clearAllFacets() { + const params = new URLSearchParams($page.url.searchParams); + for (const k of FACET_KEYS) params.delete(k); + params.delete('page'); + goto(`/library?${params}`, { noScroll: true }); + } + function toggleExpand(path) { expanded[path] = !expanded[path]; } @@ -480,6 +541,53 @@ {/if} + + + {#if facetCounts.company.length > 0 || facetCounts.topic.length > 0 || facetCounts.year.length > 0 || facetCounts.doctype.length > 0} +