- 소계 표 부품에 덧단추 자리(EqSumHooks head · body) · EqPage.sum 으로 페이지가 끼움 - M02 수식 탭: 블록 머리 「연결」 · 「끊음」 + [연결 끊기] · [다시 잇기](확인창) · 끊은 블록 속 = 같은 수식 부품 · [단가 갱신] · [재생성] 보고에 끊은 기초 한 줄 - test_799_1_subtotals 낡은 문자열 비교(createEqSum(eq, open) 통째)를 인자 앞부분 비교로 고침 — 부품 호출에 덧단추 인자가 붙어서 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
134 lines
5.9 KiB
TypeScript
134 lines
5.9 KiB
TypeScript
/* =============================================================================
|
||
* ui_template_equation_sum.ts
|
||
* 부품 「수식」 셋째 파일(799-1 · 796-2c · 요구 655-1 · 요구서_655 2 장 4) — 단가산출 소계 · 합계(보이기만 · 금액 셈 그대로).
|
||
* 표 = 줄(하위 로직 블록마다 한 줄 · 자기 자원 · 덧줄) × 노무비 · 재료비 · 경비 · 계 → 합계(엔진 비목 합) → 끝수(합계 − 줄 합)
|
||
* 값 = 서버 `소계`(M01 `master_equation.subtotals` · M02 `M02_Sangeun_Equation_Full` = SK 셈 답) · 끝수가 0 이면 줄을 안 그림 · 소계가 없으면(결과 로직) 아무것도 안 그림
|
||
* M02 SK 줄 = 상태 알약(더함 · 덮음 · 뺌) · 덮음 갈래 · 원래 금액 · 수량 · 더한 기초는 접힘 수식 블록 · 뺌 = 흐리게 · 금액 0
|
||
* 쓰는 법 = `createEqSum(tree, open?, hooks?)` → 요소 | null — 줄 이음(`createEqChain`) 아래에 붙임 · `open` = 펼친 블록 이름표 모음 ·
|
||
* `hooks` = 페이지가 더한 기초 줄 머리 단추(`head`) · 접힘 속(`body`)을 바꿔 끼움(796-2e 연결 끊기)
|
||
* ========================================================================== */
|
||
|
||
import { createBadge, type BadgeTone } from "./ui_template_badge";
|
||
import { createButton } from "./ui_template_button";
|
||
import { eqText, type EqSumHooks, type EqSumRow, type EqTree } from "./ui_template_equation";
|
||
import { createEqChain } from "./ui_template_equation_line";
|
||
|
||
const COLS = ["노무비", "재료비", "경비", "계"] as const;
|
||
const KIND: Record<string, string> = { 자원: "자기 자원", 덧줄: "덧줄" };
|
||
const TONE: Record<string, BadgeTone> = { 더함: "success", 덮음: "warning", 뺌: "neutral" };
|
||
|
||
const node = (tag: "div" | "span", className: string, text = ""): HTMLElement => {
|
||
const out = document.createElement(tag);
|
||
out.className = className;
|
||
if (text) out.textContent = text;
|
||
return out;
|
||
};
|
||
|
||
function cells(line: HTMLElement, get: (k: (typeof COLS)[number]) => unknown): void {
|
||
for (const k of COLS) {
|
||
const cell = node("span", "ui-eq__sum-cell", eqText(get(k)) || "0");
|
||
cell.dataset.eqSumCol = k;
|
||
line.append(cell);
|
||
}
|
||
}
|
||
|
||
function row(kind: string, name: string, get: (k: (typeof COLS)[number]) => unknown): HTMLElement {
|
||
const line = node("div", "ui-eq__sum-row");
|
||
line.dataset.eqSumKind = kind;
|
||
line.append(node("span", "ui-eq__sum-name", name));
|
||
cells(line, get);
|
||
return line;
|
||
}
|
||
|
||
/** M02 SK 줄 하나 — 이름 + 알약 + 원래 · 수량 / 더한 기초는 접힘 수식 */
|
||
function itemRow(r: EqSumRow, open: Set<string>, at: number, hooks?: EqSumHooks): HTMLElement {
|
||
const item = node("div", "ui-eq__sum-item");
|
||
const line = node("div", "ui-eq__sum-row");
|
||
line.dataset.eqSumKind = r.갈래;
|
||
const name = node("span", "ui-eq__sum-name");
|
||
const tree = r.나무;
|
||
const id = `sum:${at}:${r.이름}`;
|
||
const body = node("div", "ui-eq__sum-tree");
|
||
body.hidden = true;
|
||
if (tree) {
|
||
const head = createButton({
|
||
label: r.이름,
|
||
variant: "ghost",
|
||
size: 22,
|
||
iconStart: "chevronRight",
|
||
onClick: () => toggle(),
|
||
});
|
||
head.classList.add("ui-eq__block-head");
|
||
const toggle = (force?: boolean): void => {
|
||
const on = force ?? !open.has(id);
|
||
if (on) {
|
||
open.add(id);
|
||
if (!body.childElementCount)
|
||
body.append(
|
||
hooks?.body?.(r, at) ??
|
||
createEqChain(tree.줄, {
|
||
path: id,
|
||
open,
|
||
resultUnit: tree.결과단위,
|
||
numbered: false,
|
||
}),
|
||
);
|
||
} else open.delete(id);
|
||
item.dataset.open = on ? "1" : "";
|
||
body.hidden = !on;
|
||
};
|
||
name.append(head);
|
||
if (open.has(id)) toggle(true);
|
||
} else name.append(node("span", "ui-eq__sum-label", r.이름));
|
||
if (r.상태 && r.상태 !== "바탕") {
|
||
const detail = r.덮개?.length ? ` · ${r.덮개.join(" · ")}` : "";
|
||
name.append(createBadge(`${r.상태}${detail}`, TONE[r.상태] ?? "neutral", { size: 18 }));
|
||
}
|
||
name.append(...(hooks?.head?.(r, at) ?? []));
|
||
if (r.합밖) {
|
||
name.append(createBadge("합 밖", "neutral", { size: 18 }));
|
||
line.dataset.eqSumOut = "1";
|
||
}
|
||
const notes: string[] = [];
|
||
if (r.합밖) notes.push(`합계에 안 듦 ${eqText(r.밖금액) || "0"}`);
|
||
if (r.상태 === "더함" && r.수량 !== undefined && r.수량 !== null)
|
||
notes.push(`× ${eqText(r.수량)}`);
|
||
if (r.원래 !== undefined && r.원래 !== null && r.상태 !== "더함")
|
||
notes.push(`원래 ${eqText(r.원래)}`);
|
||
if (r.까닭) notes.push(r.까닭);
|
||
if (notes.length) name.append(node("span", "ui-eq__sum-was", notes.join(" · ")));
|
||
line.dataset.eqSumState = r.상태 ?? "바탕";
|
||
line.append(name);
|
||
cells(line, (k) => r[k]);
|
||
item.dataset.eqSumItem = r.갈래;
|
||
item.append(line, body);
|
||
return item;
|
||
}
|
||
|
||
export function createEqSum(
|
||
tree: Pick<EqTree, "합계" | "소계" | "답까닭">,
|
||
open: Set<string> = new Set(),
|
||
hooks?: EqSumHooks,
|
||
): HTMLElement | null {
|
||
const sub = tree.소계;
|
||
if (!sub?.줄.length) return null;
|
||
const box = node("div", "ui-eq__sum");
|
||
box.dataset.eqSum = "";
|
||
const head = node("div", "ui-eq__sum-row ui-eq__sum-head");
|
||
head.append(node("span", "ui-eq__sum-name", "소계"));
|
||
for (const k of COLS) head.append(node("span", "ui-eq__sum-cell", k));
|
||
box.append(head);
|
||
sub.줄.forEach((r, at) => {
|
||
if (r.상태) box.append(itemRow(r, open, at, hooks));
|
||
else
|
||
box.append(row(r.갈래, r.이름 === r.갈래 ? (KIND[r.갈래] ?? r.이름) : r.이름, (k) => r[k]));
|
||
});
|
||
const rest = sub.끝수;
|
||
const odd = COLS.some((k) => Number(rest[k] ?? 0) !== 0);
|
||
if (odd) box.append(row("끝수", "끝수", (k) => rest[k]));
|
||
box.append(row("합계", "합계", (k) => tree.합계[k]));
|
||
const note = [odd ? sub.끝수까닭 : "", tree.답까닭].filter(Boolean).join(" · ");
|
||
if (note) box.append(node("div", "ui-eq__sum-note", note));
|
||
return box;
|
||
}
|