feat(B08): 자재 셋 값 없는 줄 숨김 · 구조물 탭 표 넷 공용 카드 안(143-3)
- 총괄 · 콘크리트 · 자재수량 — 수량 · 금액 · 손 단위수량 없는 줄 숨김(화면만 · 고친 칸은 원래 줄 자리로 저장) - 구조물 집계표 · 자재 셋 — 공용 카드 안(토공 표와 같은 꼴) - 시험 — 새 test_143_3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
@@ -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<MaterialSheetId, string> = {
|
||||
concrete: "㎥당 = 산림품셈 12-1-3 (B) 배합 · 건축 9-1-1 모르터 기본(무근은 빈칸) — 고칠 수 있음",
|
||||
quantity: "수량 = 구조물 수량 × 단위수량 · 단위수량은 빈칸(사용자 입력) · 관매설은 1",
|
||||
};
|
||||
const HIDDEN = "값 없는 줄은 숨김";
|
||||
|
||||
/** 보일 줄 차례(원래 줄 번호) — 값 없는 줄은 숨김(143-3) */
|
||||
const shown = <T>(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<T>(path: string, init: RequestInit = {}): Promise<T> {
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user