feat(ui): discover table fits viewport (merged 영상 column)
Merge thumbnail+title+channel into one "영상" column like 수집함 so the table fits without horizontal scroll. Promote .coll-table/.row-sel to global style.css for reuse across list pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f3be6bd803
commit
652d25a483
@ -402,6 +402,13 @@ tbody td { padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--border); }
|
||||
tbody tr { transition: background 0.15s ease; }
|
||||
tbody tr:hover { background: var(--hover); }
|
||||
|
||||
/* Compact list table: number/status cells stay one line, "영상" cell stays wide */
|
||||
.coll-table th, .coll-table td { white-space: nowrap; vertical-align: middle; }
|
||||
.coll-table td:first-child, .coll-table th:first-child { white-space: normal; width: 42%; min-width: 280px; }
|
||||
.coll-table thead th { background: var(--surface-2); }
|
||||
.row-sel { padding: 4px 6px; background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: 6px; color: var(--text); font-size: 0.78rem; outline: none; max-width: 110px; }
|
||||
.row-sel option { background: var(--surface); color: var(--text); }
|
||||
|
||||
/* ===== Modal (reusable) ===== */
|
||||
.modal-overlay {
|
||||
display: none; position: fixed; inset: 0; z-index: 1000;
|
||||
|
||||
@ -84,23 +84,21 @@
|
||||
|
||||
<!-- 결과 테이블 -->
|
||||
<div class="card p-0" style="overflow-x:auto;">
|
||||
<table class="w-full" style="border-collapse:collapse; text-align:left;">
|
||||
<thead style="background:var(--surface-2); border-bottom:1px solid var(--glass-border);">
|
||||
<table class="w-full coll-table" style="border-collapse:collapse; text-align:left;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-3 text-sm font-bold text-muted">썸네일</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">제목</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">채널</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">구독자</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">조회수</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">시간당</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">배율</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">업로드</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">상태</th>
|
||||
<th class="p-3 text-sm font-bold text-muted">관리</th>
|
||||
<th>영상</th>
|
||||
<th>구독자</th>
|
||||
<th>조회수</th>
|
||||
<th>시간당</th>
|
||||
<th>배율</th>
|
||||
<th>업로드</th>
|
||||
<th>상태</th>
|
||||
<th style="text-align:right;">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="resultBody">
|
||||
<tr><td colspan="10" class="p-8 text-center text-muted">로딩 중...</td></tr>
|
||||
<tr><td colspan="8" class="p-8 text-center text-muted">로딩 중...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -156,7 +154,7 @@
|
||||
let data;
|
||||
try { data = await api(API + '/discover?' + p.toString()); }
|
||||
catch(e){
|
||||
body.innerHTML = '<tr><td colspan="10" class="p-8 text-center text-danger">불러오기 실패: '+esc(e.message)+'</td></tr>';
|
||||
body.innerHTML = '<tr><td colspan="8" class="p-8 text-center text-danger">불러오기 실패: '+esc(e.message)+'</td></tr>';
|
||||
return;
|
||||
}
|
||||
renderRows(data || []);
|
||||
@ -174,38 +172,37 @@
|
||||
const body = document.getElementById('resultBody');
|
||||
document.getElementById('resultCount').textContent = list.length + '건';
|
||||
if(list.length===0){
|
||||
body.innerHTML = '<tr><td colspan="10" class="p-8 text-center text-muted">조건에 맞는 발굴 후보가 없습니다.</td></tr>';
|
||||
body.innerHTML = '<tr><td colspan="8" class="p-8 text-center text-muted">조건에 맞는 발굴 후보가 없습니다.</td></tr>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = list.map(v=>{
|
||||
const statusOpts = STATUS_KEYS.map(k=>
|
||||
`<option value="${k}" ${v.interestStatus===k?'selected':''}>${STATUS_LABEL[k]}</option>`).join('');
|
||||
const star = v.bookmarked ? '#f59e0b' : 'var(--text-muted)';
|
||||
const shortsBadge = v.isShorts ? '<span style="font-size:0.65rem; background:#7C3AED33; color:#a78bfa; padding:1px 5px; border-radius:4px; margin-left:4px;">Shorts</span>' : '';
|
||||
return `<tr style="border-bottom:1px solid var(--glass-border);">
|
||||
<td class="p-3">
|
||||
<div style="position:relative; cursor:pointer; width:96px;" data-vid="${v.videoId}" data-title="${esc(v.title)}" onclick="openVideoModal(this.dataset.vid, this.dataset.title)">
|
||||
<img src="${esc(v.thumbnailUrl)}" style="width:96px; height:54px; object-fit:cover; border-radius:6px;">
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3" style="max-width:280px;">
|
||||
<div class="font-bold text-sm" style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
|
||||
const shortsBadge = v.isShorts ? '<span class="badge badge-primary" style="margin-left:5px;">Shorts</span>' : '';
|
||||
return `<tr>
|
||||
<td>
|
||||
<div class="flex items-center gap-3" style="min-width:0;">
|
||||
<img src="${esc(v.thumbnailUrl)}" data-vid="${v.videoId}" data-title="${esc(v.title)}" onclick="openVideoModal(this.dataset.vid, this.dataset.title)"
|
||||
style="width:84px; height:47px; object-fit:cover; border-radius:6px; cursor:pointer; flex-shrink:0;">
|
||||
<div style="min-width:0;">
|
||||
<div class="font-semibold text-sm" style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; line-height:1.35;">
|
||||
<a href="https://www.youtube.com/watch?v=${v.videoId}" target="_blank" class="hover:underline">${esc(v.title)}</a>${shortsBadge}
|
||||
</div>
|
||||
<a href="${v.ytChannelId?('https://www.youtube.com/channel/'+v.ytChannelId):'#'}" target="_blank" class="hover:underline truncate" style="color:var(--text-3); font-size:0.72rem; display:block; margin-top:2px;">${esc(v.channelTitle||'-')}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3 text-sm text-muted" style="max-width:130px;">
|
||||
<a href="${v.ytChannelId?('https://www.youtube.com/channel/'+v.ytChannelId):'#'}" target="_blank" class="hover:underline truncate" style="color:var(--text-2);">${esc(v.channelTitle||'-')}</a>
|
||||
</td>
|
||||
<td class="p-3 text-sm">${fmt(v.subscriberCount)}</td>
|
||||
<td class="p-3 text-sm">${fmt(v.viewCount)}</td>
|
||||
<td class="p-3 text-sm">${fmt(v.viewsPerHour)}</td>
|
||||
<td class="p-3 text-sm">${ratioBadge(v.viewsPerSubRatio)}</td>
|
||||
<td class="p-3 text-sm text-muted">${fmtDate(v.publishedAt)}</td>
|
||||
<td class="p-3"><select class="row-sel" onchange="setStatus(${v.id}, this.value)">${statusOpts}</select></td>
|
||||
<td class="p-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<a class="btn btn-primary p-2" title="재가공" href="/rework/${v.id}"><i data-lucide="wand-2" style="width:15px;"></i></a>
|
||||
<button class="btn btn-secondary p-2" title="북마크" onclick="toggleBookmark(${v.id}, ${!!v.bookmarked})"><i data-lucide="star" style="width:15px; color:${star};"></i></button>
|
||||
<td class="text-sm">${fmt(v.subscriberCount)}</td>
|
||||
<td class="text-sm">${fmt(v.viewCount)}</td>
|
||||
<td class="text-sm">${fmt(v.viewsPerHour)}</td>
|
||||
<td class="text-sm">${ratioBadge(v.viewsPerSubRatio)}</td>
|
||||
<td class="text-sm text-muted">${fmtDate(v.publishedAt)}</td>
|
||||
<td><select class="row-sel" onchange="setStatus(${v.id}, this.value)">${statusOpts}</select></td>
|
||||
<td>
|
||||
<div class="flex items-center gap-1 justify-end">
|
||||
<a class="btn btn-primary" style="padding:0.4rem;" title="재가공" href="/rework/${v.id}"><i data-lucide="wand-2" style="width:15px;"></i></a>
|
||||
<button class="btn btn-secondary" style="padding:0.4rem;" title="북마크" onclick="toggleBookmark(${v.id}, ${!!v.bookmarked})"><i data-lucide="star" style="width:15px; color:${star};"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user