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 확정).