From 22a54abe998c4f054bb34ddeb9dbf51f748e7624 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 9 Oct 2026 14:59:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EC=9E=90=EC=9E=AC=20=EC=85=8B=20?= =?UTF-8?q?=EA=B0=92=20=EC=97=86=EB=8A=94=20=EC=A4=84=20=EC=88=A8=EA=B9=80?= =?UTF-8?q?=20=C2=B7=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=20=ED=83=AD=20=ED=91=9C?= =?UTF-8?q?=20=EB=84=B7=20=EA=B3=B5=EC=9A=A9=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=95=88(143-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 총괄 · 콘크리트 · 자재수량 — 수량 · 금액 · 손 단위수량 없는 줄 숨김(화면만 · 고친 칸은 원래 줄 자리로 저장) - 구조물 집계표 · 자재 셋 — 공용 카드 안(토공 표와 같은 꼴) - 시험 — 새 test_143_3 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz --- B08_Quantity/B08_Quantity_UI_Material.ts | 52 ++++++++++++------- B08_Quantity/B08_Quantity_UI_Summary.ts | 4 +- .../tester/test_143_3_b08_material_cards.py | 30 +++++++++++ 3 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 resources/tester/test_143_3_b08_material_cards.py diff --git a/B08_Quantity/B08_Quantity_UI_Material.ts b/B08_Quantity/B08_Quantity_UI_Material.ts index 7d032c8a9..c0746c266 100644 --- a/B08_Quantity/B08_Quantity_UI_Material.ts +++ b/B08_Quantity/B08_Quantity_UI_Material.ts @@ -7,11 +7,13 @@ * 고칠 칸 = 단위수량 · ㎥당 배합 · 할증 % — 조작 = 캐시(`b08-materials`) + 짝 계산(`Material_Calc.ts`)으로 바로 * 총괄 모래 · 자갈 · 시멘트 할증 = 좌측 패널 값(PLAN 124 · 표에서 안 고침 · 패널 조작에 바로 다시 셈) * [저장] = PUT …/quantity/materials → 서버가 정본으로 다시 셈 · 캐시 비움 · 자동 저장 없음 + * 값 없는 줄은 숨김(화면만 · 셈 · 열 · 단위수량은 그대로) · 표는 공용 카드 안(PLAN 143-3) * ========================================================================== */ import { API_BASE_URL } from "@config/config_frontend"; import { createButton, + createCard, hideLoadingOverlay, showLoadingOverlay, showToast, @@ -49,6 +51,12 @@ const HINTS: Record = { concrete: "㎥당 = 산림품셈 12-1-3 (B) 배합 · 건축 9-1-1 모르터 기본(무근은 빈칸) — 고칠 수 있음", quantity: "수량 = 구조물 수량 × 단위수량 · 단위수량은 빈칸(사용자 입력) · 관매설은 1", }; +const HIDDEN = "값 없는 줄은 숨김"; + +/** 보일 줄 차례(원래 줄 번호) — 값 없는 줄은 숨김(143-3) */ +const shown = (rows: readonly T[], has: (row: T) => boolean): number[] => + rows.flatMap((row, i) => (has(row) ? [i] : [])); +const anyValue = (cells: Cells): boolean => Object.values(cells).some((v) => v != null); async function request(path: string, init: RequestInit = {}): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { @@ -99,7 +107,9 @@ interface Table { function quantityTable(s: MaterialSheets, o: MaterialOverrides): Table { const { materials, rows, total } = s.quantity; - const rowsOut: DataRow[] = rows.flatMap((row) => { + const keep = shown(rows, (row) => row.qty != null || anyValue(row.units)); + const rowsOut: DataRow[] = keep.flatMap((at) => { + const row = rows[at]; const unit: DataRow = { name: row.name, spec: row.spec, @@ -137,7 +147,7 @@ function quantityTable(s: MaterialSheets, o: MaterialOverrides): Table { total: totalRow, frozen: 5, editable: (i, key) => i % 2 === 0 && key.startsWith("m:"), - target: (i, key) => [o.units, rows[i / 2].id, key.slice(2)], + target: (i, key) => [o.units, rows[keep[i / 2]].id, key.slice(2)], }; } @@ -149,9 +159,11 @@ const MIX_LABEL: Record<(typeof MIX_FIELDS)[number], [string, string, string]> = function concreteTable(s: MaterialSheets, o: MaterialOverrides): Table { const { rows, total } = s.concrete; - const rowsOut: DataRow[] = rows.map((row, i) => { + const keep = shown(rows, (row) => row.qty != null || anyValue(row.amounts)); + const rowsOut: DataRow[] = keep.map((at, i) => { + const row = rows[at]; const out: DataRow = { - name: i > 0 && rows[i - 1].name === row.name ? "" : row.name, + name: i > 0 && rows[keep[i - 1]].name === row.name ? "" : row.name, spec: row.spec, qty: row.qty, }; @@ -187,12 +199,13 @@ function concreteTable(s: MaterialSheets, o: MaterialOverrides): Table { total: totalRow, frozen: 2, editable: (_i, key) => key.startsWith("per:"), - target: (i, key) => [o.mix, rows[i].key, key.slice(4)], + target: (i, key) => [o.mix, rows[keep[i]].key, key.slice(4)], }; } function totalTable(s: MaterialSheets, o: MaterialOverrides): Table { const { rows } = s.material; + const keep = shown(rows, (row) => row.qty != null || row.amount != null); return { columns: [ { key: "group", label: "구분", width: 96 }, @@ -203,19 +216,22 @@ function totalTable(s: MaterialSheets, o: MaterialOverrides): Table { numberColumn({ key: "amount", label: "합계", width: 96 }), { key: "note", label: "비고", width: 96 }, ], - rows: rows.map((row, i) => ({ - group: i > 0 && rows[i - 1].group === row.group ? "" : row.group, - spec: row.spec, - unit: row.unit, - qty: row.qty, - surcharge: row.surcharge, - amount: row.amount, - note: row.panel ? "할증 = 좌측 패널" : "", - })), + rows: keep.map((at, i) => { + const row = rows[at]; + return { + group: i > 0 && rows[keep[i - 1]].group === row.group ? "" : row.group, + spec: row.spec, + unit: row.unit, + qty: row.qty, + surcharge: row.surcharge, + amount: row.amount, + note: row.panel ? "할증 = 좌측 패널" : "", + }; + }), total: null, frozen: 2, - editable: (i, key) => key === "surcharge" && !rows[i].panel, - target: (i) => [o.surcharge, rows[i].key, null], + editable: (i, key) => key === "surcharge" && !rows[keep[i]].panel, + target: (i) => [o.surcharge, rows[keep[i]].key, null], }; } @@ -254,7 +270,7 @@ export async function renderMaterialSheet( const toolbar = document.createElement("div"); toolbar.className = "b08-material__toolbar"; const notes = document.createElement("div"); - notes.append(note(BASIS), note(HINTS[sheet])); + notes.append(note(BASIS), note(`${HINTS[sheet]} · ${HIDDEN}`)); if (sheet !== "quantity") { // 빈 까닭 한 줄(131) — 단위수량 빈 줄 · 서버 알림 · 빈 칸은 빈칸 그대로 const blank = blankUnitRows(sheets); @@ -300,7 +316,7 @@ export async function renderMaterialSheet( built = BUILD[sheet](sheets, overrides); table.setRows(built.rows, built.total); }; - host.replaceChildren(toolbar, table.root); + host.replaceChildren(createCard({ body: [toolbar, table.root] })); onOptionsChange(host, () => { sheets = calc(); redraw(); diff --git a/B08_Quantity/B08_Quantity_UI_Summary.ts b/B08_Quantity/B08_Quantity_UI_Summary.ts index 5038a7c92..ddb310f21 100644 --- a/B08_Quantity/B08_Quantity_UI_Summary.ts +++ b/B08_Quantity/B08_Quantity_UI_Summary.ts @@ -16,7 +16,7 @@ import { API_BASE_URL } from "@config/config_frontend"; import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; -import { showToast } from "@ui/ui_template_elements"; +import { createCard, showToast } from "@ui/ui_template_elements"; import { fetchStructures } from "../B05_Profile/B05_Profile_Api_Structures"; import { fetchLatestRoute, readRouteLatestCache } from "../B05_Profile/B05_Profile_Api_Fetch"; import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; @@ -238,7 +238,7 @@ export async function renderStructureSummary(host: HTMLElement, projectId: strin : note(L("B08_Quantity_Summary_NoStation")); let table = draw(); parts.push(table); - host.replaceChildren(...parts); + host.replaceChildren(createCard({ body: parts })); // 공용 카드 안(토공 표와 같은 꼴 · 143-3) onOptionsChange(host, () => { const next = draw(); table.replaceWith(next); diff --git a/resources/tester/test_143_3_b08_material_cards.py b/resources/tester/test_143_3_b08_material_cards.py new file mode 100644 index 000000000..9f3b3234b --- /dev/null +++ b/resources/tester/test_143_3_b08_material_cards.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +"""B08 구조물 탭 — 자재 셋 값 없는 줄 숨김 · 표 넷 공용 카드 안(PLAN 143-3). + +숨김은 화면만 — 셈 · 열 · 단위수량은 그대로 · 고친 칸은 원래 줄 번호(`keep`)로 저장 자리를 찾음. +""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +MATERIAL = (ROOT / "B08_Quantity/B08_Quantity_UI_Material.ts").read_text(encoding="utf-8") +SUMMARY = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8") + + +def test_자재_셋_값_없는_줄_숨김(): + assert MATERIAL.count("const keep = shown(rows, ") == 3 # 총괄 · 콘크리트 · 자재수량 + assert "row.qty != null || anyValue(row.units)" in MATERIAL # 손 단위수량 있으면 보임 + assert "row.qty != null || anyValue(row.amounts)" in MATERIAL + assert "row.qty != null || row.amount != null" in MATERIAL + # 고친 칸 → 원래 줄(숨긴 줄 건너뜀) + for target in ( + "rows[keep[i / 2]].id", + "rows[keep[i]].key, key.slice(4)", + "rows[keep[i]].panel", + ): + assert target in MATERIAL, target + + +def test_표_넷_공용_카드_안(): + assert "createCard({ body: [toolbar, table.root] })" in MATERIAL + assert "createCard({ body: parts })" in SUMMARY