/* ============================================================================= * M02_DF_Summary.ts * 단가산출 다이어그램 — 원가 합계 칸: 큰 합계 「1㎡ 당 ○원」 · 노무비 · 재료비 · 경비 · 출력 상자별 줄 * · 값(비용 아님) 출력 · 빈 받는 자리 · 오류 · 예시값(변수 상자의 예시값을 여기서도 바꿈) * 값은 화면 계산(Eval `evalLogic`) 결과를 받음 — 이 파일은 셈하지 않고 읽어서 보여 줌. * 모양은 docs/raw/M01_쉬운단가/원가다이어그램_정함.md 점검 5 · 6. 이 폴더째 지워도 됨. * ========================================================================== */ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { el } from "@ui/ui_template_elements"; import { formatMoney } from "../../M01_MasterData/M01_MasterData_UI_Logic_Money"; import type { CfEvalOptions, CfEvalResult, CfValues } from "./M02_DF_Eval"; import type { CfBox, CfLogic, CfRound } from "./M02_DF_Model"; import { showValue } from "./M02_DF_OutputPanel"; import { defaultTotalRound, itemRoundNow, roundBy, roundText, showRounded } from "./M02_DF_Round"; import { renderVerify, type CfSel } from "./M02_DF_Verify"; import "./M02_DF_Summary.css"; const TEXT = { Per: ["{per} 당", "per {per}"], Total: ["원가 합계", "Cost total"], Won: ["원", "KRW"], Labor: ["노무비", "Labor cost"], Material: ["재료비", "Material cost"], Expense: ["경비", "Expense"], Lines: ["출력 상자별", "By output box"], HName: ["이름", "Name"], HCost: ["비용 종류", "Cost kind"], HValue: ["값", "Value"], HUnit: ["단위", "Unit"], Plain: ["값(비용 아님)", "Values (not costs)"], RoundTotal: ["합계 끝수", "Total rounding"], Plain3: ["값", "Value"], } as const; type TextKey = keyof typeof TEXT; const ts = (key: TextKey, fill: Record = {}): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace( /\{(\w+)\}/g, (m, k: string) => fill[k] ?? m, ); const COSTS = ["노무비", "재료비", "경비"] as const; const COST_KEY = { 노무비: "Labor", 재료비: "Material", 경비: "Expense" } as const; const costText = (c: string): string => (c === "값" ? ts("Plain3") : c); /** 원가 합계 끝수 — 기본 = 일위대가표 계금 1원 미만 버림(표를 읽어 오면 그 값) */ let totalRound: CfRound = { places: 0, method: "버림" }; void defaultTotalRound().then((r) => (totalRound = r)); /** 출력 상자 값 — 그 출력의 끝수 자리대로(끝수를 안 정했으면 비용은 표준 기본 · 값은 그대로) */ const shown = (b: CfBox, v: unknown, isCost: boolean): string => typeof v === "number" ? showRounded(v, b.output?.round ?? (isCost ? itemRoundNow() : undefined)) : showValue(v as never); /** 원가 합계 칸 — result = 화면 계산 결과 · on 이 있으면 예시값을 바꿀 수 있음 · opts.values = 받은 입력 값(검사가 씀) */ export function renderSummary( host: HTMLElement, lg: CfLogic, result: CfEvalResult, on?: { change(lg: CfLogic): void; select?(sel: CfSel): void }, opts?: { values?: CfValues; logics?: CfEvalOptions["logics"] }, ): void { const money = !lg.resultUnit || lg.resultUnit.startsWith("원"); const perUnit = lg.resultUnit.split("/").slice(1).join("/"); const perText = !perUnit ? ts("Total") : ts("Per", { per: /^\d/.test(perUnit) ? perUnit : `1${perUnit}` }); const { cost } = result; const big = el("p", { className: "m02-dfsum__big", children: [ el("span", { text: money ? perText : ts("Total") }), el("strong", { text: `${formatMoney(roundBy(totalRound, cost.계))} ${ts("Won")}` }), ], }); const costs = el("dl", { className: "m02-dfsum__costs", children: COSTS.flatMap((c) => [ el("dt", { text: ts(COST_KEY[c]) }), el("dd", { text: formatMoney(roundBy(totalRound, cost[c])) }), ]), }); /* 출력 상자별 줄 — 비용 줄 · 값(비용 아님)은 따로 */ const outs: CfBox[] = lg.boxes.filter((b) => b.kind === "output"); const costed = outs.filter((b) => b.output?.cost !== "값"); const plain = outs.filter((b) => b.output?.cost === "값"); const line = (b: CfBox, isCost: boolean): HTMLElement => el("tr", { children: [ el("td", { text: b.name }), ...(isCost ? [el("td", { text: costText(b.output?.cost ?? "") })] : []), el("td", { className: "m02-dfsum__num", text: shown(b, result.outputs[b.id], isCost), }), el("td", { text: b.output?.unit ?? b.unit }), ], }); const table = (list: CfBox[], isCost: boolean, title: string): HTMLElement => el("div", { className: "m02-dfsum__block", children: [ el("strong", { text: title }), el("div", { className: "m02-dfsum__scroll", children: [ el("table", { className: "m02-dfsum__table", children: [ el("thead", { children: [ el("tr", { children: [ el("th", { text: ts("HName") }), ...(isCost ? [el("th", { text: ts("HCost") })] : []), el("th", { text: ts("HValue") }), el("th", { text: ts("HUnit") }), ], }), ], }), el("tbody", { children: list.map((b) => line(b, isCost)) }), ], }), ], }), ], }); const verifyHost = el("div", { className: "m02-dfsum__verify" }); renderVerify( verifyHost, lg, result, { change: (next) => on?.change(next), select: on?.select }, { values: opts?.values, logics: opts?.logics }, ); const plainValues = Object.entries(cost.값들); host.replaceChildren( big, costs, ...(costed.length ? [table(costed, true, ts("Lines"))] : []), ...(plain.length ? [table(plain, false, ts("Plain"))] : plainValues.length ? [ el("div", { className: "m02-dfsum__block", children: [ el("strong", { text: ts("Plain") }), ...plainValues.map(([k, v]) => el("p", { className: "m02-dfsum__muted", text: `${k} · ${showValue(v)}` }), ), ], }), ] : []), el("p", { className: "m02-dfsum__muted", text: `${ts("RoundTotal")}: ${roundText(totalRound)}`, }), verifyHost, ); }