fix(M02): 구조물 조합 표 「일위대가」 줄 = 그 열 일위대가번(SU) — 옛 조합 키(UA) 칸 안 읽음 · 번호 글 줄(도면 · 면적산출과 같음)

- 표 부품: 일위대가 줄도 번호 글 줄(손으로 못 침 · laborLabel) · 조회 후 선택이 열 칸을 안 고침
- 누르면 다른 줄과 같은 길(일위대가 복사해 가져오기) · 열의 옛 「일위대가」 값은 파일에 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
2026-10-04 22:08:06 +09:00
co-authored by Claude Opus 5.5
parent 26a24889e6
commit 6541b65834
5 changed files with 37 additions and 21 deletions
+6 -9
View File
@@ -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<string | null | undefined> | 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<void> => {
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) = 커서를 글 끝에 */
+3 -3
View File
@@ -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;
+11 -9
View File
@@ -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<string, number>): number =>
@@ -68,7 +74,7 @@ function fitMeta(state: RenderState, cols: SheetColumn[], mins: Map<string, numb
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
const font = `400 ${rem * 0.76}px ${getComputedStyle(document.body).fontFamily}`;
for (const col of cols) {
const texts = [col.일위대가 ?? "", ...REF_ROWS.map((key) => 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");