diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index 3523c5d..26b966d 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -14,7 +14,7 @@

Dashboard

수집 → 큐레이션 → 재가공 → 발행 파이프라인 현황

- @@ -110,6 +110,8 @@ function fmt(n){ return (n==null) ? '0' : Number(n).toLocaleString(); } function pct(n, total){ return total > 0 ? Math.round((Number(n||0)/total)*100) : 0; } + let dashboardAnimated = false; + function countUp(el, target){ target = Number(target||0); if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){ el.textContent = fmt(target); return; } @@ -123,6 +125,12 @@ requestAnimationFrame(tick); } + function setKpi(id, value){ + const el = document.getElementById(id); + if(!el) return; + if(dashboardAnimated) el.textContent = fmt(value); else countUp(el, value); + } + async function getData(url){ const r = await fetch(url); const j = await r.json().catch(()=>({})); @@ -142,7 +150,7 @@ function ratioBadgeClass(r){ const v=Number(r); return v>=10?'badge-danger':(v>=2?'badge-warning':'badge-success'); } async function loadDashboard(){ - const refreshIcon = document.querySelector('button[onclick="loadDashboard()"] svg, button[onclick="loadDashboard()"] i'); + const refreshIcon = document.querySelector('#refreshBtn svg, #refreshBtn i'); if (refreshIcon) refreshIcon.classList.add('animate-spin'); let d; try { @@ -159,14 +167,14 @@ const pub = d.publish || {}, pbs = pub.byStatus || {}; // KPI (count-up for numbers, textContent for captions) - countUp(document.getElementById('kTotal'), total); + setKpi('kTotal', total); document.getElementById('kTotalCap').textContent = '채널 ' + fmt(src.CHANNEL) + ' · 검색 ' + fmt(src.SEARCH); - countUp(document.getElementById('kNew'), bs.NEW); + setKpi('kNew', bs.NEW); document.getElementById('kReviewCap').textContent = '검토중 ' + fmt(bs.REVIEWING); - countUp(document.getElementById('kTarget'), bs.TARGET); - countUp(document.getElementById('kDone'), bs.DONE); + setKpi('kTarget', bs.TARGET); + setKpi('kDone', bs.DONE); document.getElementById('kExcludedCap').textContent = '제외 ' + fmt(bs.EXCLUDED); - countUp(document.getElementById('kPublished'), pbs.PUBLISHED); + setKpi('kPublished', pbs.PUBLISHED); document.getElementById('kPublishCap').textContent = '대기 ' + fmt(pbs.READY) + ' · 작성중 ' + fmt(pbs.DRAFT); // 깔때기 (총 수집 대비) — 3-tone color scheme @@ -192,7 +200,7 @@ '
'+esc(v.title)+'
' + '
'+esc(v.channelTitle||'')+' · 조회 '+fmt(v.viewCount)+'
' + '' + - ''+ratio+''; + ''+ratio+''; }).join(''); } @@ -224,7 +232,7 @@ if(cats.length===0 && !uncat){ catList.innerHTML = '

분류된 영상이 없습니다.

'; } else { catList.innerHTML = - cats.map(c => ''+bar(c.name, c.count, total, 'var(--primary)')+'').join('') + + cats.map(c => ''+bar(c.name, c.count, total, 'var(--primary)')+'').join('') + ''+bar('미분류', uncat, total, '#475569')+''; } @@ -239,6 +247,7 @@ ''+bar('롱폼', longForm, total, 'var(--success)')+''; if(window.lucide) lucide.createIcons(); + dashboardAnimated = true; } loadDashboard();