fix(dashboard): null-ratio badge color, escape categoryId, robust refresh selector, animate once
This commit is contained in:
parent
2d6c962567
commit
8fe90e431f
@ -14,7 +14,7 @@
|
||||
<h1 class="text-xl font-bold mb-1">Dashboard</h1>
|
||||
<p class="text-muted">수집 → 큐레이션 → 재가공 → 발행 파이프라인 현황</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary px-3 py-2 flex items-center gap-1" onclick="loadDashboard()">
|
||||
<button id="refreshBtn" class="btn btn-secondary px-3 py-2 flex items-center gap-1" onclick="loadDashboard()">
|
||||
<i data-lucide="refresh-cw" style="width:16px;"></i> 새로고침
|
||||
</button>
|
||||
</div>
|
||||
@ -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 @@
|
||||
'<div style="min-width:0;"><div class="text-sm font-bold truncate" style="max-width:300px;">'+esc(v.title)+'</div>' +
|
||||
'<div class="text-muted" style="font-size:0.7rem;">'+esc(v.channelTitle||'')+' · 조회 '+fmt(v.viewCount)+'</div></div>' +
|
||||
'</div>' +
|
||||
'<span class="badge '+ratioBadgeClass(v.viewsPerSubRatio)+'" style="flex-shrink:0;">'+ratio+'</span></a>';
|
||||
'<span class="badge '+(v.viewsPerSubRatio != null ? ratioBadgeClass(v.viewsPerSubRatio) : 'badge-muted')+'" style="flex-shrink:0;">'+ratio+'</span></a>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@ -224,7 +232,7 @@
|
||||
if(cats.length===0 && !uncat){ catList.innerHTML = '<p class="text-muted text-sm">분류된 영상이 없습니다.</p>'; }
|
||||
else {
|
||||
catList.innerHTML =
|
||||
cats.map(c => '<a href="/collection?categoryId='+c.id+'" style="text-decoration:none; display:block;">'+bar(c.name, c.count, total, 'var(--primary)')+'</a>').join('') +
|
||||
cats.map(c => '<a href="/collection?categoryId='+encodeURIComponent(c.id)+'" style="text-decoration:none; display:block;">'+bar(c.name, c.count, total, 'var(--primary)')+'</a>').join('') +
|
||||
'<a href="/collection" style="text-decoration:none; display:block;">'+bar('미분류', uncat, total, '#475569')+'</a>';
|
||||
}
|
||||
|
||||
@ -239,6 +247,7 @@
|
||||
'<a href="/collection" style="text-decoration:none; display:block;">'+bar('롱폼', longForm, total, 'var(--success)')+'</a>';
|
||||
|
||||
if(window.lucide) lucide.createIcons();
|
||||
dashboardAnimated = true;
|
||||
}
|
||||
|
||||
loadDashboard();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user