diff --git a/B09_Estimation/B09_Estimation_UI_Builders.ts b/B09_Estimation/B09_Estimation_UI_Builders.ts new file mode 100644 index 000000000..fb846bcde --- /dev/null +++ b/B09_Estimation/B09_Estimation_UI_Builders.ts @@ -0,0 +1,85 @@ +/* ============================================================================= + * B09_Estimation_UI_Builders.ts + * B09 탭 표 짜기(PLAN 303-2) — 탭 → 표(`TabTable`) · 표 → 공용 표 옵션(DOM · 누름 없이). + * + * 화면(Shell) · 팝업 창(303-2) · 인쇄(302-4)가 같은 표 짜기를 씀 — 보는 값 = 찍는 값 + * 초안 탭 = `DRAFT.builders`(draft 폴더가 넣음 · 없으면 빈 채로) + * ========================================================================== */ + +import type { DataTableOptions } from "@ui/ui_template_data_table"; +import { buildDaegaBlocks, buildDaegaList } from "./B09_Estimation_UI_Tabs_Labor"; +import { + DRAFT, + TABS, + type TabBuild, + type TabContext, + type TabId, + type TabTable, +} from "./B09_Estimation_UI_Tabs"; +import { + buildBasis, + buildCompare, + buildExpense, + buildLabor, + buildMachine, + buildMachineBlocks, + buildMaterial, +} from "./B09_Estimation_UI_Tabs_Base"; +import { buildBill } from "./B09_Estimation_UI_Tabs_Bill"; +import type { BillTable } from "./B09_Estimation_UI_Tabs_Bill_Edit"; +import { + buildSumExpense, + buildSumLabor, + buildSumMachine, + buildSumMaterial, +} from "./B09_Estimation_UI_Tabs_Summary"; +import { buildCost, buildTotal } from "./B09_Estimation_UI_Tabs_Total"; +import { buildSg, buildSgList } from "./B09_Estimation_UI_Tabs_Sangeun"; + +const BUILDERS: Partial> = { + dg_list: buildDaegaList, + dg: buildDaegaBlocks, + bill: buildBill, + basis: buildBasis, + machine: buildMachine, + machine_blocks: buildMachineBlocks, + material: buildMaterial, + labor: buildLabor, + expense: buildExpense, + compare: buildCompare, + cost: buildCost, + total: buildTotal, + sum_material: buildSumMaterial, + sum_labor: buildSumLabor, + sum_expense: buildSumExpense, + sum_machine: buildSumMachine, + sg_list: buildSgList, + sg: buildSg, +}; + +/** 탭 짜기 — 없는 탭 = null(「다음 단계에서 채움」) · 글 = 안내만 */ +export function tabTableOf(tab: TabId, ctx: TabContext): TabTable | string | null { + const build = BUILDERS[tab] ?? DRAFT.builders[tab]; + return build ? build(ctx) : null; +} + +/** 표 → 공용 표 옵션(화면과 같은 값 · 꼴) — 누름 · 메뉴 · 풍선은 그리는 쪽 몫 */ +export function tableOptionsOf(spec: TabTable, tab?: TabId): DataTableOptions { + return { + 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, + // 243-1 고칠 수 있는 칸 = 모든 표 더블클릭 · Enter · F2 (설계내역서 키 = 레벨 키 몫) + editOnDouble: spec.editOnDouble ?? true, + keyEdit: !(spec as BillTable).menu, + onEdit: spec.onEdit, + rowClass: spec.rowClass, + cellClass: spec.cellClass, + level: spec.level, + mergeSame: spec.mergeSame, + }; +} diff --git a/B09_Estimation/B09_Estimation_UI_Shell.ts b/B09_Estimation/B09_Estimation_UI_Shell.ts index 1b331e139..c84584803 100644 --- a/B09_Estimation/B09_Estimation_UI_Shell.ts +++ b/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -32,36 +32,17 @@ import { computeLabor, daegaPrice } from "./B09_Estimation_Calc_Labor"; import { computeTotal } from "./B09_Estimation_Calc_Total"; import { createTabBars } from "./B09_Estimation_UI_TabBar"; import { createCheckPanel } from "./B09_Estimation_UI_Check"; -import { buildDaegaBlocks, buildDaegaList } from "./B09_Estimation_UI_Tabs_Labor"; +import { tableOptionsOf, tabTableOf } from "./B09_Estimation_UI_Builders"; import { DRAFT, rowIndex, TABS, type Link, - type TabBuild, type TabContext, type TabId, } from "./B09_Estimation_UI_Tabs"; -import { - buildBasis, - buildCompare, - buildExpense, - buildLabor, - buildMachine, - buildMachineBlocks, - buildMaterial, -} from "./B09_Estimation_UI_Tabs_Base"; -import { buildBill } from "./B09_Estimation_UI_Tabs_Bill"; import { attachRowMenu, openMenuAt, type BillTable } from "./B09_Estimation_UI_Tabs_Bill_Edit"; import { jumpRange, jumpTo, levelBar, pickedClass } from "./B09_Estimation_UI_Tabs_Bill_Level"; -import { - buildSumExpense, - buildSumLabor, - buildSumMachine, - buildSumMaterial, -} 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 { isGroupTab, lineHint, @@ -79,27 +60,6 @@ function L(key: keyof typeof ui_locales): string { const LATER: Record = {}; -const BUILDERS: Partial> = { - dg_list: buildDaegaList, - dg: buildDaegaBlocks, - bill: buildBill, - basis: buildBasis, - machine: buildMachine, - machine_blocks: buildMachineBlocks, - material: buildMaterial, - labor: buildLabor, - expense: buildExpense, - compare: buildCompare, - cost: buildCost, - total: buildTotal, - sum_material: buildSumMaterial, - sum_labor: buildSumLabor, - sum_expense: buildSumExpense, - sum_machine: buildSumMachine, - sg_list: buildSgList, - sg: buildSg, -}; - async function request(path: string, init: RequestInit = {}): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { credentials: "include", @@ -198,10 +158,9 @@ export function renderB09Estimation(root: HTMLElement): void { bars.setTools([]); // 보기 단추 · 열 수는 묶음 탭만 다시 채움(202-1) if (!projectId) return void panel.replaceChildren(note("프로젝트를 먼저 고르세요.")); if (!payload) return void panel.replaceChildren(note("원가 자료를 불러오는 중…")); - const build = BUILDERS[tab] ?? DRAFT.builders[tab]; - if (!build) return void panel.replaceChildren(note(LATER[tab] ?? "다음 단계에서 채움")); const ctx: TabContext = { sheets, payload, user, setUser }; - const spec = build(ctx); + const spec = tabTableOf(tab, ctx); + if (!spec) return void panel.replaceChildren(note(LATER[tab] ?? "다음 단계에서 채움")); if (typeof spec === "string") return void panel.replaceChildren(note(spec)); const goto = (link: Link | null): void => { if (link) show(link.tab, link.key); @@ -209,23 +168,10 @@ export function renderB09Estimation(root: HTMLElement): void { // extra = 줄 class 곁값(181-2 표 보기 호표 구간) const makeTable = (extra?: (index: number) => string | null): 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, - // 243-1 고칠 수 있는 칸 = 모든 표 더블클릭 · Enter · F2 (설계내역서 키 = 레벨 키 몫) - editOnDouble: spec.editOnDouble ?? true, - keyEdit: !(spec as BillTable).menu, - onEdit: spec.onEdit, + ...tableOptionsOf(spec, tab), rowClass: extra ? (i) => [spec.rowClass?.(i), extra(i)].filter(Boolean).join(" ") || null : spec.rowClass, - cellClass: spec.cellClass, - level: spec.level, - mergeSame: spec.mergeSame, onCellClick: (i, key) => { if (spec.onCell) spec.onCell(i, key); if (spec.link && !spec.editable?.(i, key)) goto(spec.link(i, key)); diff --git a/resources/tester/test_145_1_b09_sangeun.py b/resources/tester/test_145_1_b09_sangeun.py index 3c1af96ab..087203723 100644 --- a/resources/tester/test_145_1_b09_sangeun.py +++ b/resources/tester/test_145_1_b09_sangeun.py @@ -178,7 +178,8 @@ def test_작업량_Q는_소수_둘(): def test_화면_탭_둘_서버_산근_그대로(): shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8") - assert "sg_list: buildSgList," in shell and "sg: buildSg," in shell + builders = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Builders.ts").read_text("utf-8") + assert "sg_list: buildSgList," in builders and "sg: buildSg," in builders # 303-2 표 짜기 assert 'const SERVER_ONLY = ["sg", "sg_list", "sg_gaps", "sg_notes"] as const;' in shell assert "gaps: [...base.gaps, ...(server.sg_gaps || [])" in shell # 뒤에 대가(145-2) gaps 이음 tabs = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs_Sangeun.ts").read_text("utf-8") diff --git a/resources/tester/test_183_3_b09_applied_pick.py b/resources/tester/test_183_3_b09_applied_pick.py index 3e4f90e27..533f89669 100644 --- a/resources/tester/test_183_3_b09_applied_pick.py +++ b/resources/tester/test_183_3_b09_applied_pick.py @@ -101,12 +101,14 @@ def test_배선_수기_적용_칸_출처_돌림_없앰(): assert 'editable: (_i, key) => key === "p:견적",' in compare assert "nextPick" not in base and 'key === "applied_from"' not in compare assert 'key === `p:${rows[i]?.applied_from}` ? "is-picked"' in compare - assert 'key === "applied" && rows[i] ? chooseApplied(rows[i]) : null' in compare # 257-3 메뉴 둘 공용 + assert ( + 'key === "applied" && rows[i] ? chooseApplied(rows[i]) : null' in compare + ) # 257-3 메뉴 둘 공용 assert "delete cell.applied" in compare shell = _src("B09_Estimation/B09_Estimation_UI_Shell.ts") - assert ( - "cellClass: spec.cellClass," in shell and "openMenuAt(box.left, box.bottom, items)" in shell - ) + builders = _src("B09_Estimation/B09_Estimation_UI_Builders.ts") # 303-2 옵션 짜기 + assert "cellClass: spec.cellClass," in builders and "...tableOptionsOf(spec, tab)," in shell + assert "openMenuAt(box.left, box.bottom, items)" in shell edit = _src("B09_Estimation/B09_Estimation_UI_Tabs_Bill_Edit.ts") assert "export function openMenuAt(" in edit and "const ctxMenu = sharedMenu();" in edit css = _src("B09_Estimation/B09_Estimation_UI_Page.css") diff --git a/resources/tester/test_201_1_table_level_merge.py b/resources/tester/test_201_1_table_level_merge.py index c2e564442..efe891570 100644 --- a/resources/tester/test_201_1_table_level_merge.py +++ b/resources/tester/test_201_1_table_level_merge.py @@ -120,7 +120,7 @@ APPLIED = [ ("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["group", "part"]'), ("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["part"]'), ("B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts", 'group: (i) => rows[i]?.kind !== "item"'), - ("B09_Estimation/B09_Estimation_UI_Shell.ts", "mergeSame: spec.mergeSame"), + ("B09_Estimation/B09_Estimation_UI_Builders.ts", "mergeSame: spec.mergeSame"), ] diff --git a/resources/tester/test_235_1_b09_bill_tree.py b/resources/tester/test_235_1_b09_bill_tree.py index 46b549af0..72f567665 100644 --- a/resources/tester/test_235_1_b09_bill_tree.py +++ b/resources/tester/test_235_1_b09_bill_tree.py @@ -345,7 +345,8 @@ def test_고른_줄_상자_테두리_테마_토큰(): def test_Shell_배선(): shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8") - assert "editOnDouble: spec.editOnDouble ?? true," in shell # 243-1 기본 = 더블클릭 + builders = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Builders.ts").read_text("utf-8") + assert "editOnDouble: spec.editOnDouble ?? true," in builders # 243-1 기본 = 더블클릭 assert "levelBar(table.root, bill, (i) => table.edit(i))" in shell for name in [ "ui_template/ui_template_data_table.ts", diff --git a/resources/tester/test_243_1_double_click_tables.py b/resources/tester/test_243_1_double_click_tables.py index a261089b1..e8e03aadd 100644 --- a/resources/tester/test_243_1_double_click_tables.py +++ b/resources/tester/test_243_1_double_click_tables.py @@ -114,8 +114,10 @@ def test_B09_표_만드는_길_한_곳_더블클릭_기본(): ] assert calls == ["B09_Estimation_UI_Shell.ts"] shell = (ROOT / "B09_Estimation" / calls[0]).read_text("utf-8") - assert "editOnDouble: spec.editOnDouble ?? true," in shell - assert "keyEdit: !(spec as BillTable).menu," in shell # 설계내역서 Enter · F2 = 레벨 키 몫 + assert "...tableOptionsOf(spec, tab)," in shell # 303-2 옵션 짜기 = Builders 한 곳 + builders = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Builders.ts").read_text("utf-8") + assert "editOnDouble: spec.editOnDouble ?? true," in builders + assert "keyEdit: !(spec as BillTable).menu," in builders # 설계내역서 Enter · F2 = 레벨 키 몫 def test_B08_고칠_칸_있는_표_모두_editOnDouble():