From e3236558bdbadb323ac5d0d27c536f91d5f00b7c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 4 Oct 2026 22:37:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(M02):=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=95=A9=20=EC=B9=B4=EB=93=9C=20[=EC=82=AD=EC=A0=9C]?= =?UTF-8?q?=20=C2=B7=20=EC=9D=BC=EC=9C=84=EB=8C=80=EA=B0=80=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=ED=95=9C=20=EC=A4=84=20=ED=91=9C=20=E2=80=94=20?= =?UTF-8?q?=EA=B7=B8=20=ED=95=AD=EB=AA=A9=20=EB=B9=84=EC=9A=B0=EA=B8=B0(?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=C2=B7=20=EC=97=B4=20=EA=B7=B8=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=C2=B7=20=EB=AC=BC=EC=9D=8C=20=ED=95=9C=20=EB=B2=88?= =?UTF-8?q?=20=C2=B7=20=EB=B9=84=EC=9A=B4=20=EB=92=A4=20=EB=B9=88=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C)=20=C2=B7=20=EC=A2=81=EC=9D=80=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=8B=A8=EC=B6=94=20=EC=A4=84=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 40-2 ④ ⑤ — clearPart(40-4) 잇기 · 찬 카드 · 쓸 수 있을 때만 · 실패 = 카드 안 까닭 · Labor_View compact(40-1). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt --- .../M02_MasterTemplete_ItemCards.ts | 40 +++++++++++++++++-- .../M02_MasterTemplete_Structure.css | 5 ++- .../M02_MasterTemplete_Structure.ts | 5 +++ resources/tester/test_m02_card_shell.py | 2 +- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts index 04f748bf9..f62db757e 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemCards.ts @@ -69,6 +69,10 @@ const TEXT = { pills: "알약", pillsTip: "아래 알약 줄 보임 · 숨김", take: "가져오기", + clear: "삭제", + clearTip: "이 항목의 {value} 을(를) 비움 — 번호 · 열은 그대로", + clearAsk: "「{name}」 의 {value} 을(를) 비울까? — 번호 · 열은 그대로 · 되돌릴 수 없음", + clearFailed: "비우지 못함 — {value}", takeTip: "다른 항목의 {value} 을(를) 골라 이 칸에 덮어씀 — 번호 · 열은 그대로", blank: "비어 있음 — 새로 만들거나 다른 항목에서 복사해 옴", makeNew: "새로 만들기", @@ -117,6 +121,9 @@ export interface ItemCardsOptions { sheetEdit?: SheetEditOptions; /** 「복사해 가져오기」 · 빈 카드 [복사 생성](39-5) — 다른 항목의 이 칸을 골라 덮어씀 · 없으면 단추 없음 */ onImport?: (kind: CardKind) => void; + /** 카드 [삭제](40-2) — 그 항목 비우기(번호 · 열 그대로 · 물음은 카드가 한 번) · 없으면 단추 없음 · + * 끝나면 카드를 다시 읽음(빈 카드 = [새로 만들기] [복사 생성]) */ + onClear?: (kind: CardKind) => Promise; /** 일위대가 카드(39-5 · 몸 = 일위대가 보기 갈래 `Labor_View`) — 도면 다음 · 지금 층에서 읽음 · * [일위대가 수정] · 빈 카드 [새로 만들기] = 이것(일위대가 상세) */ labor?: () => void; @@ -174,6 +181,8 @@ interface Card { edit: HTMLButtonElement; /** 「복사해 가져오기」 — 찬 카드만 */ take: HTMLButtonElement; + /** [삭제] — 찬 카드만 · 쓸 수 있을 때만 */ + del: HTMLButtonElement; no: HTMLElement; name: HTMLElement; } @@ -218,14 +227,35 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { const take = createButton({ label: TEXT.take, variant: "ghost", size: "sm" }); take.title = fill(TEXT.takeTip, PART_WORD[kind]); take.addEventListener("click", () => opts.onImport?.(kind)); - shell.tools.append(help, saver.button, take, edit); - return { kind, shell, saver, help, edit, take, no, name }; + const del = createButton({ label: TEXT.clear, variant: "ghost", size: "sm" }); + del.title = fill(TEXT.clearTip, PART_WORD[kind]); + del.addEventListener("click", () => void clearCard(kind)); + shell.tools.append(help, saver.button, take, edit, del); + return { kind, shell, saver, help, edit, take, del, no, name }; }; + /** 카드 [삭제](40-2) — 물음 한 번(저장 안 한 카드 고침도 함께 버려짐) → 비움 → 카드 다시 읽음(빈 카드가 결과) */ + async function clearCard(kind: CardKind): Promise { + if (!opts.onClear || !column) return; + const ask = fill(TEXT.clearAsk, PART_WORD[kind]).replace("{name}", label); + if (!(await showConfirmDialog(ask))) return; + try { + await opts.onClear(kind); + } catch (error) { + // 알림은 카드 틀 안(토스트 안 씀) — 까닭 + [다시 불러오기] + const card = cards.find((c) => c.kind === kind); + return void card?.shell.fail(fill(TEXT.clearFailed, why(error)), reload); + } + for (const c of cards) c.saver.mark(false); + started.delete(kind); + reload(); + } + /** 빈 카드(39-5) — 고칠 수 있고(가져오기 길이 있는 M02 화면) [새로 만들기] 를 안 눌렀으면 두 단추 · 아니면 미리보기 */ const blank = (c: Card, empty: boolean): boolean => { const show = empty && opts.editable() && !!opts.onImport && !started.has(c.kind); c.take.hidden = show || !opts.editable() || !opts.onImport; + c.del.hidden = show || !opts.editable() || !opts.onClear; // 빈 카드엔 비울 것이 없음 return show; }; /** 빈 카드 몸 — [새로 만들기] = 그 자리에서 빈 미리보기 편집 · [복사 생성] = 가져오기 창 */ @@ -307,7 +337,10 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { const paintLabor = async (at: string, mine: number): Promise => { if (!laborCard) return; try { - laborView ??= (await import("./M02_MasterTemplete_Labor_View")).createLaborView(); + // 카드 = 한 줄 표(항목 · 수량산출 · 단위 · 금액 넷 · 40-2) — 자세한 것은 [수정] 상세 + laborView ??= (await import("./M02_MasterTemplete_Labor_View")).createLaborView(undefined, { + compact: true, + }); } catch (error) { if (mine === seq) laborCard.shell.fail(fill(TEXT.noSheet, why(error)), reload); return; @@ -345,6 +378,7 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle { const can = c.kind === "structure" ? drawEdit : c.kind !== "labor" && sheetEdit; c.saver.button.hidden = c.help.hidden = !can; c.edit.hidden = !opts.editable(); + c.del.hidden = !opts.editable() || !opts.onClear; // 빈 카드는 그린 뒤 `blank` 가 다시 숨김 } pills.hidden = !sheetEdit; }; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Structure.css b/M02_MasterTemplete/M02_MasterTemplete_Structure.css index 133551038..aabaaa5e8 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Structure.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Structure.css @@ -90,7 +90,10 @@ .m02-struct__card-tools { display: inline-flex; - flex: none; + flex: 0 1 auto; /* 좁은 카드(1열 · 400px)에서 단추가 많으면 줄 바꿈 — 카드 밖으로 안 넘침(40-2) */ + flex-wrap: wrap; + justify-content: flex-end; + min-width: 0; align-items: center; gap: var(--spacing-8); margin-left: auto; diff --git a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts index e03126161..44750848b 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Structure.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Structure.ts @@ -17,6 +17,7 @@ import { showConfirmDialog, showToast } from "@ui/ui_template_elements"; import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links"; import { + clearPart, copyPartFrom, fetchLaborPills, fetchStructureNumbers, @@ -222,6 +223,10 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane warnOnFail: (id) => !!numbers[id], // 「복사해 가져오기」 · 빈 카드 [복사 생성](39-5) — 집계표 줄 「조회 후 선택」 과 같은 길 onImport: (kind) => void pickCopy(kind, shown ?? undefined), + // 카드 [삭제](40-2) — 그 항목 비우기(서버 clear 길 · 번호 · 열 그대로 · 쓰기 권한 없으면 403) + onClear: async (kind) => { + if (column) await clearPart(kind, column); + }, labor: () => void openEdit("labor"), // 일위대가 카드(보기 갈래 · 39-3) — 도면 다음 // 면적산출 카드 미리보기 수정(31-3) — 전체 편집기와 같은 알약 · 저장 길 sheetEdit: { diff --git a/resources/tester/test_m02_card_shell.py b/resources/tester/test_m02_card_shell.py index ae89a8b8e..aae597a1d 100644 --- a/resources/tester/test_m02_card_shell.py +++ b/resources/tester/test_m02_card_shell.py @@ -40,7 +40,7 @@ def test_저장_단추_알림_한_벌(): def test_네_카드가_틀을_씀_수정_단추_한_모양(): cards = _text("M02_MasterTemplete_ItemCards.ts") assert "const shell = cardShell({ title, body, sub: [no, name] });" in cards - assert "shell.tools.append(help, saver.button, take, edit);" in cards # 가져오기(39-5 · 40-2) + assert "shell.tools.append(help, saver.button, take, edit, del);" in cards # 40-2 assert 'variant: "filled"' not in cards # [수정] 은 모두 빈 단추(ghost) assert "c.saver.button.hidden = c.help.hidden = !can;" in cards # [저장] · ? 늘 보임 assert "DRAWING_KEYS" in cards and "SHEET_KEYS" in cards