From ff3d8190197dac2a59645d47a66e470d68570c52 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 8 Aug 2026 13:42:55 +0900 Subject: [PATCH] =?UTF-8?q?feat(B01):=20=EC=9E=84=EC=8B=9C=20=EB=B3=B4?= =?UTF-8?q?=EA=B4=80=ED=95=A8=20=EA=B7=B8=EB=A3=B9=20=EC=A0=91=EA=B8=B0/?= =?UTF-8?q?=ED=8E=BC=EC=B9=98=EA=B8=B0=20+=20=EC=A0=9C=EB=AA=A9=20?= =?UTF-8?q?=ED=96=89=20=ED=95=9C=20=EC=A4=84=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 그룹은 기본 접힘, 제목 행 전체가 토글 트리거(액션 버튼 클릭은 전파 차단) - 목록을 다시 그려도 펼친 그룹은 유지, 업로드 시작 시 해당 그룹 자동 펼침 - 파일 수·용량·보관 만료를 상태 배지 오른쪽으로 옮겨 제목과 한 줄로 배치 - 파일 표 상하 여백 축소(셀·버튼 padding 2px, 글자 크기 유지) Co-Authored-By: Claude Fable 5 --- B01_Dashboard/B01_Dashboard_UI_Style_Temp.css | 39 +++++++++++++++---- B01_Dashboard/B01_Dashboard_UI_TempUpload.ts | 39 +++++++++++++++---- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/B01_Dashboard/B01_Dashboard_UI_Style_Temp.css b/B01_Dashboard/B01_Dashboard_UI_Style_Temp.css index cd5e66df..c1d03191 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Style_Temp.css +++ b/B01_Dashboard/B01_Dashboard_UI_Style_Temp.css @@ -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); diff --git a/B01_Dashboard/B01_Dashboard_UI_TempUpload.ts b/B01_Dashboard/B01_Dashboard_UI_TempUpload.ts index a5841609..0e193503 100644 --- a/B01_Dashboard/B01_Dashboard_UI_TempUpload.ts +++ b/B01_Dashboard/B01_Dashboard_UI_TempUpload.ts @@ -84,6 +84,8 @@ export function buildTempUploadSection(): HTMLElement { const tasks = new Map(); /** 진행률 막대 DOM — 청크 하나 올릴 때마다 목록 전체를 다시 그리지 않기 위해 잡아 둔다. */ const progressNodes = new Map(); + /** 펼쳐 둔 그룹 — 기본은 접힘, 사용자가 연 그룹만 목록을 다시 그려도 열린 상태를 유지한다. */ + const openGroups = new Set(); /* ── 행 구성 ─────────────────────────────────────────────────────────── */ @@ -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;