From 87083c3341953e0b0670a626272f9bb44e1624f8 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 10:52:52 +0900 Subject: [PATCH] =?UTF-8?q?feat(b08):=20=EC=9E=90=EC=9E=AC=EC=B4=9D?= =?UTF-8?q?=EA=B4=84=20=ED=95=A0=EC=A6=9D=EB=A5=A0=20=E3=80=8C=EC=9D=B4?= =?UTF-8?q?=EB=82=B4=E3=80=8D=20=EC=B9=B8=20=E2=80=94=20=ED=91=9C=EA=B0=92?= =?UTF-8?q?=20=EC=83=81=ED=95=9C=20=EC=95=88=EC=97=90=EC=84=9C=EB=A7=8C=20?= =?UTF-8?q?=EA=B3=A0=EB=A6=84=20=C2=B7=20=EB=B9=84=EC=9A=B0=EB=A9=B4=20?= =?UTF-8?q?=ED=91=9C=EA=B0=92(=EC=B4=88=EC=95=88=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EC=9D=80=20Page=20=ED=95=9C=20=EC=A4=84=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- B08_Quantity/B08_Quantity_UI_MaterialGrid.ts | 49 +++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts b/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts index da19e5e9..6c173438 100644 --- a/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts +++ b/B08_Quantity/B08_Quantity_UI_MaterialGrid.ts @@ -28,6 +28,8 @@ export interface MaterialRow { unit: string; net_amount: number; surcharge_pct: number | null; + /** 할증률표 값 = 고를 수 있는 천장(품셈 1-3-1 「표의 값 이내」). `null` 이면 칸이 안 섬. */ + surcharge_cap?: number | null; total_amount: number; supply: string; supply_label: string; @@ -241,9 +243,49 @@ export interface SupplyChoice { export interface MaterialGridOptions { /** 저장 전 변경분 — 고른 값은 여기 쌓이고 [저장]에서만 정본으로 간다. */ choices: Record; + /** 할증률 고른 값 `{이름 규격: % | null}` — 산출 조건 `material_surcharge`. 안 주면 칸 없이 글자만. + * `null` = 표값으로 되돌림. 0 ~ 표값만 받음(서버도 한 번 더 거름). */ + surcharge?: Record; onChange: () => void; } +/** 할증률 칸 — 표값(상한) 안에서만. 범위를 넘으면 칸이 빨갛게 되고 값을 안 받음. */ +function surchargeCell( + row: MaterialRow, + key: string, + picks: Record, + onChange: () => void, +): HTMLTableCellElement { + const cap = row.surcharge_cap as number; + const td = document.createElement("td"); + const input = document.createElement("input"); + input.type = "number"; + input.min = "0"; + input.max = String(cap); + input.step = "0.1"; + input.className = "b08-grid__select"; + input.style.width = "4.5em"; + const picked = picks[key]; + input.value = String(picked ?? row.surcharge_pct ?? cap); + input.title = `표값 ${cap}% 이내(품셈 1-3-1) · 비우면 표값`; + input.addEventListener("change", () => { + const text = input.value.trim(); + const value = Number(text); + if (text !== "" && !(Number.isFinite(value) && value >= 0 && value <= cap)) { + input.setCustomValidity(`0 ~ ${cap}% 만 받음`); + input.reportValidity(); + return; + } + input.setCustomValidity(""); + picks[key] = text === "" ? null : value; + if (text === "") input.value = String(cap); + td.classList.add("is-changed"); + onChange(); + }); + td.append(input); + return td; +} + /** 표 안의 고르는 칸. 바꾼 줄은 **표시가 남는다** — 무엇을 만졌는지 보여야 한다. */ function choiceCell( value: string, @@ -337,7 +379,12 @@ export function renderMaterialGrid( tr.append(textCell(row.unit, "b08-grid__unit")); tr.append(textCell(num(row.net_amount, 2))); // 미확보는 빈칸이 아니라 「-」 — 빈칸이면 0 % 로 오해된다. - tr.append(textCell(row.surcharge_pct === null ? "-" : num(row.surcharge_pct, 0))); + // 표값이 있는 줄은 「이내」로 고를 칸(초안 `surcharge` 가 이어졌을 때만). + tr.append( + options?.surcharge && typeof row.surcharge_cap === "number" + ? surchargeCell(row, key, options.surcharge, options.onChange) + : textCell(row.surcharge_pct === null ? "-" : num(row.surcharge_pct, 0)), + ); tr.append(textCell(num(row.total_amount, 2))); if (options) { // 관급/사급은 **자재마다 갈리는 발주 결정**이라 줄에서 고른다(2026-09-07 확정).