feat(B01): 임시 보관함 그룹 접기/펼치기 + 제목 행 한 줄 정리

- 그룹은 기본 접힘, 제목 행 전체가 토글 트리거(액션 버튼 클릭은 전파 차단)
- 목록을 다시 그려도 펼친 그룹은 유지, 업로드 시작 시 해당 그룹 자동 펼침
- 파일 수·용량·보관 만료를 상태 배지 오른쪽으로 옮겨 제목과 한 줄로 배치
- 파일 표 상하 여백 축소(셀·버튼 padding 2px, 글자 크기 유지)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 13:42:55 +09:00
co-authored by Claude Fable 5
parent 8d51f6d334
commit ff3d819019
2 changed files with 63 additions and 15 deletions
+32 -7
View File
@@ -34,24 +34,26 @@
opacity: 0.6;
}
/* 제목 행 전체가 접기/펼치기 트리거다. */
.b01-temp__group-head {
display: flex;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
flex-wrap: wrap;
}
.b01-temp__group-info {
display: flex;
flex-direction: column;
gap: 4px;
cursor: pointer;
}
.b01-temp__group-title {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
}
.b01-temp__caret {
width: 12px;
color: var(--color-muted-text, var(--color-primary-text));
}
.b01-temp__group-meta {
@@ -59,6 +61,29 @@
color: var(--color-muted-text, var(--color-primary-text));
}
/* 기본은 접힘 — 펼친 그룹만 파일 표를 보여 준다. */
.b01-temp__group-body {
display: none;
margin-top: var(--spacing-8);
}
.b01-temp__group.is-open .b01-temp__group-body {
display: block;
}
/* 파일 표는 행이 많아 촘촘하게 — 글자 크기는 그대로 두고 상하 여백만 줄인다. */
.b01-temp__group-body .ui-general-block__table th,
.b01-temp__group-body .ui-general-block__table td {
padding-top: 2px;
padding-bottom: 2px;
}
.b01-temp__group-body .ui-general-block__table .ui-btn {
padding-top: 2px;
padding-bottom: 2px;
min-height: 0;
}
.b01-temp__badge {
padding: 2px var(--spacing-8);
border: 1px solid var(--color-border);
+31 -8
View File
@@ -84,6 +84,8 @@ export function buildTempUploadSection(): HTMLElement {
const tasks = new Map<string, UploadTask[]>();
/** 진행률 막대 DOM — 청크 하나 올릴 때마다 목록 전체를 다시 그리지 않기 위해 잡아 둔다. */
const progressNodes = new Map<string, { fill: HTMLElement; caption: HTMLElement }>();
/** 펼쳐 둔 그룹 — 기본은 접힘, 사용자가 연 그룹만 목록을 다시 그려도 열린 상태를 유지한다. */
const openGroups = new Set<string>();
/* ── 행 구성 ─────────────────────────────────────────────────────────── */
@@ -174,13 +176,18 @@ export function buildTempUploadSection(): HTMLElement {
const group = document.createElement("div");
group.className = `b01-temp__group is-${batch.status}`;
const opened = openGroups.has(batch.batch_id);
group.classList.toggle("is-open", opened);
const head = document.createElement("div");
head.className = "b01-temp__group-head";
const info = document.createElement("div");
info.className = "b01-temp__group-info";
// 제목 행 = 접기/펼치기 트리거. 이름·상태·요약이 한 줄에 들어간다.
const titleRow = document.createElement("div");
titleRow.className = "b01-temp__group-title";
const caret = document.createElement("span");
caret.className = "b01-temp__caret";
caret.textContent = opened ? "▾" : "▸";
const title = document.createElement("strong");
title.textContent = batch.name;
const badge = document.createElement("span");
@@ -191,9 +198,8 @@ export function buildTempUploadSection(): HTMLElement {
: batch.required_complete
? L("B01_Temp_Status_Ready")
: L("B01_Temp_Status_Uploading");
titleRow.append(title, badge);
const meta = document.createElement("div");
const meta = document.createElement("span");
meta.className = "b01-temp__group-meta";
const parts = [
`${batch.files.length}${L("B01_Temp_Meta_FileCount")}`,
@@ -205,10 +211,12 @@ export function buildTempUploadSection(): HTMLElement {
parts.push(`${L("B01_Temp_Meta_Expires")} ${formatDate(batch.expires_at)}`);
}
meta.textContent = parts.join(" · ");
info.append(titleRow, meta);
titleRow.append(caret, title, badge, meta);
const actions = document.createElement("div");
actions.className = "b01-dashboard__actions";
// 버튼 클릭이 접기/펼치기까지 건드리지 않게 막는다.
actions.addEventListener("click", (event) => event.stopPropagation());
if (batch.status !== "linked") {
actions.append(
createButton({
@@ -223,10 +231,21 @@ export function buildTempUploadSection(): HTMLElement {
}),
);
}
head.append(info, actions);
head.append(titleRow, actions);
head.setAttribute("aria-expanded", String(opened));
head.addEventListener("click", () => {
const nowOpen = !group.classList.contains("is-open");
group.classList.toggle("is-open", nowOpen);
head.setAttribute("aria-expanded", String(nowOpen));
caret.textContent = nowOpen ? "▾" : "▸";
// 목록을 다시 그려도 열어 둔 그룹은 계속 열려 있어야 한다(업로드 중 진행률 확인).
if (nowOpen) openGroups.add(batch.batch_id);
else openGroups.delete(batch.batch_id);
});
group.append(
head,
const body = document.createElement("div");
body.className = "b01-temp__group-body";
body.append(
table(
[
L("B01_Temp_Table_Type"),
@@ -238,6 +257,8 @@ export function buildTempUploadSection(): HTMLElement {
fileRows(batch),
),
);
group.append(head, body);
return group;
}
@@ -361,6 +382,8 @@ export function buildTempUploadSection(): HTMLElement {
// 지난번 실패 행은 새로 올리기 시작하면 치운다.
const previous = (tasks.get(batchId) ?? []).filter((item) => item.status !== "failed");
tasks.set(batchId, [...previous, ...queued]);
// 올리는 동안은 진행률이 보여야 하므로 해당 그룹을 펼친 상태로 둔다.
openGroups.add(batchId);
await refresh();
let failed = 0;