feat(b09): 산근 탭 보기 둘(표 · 호표별 카드) · 규격 칸 기계 · 직종 「이름 규격」(157-2 · 157-3)

- 157-2 탭 머리 단추 = 「표로 보기」 ↔ 「호표별 보기」 · 고른 보기 localStorage 기억(못 읽으면 표)
- 호표별 카드 = 표 줄 그대로 호표마다 묶음 · 머리 산근 n호표 · 공종 · 단위 · 합계 · 가름줄 없이 간격 · 긴 글 줄바꿈 · 줄 누름 이동 같음
- 157-3 산근 규격 글 = 기계 · 직종 원문번호 → 「이름 규격」(M02 85-1 도우미) · 원문번호는 풍선(두 보기 · 목록표)
- 시험 test_157_2_b09_sangeun_views — 두 보기 같은 값 · 단추 바꿈 · 기억 실패 때 표 · 이름 규격 · 풍선

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
2026-10-09 21:21:37 +09:00
co-authored by Claude Opus 5.5
parent c08d321af4
commit d02c1d3f08
7 changed files with 530 additions and 26 deletions
+41 -18
View File
@@ -19,7 +19,7 @@ import {
showToast,
} from "@ui/ui_template_elements";
import { attachCollapsible } from "@ui/ui_template_collapsible";
import { createDataTable } from "@ui/ui_template_data_table";
import { createDataTable, type DataTable } from "@ui/ui_template_data_table";
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
import { workflowSteps } from "../A00_Common/b_page_scaffold";
@@ -56,6 +56,7 @@ import {
} from "./B09_Estimation_UI_Tabs_Summary";
import { buildCost, buildTotal } from "./B09_Estimation_UI_Tabs_Total";
import { buildSg, buildSgList } from "./B09_Estimation_UI_Tabs_Sangeun";
import { renderSgPanel } from "./B09_Estimation_UI_Tabs_Sangeun_Cards";
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
@@ -190,23 +191,45 @@ export function renderB09Estimation(root: HTMLElement): void {
const goto = (link: Link | null): void => {
if (link) show(link.tab, link.key);
};
const table = createDataTable({
columns: spec.columns,
rows: spec.rows,
total: spec.total ?? null,
frozen: spec.frozen ?? 0,
empty: "",
caption: TABS.find(([id]) => id === tab)?.[1],
editable: spec.editable,
onEdit: spec.onEdit,
rowClass: spec.rowClass,
onCellClick: (i, key) => {
if (spec.onCell) spec.onCell(i, key);
if (spec.link && !spec.editable?.(i, key)) goto(spec.link(i, key));
},
});
table.root.dataset.b09Table = tab;
panel.replaceChildren(...(spec.hint ? [note(spec.hint)] : []), table.root);
const makeTable = (): DataTable => {
const made = createDataTable({
columns: spec.columns,
rows: spec.rows,
total: spec.total ?? null,
frozen: spec.frozen ?? 0,
empty: "",
caption: TABS.find(([id]) => id === tab)?.[1],
editable: spec.editable,
onEdit: spec.onEdit,
rowClass: spec.rowClass,
onCellClick: (i, key) => {
if (spec.onCell) spec.onCell(i, key);
if (spec.link && !spec.editable?.(i, key)) goto(spec.link(i, key));
},
});
made.root.dataset.b09Table = tab;
// 157-3 칸 풍선 — 공용 표는 그대로 · 올린 칸에 title
if (spec.tip)
made.root.addEventListener("mouseover", (event) => {
const cell = (event.target as Element).closest<HTMLTableCellElement>("tr[data-i] td");
const row = cell?.parentElement;
const column = cell ? spec.columns[cell.cellIndex] : undefined;
if (!cell || !row || !column) return;
const tip = spec.tip?.(Number(row.dataset.i), column.key);
if (tip) cell.title = tip;
});
return made;
};
const hint = spec.hint ? note(spec.hint) : null;
// 157-2 산근 = 보기 둘(표 · 호표별 카드) — 머리 단추로 바꿈
if (tab === "sg") {
const redraw = (): void => show("sg");
return void panel.replaceChildren(
...renderSgPanel({ spec, hint, focus, goto, makeTable, redraw }),
);
}
const table = makeTable();
panel.replaceChildren(...(hint ? [hint] : []), table.root);
if (focus !== undefined) {
const index = rowIndex(spec.rows, focus);
if (index >= 0) requestAnimationFrame(() => table.focusRow(index));