Files
Aislo/ui_template/ui_template_equation_sum.ts
T
eomsangdonandClaude Sonnet 5.5 c47e6dc832 feat(796-2e-2): 더한 기초 블록 [연결 끊기] · [다시 잇기] 화면 — 수식 탭 소계 블록 머리 상태 · 단추 · 끊은 블록 속 수식 고침
- 소계 표 부품에 덧단추 자리(EqSumHooks head · body) · EqPage.sum 으로 페이지가 끼움
- M02 수식 탭: 블록 머리 「연결」 · 「끊음」 + [연결 끊기] · [다시 잇기](확인창) · 끊은 블록 속 = 같은 수식 부품 · [단가 갱신] · [재생성] 보고에 끊은 기초 한 줄
- test_799_1_subtotals 낡은 문자열 비교(createEqSum(eq, open) 통째)를 인자 앞부분 비교로 고침 — 부품 호출에 덧단추 인자가 붙어서

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 15:47:37 +09:00

134 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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;
}