diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index cfc78a79..aa054865 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -53,6 +53,7 @@ interface EditorHandle { interface SheetEditorHandle extends EditorHandle { getDoc: () => { 열: StructureColumn[] }; recalc: () => void; + selectColumn: (colId: string) => void; } type Mods = Record Promise>; @@ -104,6 +105,8 @@ export function createMain( const title = el("h2", { className: "m02-main__title", text: L("M02_PickTemplate") }); const reload = createButton({ label: L("M02_Reload"), variant: "ghost" }); const save = createButton({ label: L("M02_Save"), variant: "filled" }); + // 구조물 도면 · 면적산출 · 재료산출 상세 화면 → 구조물집계표(그 열 고른 채) + const back = createButton({ label: L("M02_Back"), variant: "ghost", size: "sm" }); const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴 const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } }); const comboSlot = el("div", { className: "m02-main__combo" }); // 면적산출 — 알약 막대 위 조합 줄 @@ -112,7 +115,7 @@ export function createMain( const root = el("div", { className: "m02-main", children: [ - el("div", { className: "m02-main__head", children: [title, slot, reload, save] }), + el("div", { className: "m02-main__head", children: [title, back, slot, reload, save] }), notice, comboSlot, host, @@ -163,6 +166,7 @@ export function createMain( const bar = (): void => { reload.hidden = !sel; save.hidden = !sel || !canEdit(sel.layer, isAdmin); + back.hidden = !sel || sel.layer !== "system" || !(sel.kind in NUMBER_FIELD); }; const drop = (): void => { @@ -457,6 +461,12 @@ export function createMain( } save.addEventListener("click", () => void doSave()); + back.addEventListener("click", async () => { + const column = sel?.name; + if (!column || !(await confirmLeave())) return; + await open({ layer: "system", projectId: null, kind: "table", name: STRUCTURE_TABLE }); + if (sel?.kind === "table") sheet?.selectColumn(column); // 그 사이 다른 것을 열었으면 안 고름 + }); reload.addEventListener("click", async () => { if (sel && (await confirmLeave())) void open(sel); }); diff --git a/resources/tester/test_m02_structure_panel.py b/resources/tester/test_m02_structure_panel.py index 80733940..a1ecd7b0 100644 --- a/resources/tester/test_m02_structure_panel.py +++ b/resources/tester/test_m02_structure_panel.py @@ -168,3 +168,20 @@ def test_리본_접힘_모드에서_탭으로_편_것은_밖을_누르면_다시 assert "if (collapsed) setPeek(true);" in ribbon # 탭 = 잠깐 펼침(접힘 모드 유지) assert "addEventListener('pointerdown', close, true)" in ribbon # 리본 밖 = 다시 접힘 assert "setPeek(false);" in ribbon and "setRibbonCollapsed(!collapsed);" in ribbon # 단추 + + +def test_표_도구_막대에_줄_더하기_줄_지우기_손_입력_열_없음(): + sheet = _text(ROOT / "ui_template/sheet/ui_template_sheet.ts") + text = _text(ROOT / "ui_template/sheet/ui_template_sheet_text.ts") + for key in ("Row_Add", "Row_Delete", "Col_Hand"): + assert key not in sheet and key not in text, key + assert "selectColumn: (colId) =>" in sheet + + +def test_상세_화면_돌아가기는_집계표로_그_열_고른_채(): + main = _text(MAIN) + assert "children: [title, back, slot, reload, save]" in main # 제목 바로 오른쪽 + assert "!(sel.kind in NUMBER_FIELD)" in main # 구조물 도면 · 면적산출 · 재료산출만 + body = main.split('back.addEventListener("click"')[1] + assert "confirmLeave()" in body and "STRUCTURE_TABLE" in body + assert "sheet?.selectColumn(column)" in body diff --git a/ui_template/sheet/ui_template_sheet.ts b/ui_template/sheet/ui_template_sheet.ts index 5728c0e5..e26ceedd 100644 --- a/ui_template/sheet/ui_template_sheet.ts +++ b/ui_template/sheet/ui_template_sheet.ts @@ -1,10 +1,10 @@ /* ============================================================================= * ui_template_sheet.ts * 엑셀처럼 도는 표 부품 — `createSheet(칸, 문서, {mode, onChange, drawingLabel, areaLabel, materialLabel, onSelect, onPick})` - * → `{getDoc, setDoc, recalc, destroy}`. + * → `{getDoc, setDoc, recalc, selectColumn, destroy}`. * - * master = 시스템 관리자 양식 고치기 — 머리 · 층 이름 · 단위 · 일위대가 · 줄 더하기·지우기 · - * 층마다 열 더하기(「분류1 더하기」 … 층 이름 + 더하기) · 열 지우기 · 손 열. + * master = 시스템 관리자 양식 고치기 — 머리 · 층 이름 · 단위 · 일위대가 · + * 층마다 열 더하기(「분류1 더하기」 … 층 이름 + 더하기) · 열 지우기. * 도면 · 면적산출 · 재료산출 줄 = `drawingLabel` · `areaLabel` · `materialLabel` 글 · * 칸을 고르면 `onSelect` · `onPick` 을 주면 일위대가 · 도면 · 면적산출 · 재료산출 칸은 * 치지 않고 조회 후 선택. @@ -18,13 +18,8 @@ import { createButton, el } from "@ui/ui_template_elements"; import { startEdit } from "../../M01_MasterData/M01_MasterData_UI_Cells"; import { addLevel, - addRow, - canDeleteRow, deleteColumn, - deleteRow, isBlankPick, - isBoundColumn, - isCalcColumn, KEY_PRICE, KEY_UNIT, navRows, @@ -67,6 +62,8 @@ export interface SheetHandle { getDoc(): SheetDoc; setDoc(doc: SheetDoc): void; recalc(): SheetResult; + /** 그 열 단위 칸을 고름(보이게 굴림 · `onSelect` 부름) — 없는 열이면 그대로 */ + selectColumn(colId: string): void; destroy(): void; } @@ -93,26 +90,10 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio host.replaceChildren(root); const colOf = (id: string): SheetColumn | undefined => state.doc.열.find((c) => c.id === id); - const rowIdOf = (key: string | undefined): string | null => - key?.startsWith("d:") ? key.slice(2) : null; // ── 단추 ────────────────────────────────────────────────────────── const button = (label: string, run: () => void): HTMLButtonElement => createButton({ label, variant: "ghost", onClick: run }); - const rowAdd = button(st("Row_Add"), () => { - const id = addRow(state.doc, state.mode, rowIdOf(state.sel?.r)); - state.sel = { r: `d:${id}`, c: state.sel?.c ?? state.doc.열[0]?.id ?? "" }; - changed(); - }); - const rowDelete = button(st("Row_Delete"), () => { - const id = rowIdOf(state.sel?.r); - if (!id) return; - const keys = navRows(state.doc, state.mode); - deleteRow(state.doc, id); - const next = keys[keys.indexOf(`d:${id}`) + 1] ?? keys[keys.indexOf(`d:${id}`) - 1]; - state.sel = next && state.sel ? { r: next, c: state.sel.c } : null; - changed(); - }); /** `level` 층 새 칸 열 — 고른 열 무리 끝에(층 이름이 있으면 「새 분류2」 · 없으면 「새 열」) */ const addAt = (level: number): void => { const at = state.sel?.c; @@ -133,15 +114,7 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio state.sel = state.sel && next ? { r: state.sel.r, c: next.id } : null; changed(); }); - const colHand = button(st("Col_Hand"), () => { - const col = state.sel && colOf(state.sel.c); - if (!col) return; - if (col.손) delete col.손; - else col.손 = true; - changed(); - }); - toolbar.append(rowAdd, rowDelete); - if (opts.mode === "master") toolbar.append(colDelete, colHand); + if (opts.mode === "master") toolbar.append(colDelete); // 층마다 열 더하기 단추 — 층이 둘 이상이면 둘째 층부터(「분류1 더하기」 …) · 하나면 「열 더하기」 · // 층 이름 · 층 수가 바뀌면 다시 만듦 @@ -164,15 +137,9 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio }; const syncToolbar = (): void => { - const rowId = rowIdOf(state.sel?.r); - const row = state.doc.줄.find((r) => r.id === rowId); - rowDelete.disabled = !row || !canDeleteRow(state.mode, row); const col = state.sel ? colOf(state.sel.c) : undefined; if (opts.mode === "master") syncLevels(!!col); colDelete.disabled = !col || state.doc.열.length <= 1; - // 바인딩 · 계산 열은 손 입력이 안 먹음(프로젝트에서 잠김) — 켜지 못하게 · 이미 켜진 것은 끌 수 있게 - colHand.disabled = !col || (!col.손 && (isBoundColumn(col) || isCalcColumn(col))); - colHand.classList.toggle("is-on", !!col?.손); }; // ── 그리기 · 고름 ───────────────────────────────────────────────── @@ -401,6 +368,9 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio render(); return structuredClone(state.result); }, + selectColumn: (colId) => { + if (colOf(colId)) select(KEY_UNIT, colId, true); + }, destroy: () => { root.remove(); drag = null; diff --git a/ui_template/sheet/ui_template_sheet_ops.ts b/ui_template/sheet/ui_template_sheet_ops.ts index 4e28211a..ca4acd1c 100644 --- a/ui_template/sheet/ui_template_sheet_ops.ts +++ b/ui_template/sheet/ui_template_sheet_ops.ts @@ -1,6 +1,6 @@ /* ============================================================================= * ui_template_sheet_ops.ts - * 표 문서 고치기 — 줄 · 열 더하기 · 지우기 · 칸 값. 문서를 그 자리에서 바꿈(부른 쪽이 다시 그림). + * 표 문서 고치기 — 열 더하기 · 지우기 · 칸 값. 문서를 그 자리에서 바꿈(부른 쪽이 다시 그림). * 화면(DOM)을 모름 — 격자(`ui_template_sheet.ts`)가 부름. * ⚠ 타입 말고는 import 하지 않음 — 시험이 Node 로 이 파일을 바로 돌림(`test_sheet_pages.py`). * ========================================================================== */ @@ -95,27 +95,6 @@ export function cellEditable(mode: SheetMode, col: SheetColumn, row: SheetRow): return !!col.손 || (!!row.손 && col.꼴 === "글"); } -export function canDeleteRow(mode: SheetMode, row: SheetRow): boolean { - if (mode === "master") return true; - return !!row.손 && row.고정 !== ROW_FIXED_ALL; -} - -/** `after` 줄 뒤에 빈 줄(없으면 맨 끝) — 새 줄 id. project 에서 더한 줄은 `손`. */ -export function addRow(doc: SheetDoc, mode: SheetMode, after: string | null): string { - const id = freshId( - "r", - doc.줄.map((r) => r.id), - ); - const row: SheetRow = { id, 값: {}, ...(mode === "project" ? { 손: true } : {}) }; - const at = doc.줄.findIndex((r) => r.id === after); - doc.줄.splice(at < 0 ? doc.줄.length : at + 1, 0, row); - return id; -} - -export function deleteRow(doc: SheetDoc, id: string): void { - doc.줄 = doc.줄.filter((r) => r.id !== id); -} - /** `level` 층 새 칸 열 — `after` 열과 그 층 위 머리(`머리[0..level-1]`)가 같은 이웃 끝에 둠 · * `[…위 머리, label, "" …]`(아래 층은 빈칸) · 같은 무리에 같은 글이 있으면 뒤에 수를 붙임 * (같은 머리는 가로로 합쳐져 새 열이 안 보임) · 새 열 id(`after` 가 없으면 null). */ diff --git a/ui_template/sheet/ui_template_sheet_text.ts b/ui_template/sheet/ui_template_sheet_text.ts index a1e7133f..66e7d6fd 100644 --- a/ui_template/sheet/ui_template_sheet_text.ts +++ b/ui_template/sheet/ui_template_sheet_text.ts @@ -6,11 +6,8 @@ import { currentLanguageIndex } from "@ui/ui_template_locale"; const TEXT = { - Row_Add: ["줄 더하기", "Add row"], - Row_Delete: ["줄 지우기", "Delete row"], Col_Add: ["열 더하기", "Add column"], Col_Delete: ["열 지우기", "Delete column"], - Col_Hand: ["손 입력 열", "Manual column"], Col_New: ["새 열", "New column"], Row_Unit: ["단위", "Unit"], Row_Unit_Price: ["일위대가", "Unit price"], diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index bb81c0ae..90b469ff 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -55,4 +55,5 @@ export const ui_locales_m2 = { ], M02_ActionFailed: ["안 됨 — {value}", "Failed — {value}"], M02_StructureNone: ["없음", "None"], + M02_Back: ["돌아가기", "Back"], } as const;