- 704-2 Labor_Sheet 줄 · 소계 · 계 = calcSheetRows(표본 셋 DOM 전후 같음) - 새 M01_MasterData_UI_LogicLab_Est.ts — /text 묶음 그대로 · 묶음이 쓰는 시험 입력(714-3 usedInputs) = 머리 변수 칸 · 「준비 중」 자리 걷음 - 낡은 시험 test_704_logic_standard 고침 — 계산서가 계산 줄 부품을 공용 계산서 안에서 씀(import 자리만 바뀜) - 새 지킴 시험 test_714_2_calc_sheet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
4.5 KiB
TypeScript
107 lines
4.5 KiB
TypeScript
/* =============================================================================
|
||
* calc_sheet.ts
|
||
* 공용 「단가산출 계산서」(PLAN 714-2 · 요구서 내역12 4장 5) — 704-2 M02 계산서 보기를 올린 것.
|
||
* 쓰는 곳 = M01 「인력·재료·경비」 보기 · M02 기준 목록 계산서 보기(704-2) · (뒤에) 일위대가 모달 · B09 단가산출근거
|
||
* 꼴 = 묶음마다 [머리(변수 칸이 있을 때만) · 쓰는 입력 값 줄] · 식 줄(이름 | 단가 × 품 | 금액) · 소계 · 맨 아래 계
|
||
* 값 = 서버 셈 그대로 찍음(화면에서 다시 셈 안 함) · 변수 칸 바꾸면 `onInput(이름, 값)` — 부르는 쪽이 다시 셈
|
||
* 부품 = `ui_template_calc_row`(값 줄 · 식 줄) · 입력 · 고르기 · 글 — 날 요소 · 페이지 CSS 없음
|
||
* ========================================================================== */
|
||
|
||
import { createCalcFormula, createCalcFormulaRow, createCalcRow } from "@ui/ui_template_calc_row";
|
||
import { el } from "@ui/ui_template_elements";
|
||
import { createInputField, createSelectField } from "@ui/ui_template_input";
|
||
import { createText } from "@ui/ui_template_text";
|
||
|
||
export interface SheetLine {
|
||
이름: string;
|
||
/** 서버 값 식 「단가 * 품 = 금액」 — 「= 금액」 은 떼고 「*」 → 「×」 */
|
||
글: string;
|
||
금액: number | null;
|
||
}
|
||
export interface SheetGroup {
|
||
비목: string;
|
||
소계: number | null;
|
||
끝수?: string;
|
||
줄: SheetLine[];
|
||
}
|
||
export interface SheetInput {
|
||
이름: string;
|
||
단위?: string;
|
||
고르기?: (string | number)[];
|
||
}
|
||
export interface SheetOptions {
|
||
묶음: SheetGroup[];
|
||
계: number | null;
|
||
/** 맨 아래 계 줄 이름(「계 (원/㎡)」) */
|
||
sumLabel: string;
|
||
/** 변수 칸 — 입력 꼴 · 지금 값 · 묶음이 쓰는 입력 이름 · 바꾼 뒤 할 일(없으면 변수 칸 없음) */
|
||
inputs?: SheetInput[];
|
||
values?: Record<string, string>;
|
||
uses?: (group: SheetGroup, index: number) => string[];
|
||
onInput?: (name: string, value: string) => void;
|
||
}
|
||
|
||
const won = (v: number | null, digits = 0): string =>
|
||
v === null ? "-" : v.toLocaleString("ko-KR", { maximumFractionDigits: digits });
|
||
|
||
const out = (v: number | null, key = false, digits = 0): HTMLElement =>
|
||
el("span", { className: `ui-calc-row__out${key ? " is-key" : ""}`, text: won(v, digits) });
|
||
|
||
/** 값 식 → 식 칸 글 — 「268,908 * 0.12 = 32,269」 → 「268,908 × 0.12」 */
|
||
export const sheetFormula = (text: string): string =>
|
||
text.replace(/ = [^=]*$/, "").replaceAll(" * ", " × ");
|
||
|
||
/** 변수 칸 하나 — 고르기 있으면 고르기 · 아니면 글 칸(Enter · 떠남 때 넘김) */
|
||
function varRow(spec: SheetInput, value: string, onInput: (v: string) => void): HTMLElement {
|
||
let control: HTMLElement;
|
||
if (spec.고르기?.length) {
|
||
control = createSelectField({
|
||
options: spec.고르기.map((o) => ({ value: String(o), text: String(o) })),
|
||
value,
|
||
compact: true,
|
||
ariaLabel: spec.이름,
|
||
onChange: onInput,
|
||
}).root;
|
||
} else {
|
||
const box = createInputField({ value, compact: true });
|
||
box.input.setAttribute("aria-label", spec.이름);
|
||
box.input.addEventListener("change", () => {
|
||
if (box.input.value.trim() && box.input.value !== value) onInput(box.input.value.trim());
|
||
});
|
||
control = box.root;
|
||
}
|
||
const row = createCalcRow(spec.단위 ? `${spec.이름} (${spec.단위})` : spec.이름, control);
|
||
row.dataset.sheetVar = spec.이름;
|
||
return row;
|
||
}
|
||
|
||
/** 계산서 줄들 — 부르는 쪽 세로 칸(flex column)에 그대로 붙임 */
|
||
export function calcSheetRows(o: SheetOptions): HTMLElement[] {
|
||
const rows: HTMLElement[] = [];
|
||
const specs = new Map((o.onInput ? (o.inputs ?? []) : []).map((s) => [s.이름, s]));
|
||
o.묶음.forEach((g, gi) => {
|
||
const vars = (o.uses?.(g, gi) ?? []).filter((n) => specs.has(n));
|
||
if (vars.length && o.onInput) {
|
||
const onInput = o.onInput;
|
||
const head = createText(g.비목, "muted", 14);
|
||
head.dataset.sheetHead = g.비목;
|
||
rows.push(head);
|
||
for (const n of vars)
|
||
rows.push(varRow(specs.get(n)!, o.values?.[n] ?? "", (v) => onInput(n, v)));
|
||
}
|
||
for (const line of g.줄)
|
||
rows.push(
|
||
createCalcFormulaRow(
|
||
line.이름,
|
||
createCalcFormula(sheetFormula(line.글)),
|
||
out(line.금액, false, 1),
|
||
),
|
||
);
|
||
rows.push(
|
||
createCalcFormulaRow(`${g.비목}${g.끝수 ? ` (${g.끝수})` : ""}`, null, out(g.소계, true)),
|
||
);
|
||
});
|
||
rows.push(createCalcFormulaRow(o.sumLabel, null, out(o.계, true)));
|
||
return rows;
|
||
}
|