v4.0.9: 관리자 회원 목록 2열·덱 공개/전체 통계 추가

관리자가 회원별 저장 덱 이용 현황을 한눈에 볼 수 있도록 API 집계와 UI를 보강한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-23 16:51:14 +09:00
parent eb4b35bccc
commit bc30356c8b
8 changed files with 83 additions and 23 deletions

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./style/output.css?v=4.0.6" />
<link rel="stylesheet" href="./style/output.css?v=4.0.9" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&family=Noto+Sans+KR:wght@400;700;900&display=swap" rel="stylesheet" />
<title>관리자 - UNIARE</title>
</head>
@@ -35,7 +35,7 @@
<input type="search" id="user-search" class="box-border min-w-0 flex-1 rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/15" placeholder="이메일, 사용자명 검색..." />
<button type="button" id="user-search-btn" class="cursor-pointer rounded-xl border-0 bg-slate-800 px-5 py-2.5 text-sm font-bold text-white transition hover:bg-slate-700">검색</button>
</div>
<div id="user-list" class="flex flex-col gap-3"></div>
<div id="user-list" class="grid grid-cols-1 gap-3 sm:grid-cols-2"></div>
</section>
<section id="tab-decks" class="hidden">
@@ -215,23 +215,26 @@
const { users, total } = await UaDeckApi.adminGetUsers(q);
const list = document.getElementById('user-list');
list.innerHTML =
`<p class="mb-4 text-xs text-slate-400">총 ${total}명</p>` +
`<p class="col-span-full mb-1 text-xs text-slate-400">총 ${total}명</p>` +
users
.map((u) => {
const isSelf = u.id === currentAdminId;
const deckPublic = Number(u.deckPublic ?? 0);
const deckTotal = Number(u.deckTotal ?? 0);
return `
<article class="rounded-2xl border border-slate-200 bg-white px-[1.125rem] py-4 shadow-sm">
<div class="flex items-center gap-4">
<img src="${escapeHtml(resolveAvatarSrc(u))}" alt="" class="h-12 w-12 shrink-0 rounded-full border-2 border-slate-200 bg-slate-100 object-cover" onerror="this.src='${DEFAULT_AVATAR}'" />
<article class="rounded-2xl border border-slate-200 bg-white px-4 py-3.5 shadow-sm">
<div class="flex items-start gap-3">
<img src="${escapeHtml(resolveAvatarSrc(u))}" alt="" class="h-11 w-11 shrink-0 rounded-full border-2 border-slate-200 bg-slate-100 object-cover" onerror="this.src='${DEFAULT_AVATAR}'" />
<div class="min-w-0 flex-1">
<h3 class="mb-1 text-[0.9375rem] font-bold leading-snug text-slate-900">${escapeHtml(u.username)}${isSelf ? ' <span class="rounded-md bg-blue-50 px-2 py-0.5 text-[0.625rem] font-bold text-blue-600">나</span>' : ''}</h3>
<div class="text-xs leading-normal text-slate-400">${escapeHtml(u.email)}</div>
<div class="mt-2 flex flex-wrap gap-1.5">
<span class="rounded-md bg-blue-50 px-2 py-0.5 text-[0.625rem] font-bold text-blue-600">${escapeHtml(u.role)}</span>
<span class="rounded-md px-2 py-0.5 text-[0.625rem] font-bold ${u.isActive ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}">${u.isActive ? '활성' : '비활성'}</span>
<h3 class="mb-0.5 text-sm font-bold leading-snug text-slate-900">${escapeHtml(u.username)}${isSelf ? ' <span class="rounded-md bg-blue-50 px-1.5 py-0.5 text-[0.625rem] font-bold text-blue-600">나</span>' : ''}</h3>
<div class="truncate text-xs text-slate-400">${escapeHtml(u.email)}</div>
<div class="mt-1.5 text-xs font-semibold text-slate-600" title="공개 덱 / 전체 저장 덱">덱 ${deckPublic}/${deckTotal} <span class="font-normal text-slate-400">(공개/전체)</span></div>
<div class="mt-1.5 flex flex-wrap gap-1">
<span class="rounded-md bg-blue-50 px-1.5 py-0.5 text-[0.625rem] font-bold text-blue-600">${escapeHtml(u.role)}</span>
<span class="rounded-md px-1.5 py-0.5 text-[0.625rem] font-bold ${u.isActive ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-100 text-slate-500'}">${u.isActive ? '활성' : '비활성'}</span>
</div>
</div>
<button type="button" data-edit='${JSON.stringify(u).replace(/'/g, '&#39;')}' class="shrink-0 cursor-pointer rounded-lg border-0 bg-slate-100 px-3 py-1.5 text-[0.6875rem] font-bold text-slate-600 hover:bg-slate-200">수정</button>
<button type="button" data-edit='${JSON.stringify(u).replace(/'/g, '&#39;')}' class="shrink-0 cursor-pointer rounded-lg border-0 bg-slate-100 px-2.5 py-1.5 text-[0.6875rem] font-bold text-slate-600 hover:bg-slate-200">수정</button>
</div>
</article>`;
})