diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 450fc31a7..d70207bdb 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -329,6 +329,7 @@ export function createMain( onChange: () => (dirty = !readOnly), ...(struct ? { + laborLabel: (colId: string) => struct.numbers()[colId]?.일위대가번 ?? "", drawingLabel: (colId: string) => struct.numbers()[colId]?.도번 ?? "", areaLabel: (colId: string) => struct.numbers()[colId]?.면적번 ?? "", combo: { diff --git a/resources/tester/test_sheet_drawing_select.py b/resources/tester/test_sheet_drawing_select.py index 2909841c4..133f770b1 100644 --- a/resources/tester/test_sheet_drawing_select.py +++ b/resources/tester/test_sheet_drawing_select.py @@ -8,6 +8,7 @@ master 만 · 단위 줄 바로 아래 차례(키보드) · project 에는 없 from __future__ import annotations import json +import re import shutil import subprocess from pathlib import Path @@ -70,3 +71,18 @@ def test_text_column_pick_rows_blank(): assert _run()["blank"] == [ [True, False], [True, False], [True, False], [False, False], [False, False], ] # fmt: skip + + +def test_labor_row_shows_su_number(): + """마스터 일위대가 줄 = 그 열 일위대가번(SU) — 열의 옛 `일위대가` 칸(UA 키)은 안 읽음.""" + ops = OPS.read_text(encoding="utf-8") + assert "REF_ROWS: readonly string[] = [KEY_PRICE, KEY_DRAWING, KEY_AREA]" in ops + render = (OPS.parent / "ui_template_sheet_render.ts").read_text(encoding="utf-8") + assert re.search(r"key === KEY_PRICE\s*\?\s*state\.laborLabel", render) + assert "col.일위대가" not in render + sheet = (OPS.parent / "ui_template_sheet.ts").read_text(encoding="utf-8") + assert "col.일위대가" not in sheet # 읽기 · 쓰기 · 조회 후 선택 모두 안 건드림 + main = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_UI_Main.ts").read_text( + encoding="utf-8" + ) + assert 'laborLabel: (colId: string) => struct.numbers()[colId]?.일위대가번 ?? ""' in main diff --git a/ui_template/sheet/ui_template_sheet.ts b/ui_template/sheet/ui_template_sheet.ts index 35372ff8a..b8f38ebe6 100644 --- a/ui_template/sheet/ui_template_sheet.ts +++ b/ui_template/sheet/ui_template_sheet.ts @@ -25,7 +25,6 @@ import { addLevel, deleteColumn, isBlankPick, - KEY_PRICE, KEY_UNIT, navRows, PICK_ROWS, @@ -54,6 +53,8 @@ export interface SheetOptions { /** master — 열 더하기 · 지우기 · 머리 · 층 이름 고치기 없음(열 · 머리는 나무가 정함) */ lockHead?: boolean; onChange?: (doc: SheetDoc) => void; + /** 일위대가 줄 칸 글(일위대가번 SU · 열 id) — `drawingLabel` 과 같음(열의 옛 `일위대가` 칸은 안 읽음). */ + laborLabel?: (colId: string) => string; /** 도면 줄 칸 글(마스터 첫 쪽 · 열 id) — 없거나 빈 글이면 「없음」 · 글이 바뀌면 `recalc()` 로 다시 그림. */ drawingLabel?: (colId: string) => string; /** 면적산출 줄 칸 글(번호) — `drawingLabel` 과 같음. */ @@ -61,8 +62,7 @@ export interface SheetOptions { /** 칸을 고름 — 누르기 · 방향키 · Tab. */ onSelect?: (sel: SheetSelection) => void; /** 조회 후 선택 — master 일위대가 · 도면 · 면적산출 칸을 두 번 누르기 · Enter · F2 로 부름. - * 글을 돌려주면 일위대가 칸에 넣음 · 번호 글 줄은 부른 쪽이 가져온 뒤 `recalc()`. - * 없으면 일위대가는 글로 고침 · 번호 글 줄은 읽기만. */ + * 번호 글 줄은 부른 쪽이 가져온 뒤 `recalc()` · 없으면 읽기만. */ onPick?: (sel: SheetSelection) => Promise | void; /** 구조물 조합 표(PLAN 31-1) — 단위 줄 · 안내 글 없음 · 열 폭 자동 · 고른 열은 가장 깊은 이름 칸을 밝힘 · * `onHeadEdit` 를 주면 이름 칸을 두 번 누르기 · F2 · Enter 로 고쳐 부른 쪽에 넘김(표 문서는 안 고침 — @@ -87,6 +87,7 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio mode: opts.mode, result: { 계산: {}, 합계: {}, 오류: [] }, sel: null, + laborLabel: opts.laborLabel, drawingLabel: opts.drawingLabel, areaLabel: opts.areaLabel, pick: !!opts.onPick, @@ -236,7 +237,6 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio // ── 칸 고치기 ───────────────────────────────────────────────────── const rawText = (key: string, col: SheetColumn): string => { if (key === KEY_UNIT) return col.단위 ?? ""; - if (key === KEY_PRICE) return col.일위대가 ?? ""; if (REF_ROWS.includes(key)) return refText(state, key, col.id); const row = state.doc.줄.find((r) => `d:${r.id}` === key); const formula = row?.식?.[col.id] ?? col.식; @@ -248,15 +248,12 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio if (ro || REF_ROWS.includes(key)) return; // 읽기만 const value = text.trim(); if (key === KEY_UNIT) col.단위 = value || null; - else if (key === KEY_PRICE) col.일위대가 = value || null; else setCell(state.doc, key.slice(2), col, text); changed(); }; - /** 조회 후 선택 — 부른 쪽이 고른 글을 돌려주면 일위대가 칸에 넣음(그 사이 문서가 바뀌었으면 새 열에) */ + /** 조회 후 선택 — 부른 쪽이 가져옴(번호 글 줄 · 문서는 안 고침) */ const pick = async (key: string, colId: string): Promise => { - const got = await opts.onPick?.(selectionOf(state.doc, key, colId)); - const col = colOf(colId); - if (key === KEY_PRICE && typeof got === "string" && col) write(key, col, got); + await opts.onPick?.(selectionOf(state.doc, key, colId)); root.focus({ preventScroll: true }); }; /** 칸 고치기 — 두 번 누르기 = 글 전부 고름 · 바로 치기(initial) = 그 글자로 바꿈 · Enter · F2(atEnd) = 커서를 글 끝에 */ diff --git a/ui_template/sheet/ui_template_sheet_ops.ts b/ui_template/sheet/ui_template_sheet_ops.ts index a3d807da6..0f0a51ea2 100644 --- a/ui_template/sheet/ui_template_sheet_ops.ts +++ b/ui_template/sheet/ui_template_sheet_ops.ts @@ -22,11 +22,11 @@ export const KEY_UNIT = "s:unit"; export const KEY_PRICE = "s:price"; export const KEY_DRAWING = "s:drawing"; export const KEY_AREA = "s:area"; -/** 마스터 전용 줄(첫 쪽 단위 줄 아래 차례) — 도면 · 면적산출 줄은 손으로 못 침(번호 글). */ +/** 마스터 전용 줄(첫 쪽 단위 줄 아래 차례) — 일위대가 · 도면 · 면적산출 줄은 손으로 못 침(번호 글). */ export const MASTER_ROWS = [KEY_PRICE, KEY_DRAWING, KEY_AREA] as const; export type MasterRowKey = (typeof MASTER_ROWS)[number]; -/** 번호 글 줄 — 부른 쪽이 준 글(`refLabel`)만 보임. */ -export const REF_ROWS: readonly string[] = [KEY_DRAWING, KEY_AREA]; +/** 번호 글 줄 — 부른 쪽이 준 글(`refLabel`)만 보임(일위대가 = SU · 열의 옛 `일위대가` 칸은 안 읽음). */ +export const REF_ROWS: readonly string[] = [KEY_PRICE, KEY_DRAWING, KEY_AREA]; /** 조회 후 선택 줄 — `onPick` 을 주면 이 줄 칸은 치지 않고 고름. */ export const PICK_ROWS: readonly string[] = MASTER_ROWS; diff --git a/ui_template/sheet/ui_template_sheet_render.ts b/ui_template/sheet/ui_template_sheet_render.ts index 3b5113938..e9992bbf7 100644 --- a/ui_template/sheet/ui_template_sheet_render.ts +++ b/ui_template/sheet/ui_template_sheet_render.ts @@ -17,6 +17,7 @@ import { isBlankPick, isCalcColumn, KEY_AREA, + KEY_PRICE, KEY_UNIT, MASTER_ROWS, type MasterRowKey, @@ -38,7 +39,8 @@ export interface RenderState { mode: SheetMode; result: SheetResult; sel: { r: string; c: string } | null; - /** 도면 · 면적산출 줄 칸 글(열 id) — 없거나 빈 글이면 「없음」. */ + /** 일위대가 · 도면 · 면적산출 줄 칸 글(열 id) — 없거나 빈 글이면 「없음」. */ + laborLabel?: (colId: string) => string; drawingLabel?: (colId: string) => string; areaLabel?: (colId: string) => string; /** 조회 후 선택 줄(`PICK_ROWS`)을 고름으로 받나 — 칸 겉모양만. */ @@ -51,9 +53,13 @@ export interface RenderState { headEditable?: boolean; } -/** 도면 · 면적산출 줄 칸 글. */ +/** 일위대가 · 도면 · 면적산출 줄 칸 글(번호). */ export const refText = (state: RenderState, key: string, colId: string): string => - (key === KEY_AREA ? state.areaLabel : state.drawingLabel)?.(colId)?.trim() || st("Drawing_None"); + (key === KEY_AREA + ? state.areaLabel + : key === KEY_PRICE + ? state.laborLabel + : state.drawingLabel)?.(colId)?.trim() || st("Drawing_None"); /** 열 폭 — 저장 폭(없으면 기본) · 머리 글이 안 잘리는 최소 폭보다 좁지 않게. */ const columnWidth = (doc: SheetDoc, col: SheetColumn, mins: Map): number => @@ -68,7 +74,7 @@ function fitMeta(state: RenderState, cols: SheetColumn[], mins: Map refText(state, key, col.id))]; + const texts = REF_ROWS.map((key) => refText(state, key, col.id)); const need = Math.ceil(Math.max(...texts.map((t) => textWidth(t, font)))) + 14; mins.set(col.id, Math.max(mins.get(col.id) ?? 0, need)); } @@ -191,11 +197,7 @@ export function renderSheet(state: RenderState): HTMLElement { const tr = el("tr", { className: "ui-sheet__meta", children: [gutter("th", st(label))] }); cols.forEach((col, j) => { const blank = isBlankPick(key, col); - const text = blank - ? "" - : REF_ROWS.includes(key) - ? refText(state, key, col.id) - : (col.일위대가 ?? ""); + const text = blank ? "" : refText(state, key, col.id); // 마스터 줄 = 모두 번호 글 const td = bodyCell(key, col, j, text); td.classList.remove("is-num"); if (state.pick && PICK_ROWS.includes(key) && !blank) td.classList.add("is-pick");