/* ============================================================================= * B09_Estimation_UI_BillRates.ts * 설계내역서 **할증율·일괄보정** 칸 — STmate `wBoqRate`·`wM_Rate` 본 (PLAN 12장 2차 ④) * * - 범위: 선택 항목만(줄마다 한 칸) / 내역서 전체(`*`) · 방식: 같은 비율 / 비목별(재·노·경) * - 비목마다 절사·반올림 × 소수 0~4 자리(안 고르면 원 미만 절사). * - ⚠ 여러 줄을 한꺼번에 바꾸므로 「할증 전부 되돌리기」를 둠 — 줄마다 ↺ 도 있음. * - 화면은 고른 값만 보냄 — 금액은 서버가 다시 셈(내역 한 벌을 새로 받음). * ========================================================================== */ import { showToast } from "@ui/ui_template_elements"; import { L, el, userMark } from "./B09_Estimation_UI_Sheet"; import { loadEdits, saveEdits, type BillRowDto, type EditChange } from "./B09_Estimation_UI_Store"; const SECTION = "bill_rates"; const PARTS = [ ["material", "B09_Sheet_Col_Material"], ["labor", "B09_Sheet_Col_Labor"], ["expense", "B09_Sheet_Col_Expense"], ] as const; function save(projectId: string, changes: EditChange[], reload: () => void): void { if (changes.length === 0) return; void saveEdits(projectId, changes) .then(() => { showToast(L("B09_Sheet_Saved"), "success"); reload(); }) .catch((error: Error) => showToast(`${L("B09_Sheet_SaveFailed")} ${error.message}`, "error")); } function numberBox(placeholder: string): HTMLInputElement { const input = el("input", "b09s-edit-input"); input.type = "text"; input.inputMode = "decimal"; input.placeholder = placeholder; return input; } function choice(options: Array<[string, string]>): HTMLSelectElement { const select = el("select"); for (const [value, label] of options) { const option = el("option", "", label); option.value = value; select.append(option); } return select; } /** 할증·일괄보정 칸 한 벌 — `selected` = 고른 줄의 할증 칸 이름. */ export function rateForm( projectId: string, selected: Set, reload: () => void, ): HTMLElement { const box = el("div", "b09s-group"); const scope = choice([ ["selected", L("B09_Sheet_Rate_Selected")], ["all", L("B09_Sheet_Rate_All")], ]); const way = choice([ ["same", L("B09_Sheet_Rate_Same")], ["parts", L("B09_Sheet_Rate_Parts")], ]); const same = numberBox("%"); const parts = PARTS.map(([key, label]) => { const rate = numberBox("%"); const mode = choice([ ["floor", L("B09_Sheet_Rate_Floor")], ["round", L("B09_Sheet_Rate_Round")], ]); const digits = choice( [0, 1, 2, 3, 4].map((n): [string, string] => [ String(n), `${L("B09_Sheet_Rate_Digits")} ${n}`, ]), ); const line = el("span", "b09s-inline"); line.append(el("span", "b09s-head", L(label)), rate, mode, digits); return { key, rate, mode, digits, line }; }); const apply = el("button", "b09s-undo", L("B09_Sheet_Apply")); apply.type = "button"; apply.addEventListener("click", () => { const rounding = Object.fromEntries( parts.map((part) => [part.key, { mode: part.mode.value, digits: Number(part.digits.value) }]), ); const value = way.value === "same" ? { all: same.value.trim() || "0", rounding } : { ...Object.fromEntries(parts.map((part) => [part.key, part.rate.value.trim() || "0"])), rounding, }; const keys = scope.value === "all" ? ["*"] : [...selected]; if (keys.length === 0) { showToast(L("B09_Sheet_Rate_PickRows"), "info"); return; } save( projectId, keys.map((key) => ({ section: SECTION, key, value })), reload, ); }); const clear = el("button", "b09s-undo", L("B09_Sheet_Rate_ClearAll")); clear.type = "button"; clear.addEventListener("click", () => { void loadEdits(projectId).then((stored) => save( projectId, Object.keys(stored.edits[SECTION] ?? {}).map((key) => ({ section: SECTION, key, value: null, })), reload, ), ); }); const head = el("div", "b09s-bar"); head.append( el("span", "b09s-head", L("B09_Sheet_Rate")), scope, way, same, apply, clear, el("span", "b09s-hint", `${L("B09_Sheet_Rate_SelectedCount")} ${selected.size}`), ); const partLine = el("div", "b09s-bar"); partLine.append(...parts.map((part) => part.line)); box.append(head, partLine); return box; } /** 줄 비고의 할증 표시 — 그 줄 칸으로 얹혔으면 「사용자」+↺, 내역서 전체로 얹혔으면 표시만. */ export function rateMark( projectId: string, row: BillRowDto, reload: () => void, ): HTMLElement | null { if (!row.rate) return null; if (row.rate.source === "*") return el("span", "b09s-badge b09s-badge--user", L("B09_Sheet_Rate_AllBadge")); return userMark(() => save(projectId, [{ section: SECTION, key: row.rate_key, value: null }], reload), ); }