- +layout.svelte: 사이드바 + 상단 nav 통합 (로그인/셋업 제외) - 각 페이지 중복 nav 제거 (dashboard, documents, detail, inbox, settings) - 모바일 drawer + ESC 닫기 전역 처리 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
3.7 KiB
Svelte
90 lines
3.7 KiB
Svelte
<script>
|
|
import { onMount } from 'svelte';
|
|
import { goto } from '$app/navigation';
|
|
import { api } from '$lib/api';
|
|
import { addToast } from '$lib/stores/ui';
|
|
|
|
let dashboard = null;
|
|
let loading = true;
|
|
|
|
onMount(async () => {
|
|
try {
|
|
dashboard = await api('/dashboard/');
|
|
} catch (err) {
|
|
addToast('error', '대시보드 로딩 실패');
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="p-4 lg:p-6">
|
|
<div class="max-w-6xl mx-auto">
|
|
<h2 class="text-xl font-bold mb-6">대시보드</h2>
|
|
|
|
{#if loading}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
{#each Array(4) as _}
|
|
<div class="bg-[var(--surface)] rounded-xl p-5 border border-[var(--border)] animate-pulse h-28"></div>
|
|
{/each}
|
|
</div>
|
|
{:else if dashboard}
|
|
<!-- 위젯 그리드 -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
|
<!-- 전체 문서 -->
|
|
<div class="bg-[var(--surface)] rounded-xl p-5 border border-[var(--border)]">
|
|
<p class="text-sm text-[var(--text-dim)]">전체 문서</p>
|
|
<p class="text-3xl font-bold mt-1">{dashboard.total_documents}</p>
|
|
<p class="text-xs text-[var(--text-dim)] mt-1">오늘 +{dashboard.today_added}</p>
|
|
</div>
|
|
|
|
<!-- Inbox -->
|
|
<div class="bg-[var(--surface)] rounded-xl p-5 border border-[var(--border)]">
|
|
<p class="text-sm text-[var(--text-dim)]">Inbox 미분류</p>
|
|
<p class="text-3xl font-bold mt-1" class:text-[var(--warning)]={dashboard.inbox_count > 0}>{dashboard.inbox_count}</p>
|
|
{#if dashboard.inbox_count > 0}
|
|
<a href="/inbox" class="text-xs text-[var(--accent)] hover:underline mt-1 inline-block">분류하기</a>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- 법령 알림 -->
|
|
<div class="bg-[var(--surface)] rounded-xl p-5 border border-[var(--border)]">
|
|
<p class="text-sm text-[var(--text-dim)]">법령 알림</p>
|
|
<p class="text-3xl font-bold mt-1">{dashboard.law_alerts}</p>
|
|
<p class="text-xs text-[var(--text-dim)] mt-1">오늘 변경</p>
|
|
</div>
|
|
|
|
<!-- 파이프라인 -->
|
|
<div class="bg-[var(--surface)] rounded-xl p-5 border border-[var(--border)]">
|
|
<p class="text-sm text-[var(--text-dim)]">파이프라인</p>
|
|
{#if dashboard.failed_count > 0}
|
|
<p class="text-3xl font-bold mt-1 text-[var(--error)]">{dashboard.failed_count} 실패</p>
|
|
{:else}
|
|
<p class="text-3xl font-bold mt-1 text-[var(--success)]">정상</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 최근 문서 -->
|
|
<div class="bg-[var(--surface)] rounded-xl border border-[var(--border)] p-5">
|
|
<h3 class="text-sm font-semibold text-[var(--text-dim)] mb-3">최근 문서</h3>
|
|
{#if dashboard.recent_documents.length > 0}
|
|
<div class="space-y-2">
|
|
{#each dashboard.recent_documents as doc}
|
|
<a href="/documents/{doc.id}" class="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-[var(--bg)] transition-colors">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-xs px-2 py-0.5 rounded bg-[var(--border)] text-[var(--text-dim)] uppercase">{doc.file_format}</span>
|
|
<span class="text-sm truncate max-w-md">{doc.title || '제목 없음'}</span>
|
|
</div>
|
|
<span class="text-xs text-[var(--text-dim)]">{doc.ai_domain || ''}</span>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<p class="text-sm text-[var(--text-dim)]">문서가 없습니다</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|