Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Sangeun_BaseHead.ts
T
eomsangdon 94e330a5f3 feat(748-0): 단가산출 둘째 기초 — 세부 품 M01 원문 · 머리 수량 칸 하나 · 되돌림 셋(기초 빼기 · 더한 항목 빼기 · 더한 항목 수량)
- 둘째 기초 세부 줄 품 칸 = 서버 `원문`(0.871 그대로) · 금액 · 비목은 상자 합 몫 그대로 · 세부 고침도 원문 몫(머리 수량 · 화면배 안 나눔)
- 더한 기초 머리 상자(새 `Sangeun_BaseHead`) — 기초마다 이름 · 수량(기준단위 몇 개) 칸 · [되돌림] · [기초 빼기]
- 새 `logic/M02_Sangeun_Undo` — 뺀더한줄 기억 · 되살림(열쇠 당김 · 밂) · 처음수량 되돌림 · 넣을 때 처음수량 적음
- 되살림 띠에 뺀 더한줄도 · 일위대가 줄 패치 뺀더한줄 이음(RowSk · LinePatch)
- 낡은 시험 고침: test_727 세부 고침 ÷수량 기대 → 원문 그대로 · test_717_2b 모델 인라인에 Undo 추가
- 새 시험 test_748_0_sk_undo(거울 helper_748_0)
2026-11-02 22:05:31 +09:00

93 lines
3.8 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Sangeun_BaseHead.ts
* 단가산출(SK) 둘째 기초 머리 줄(PLAN 748-0) — 기초마다 한 줄: 이름 · 「수량(기준단위 몇 개)」 칸 하나 ·
* [되돌림](넣을 때 수량으로) · [기초 빼기]. 세부 품은 M01 원문 그대로 보임(머리 수량은 이 칸 하나로).
* 값은 문서 `더한줄[k].수량`(화면배 t 로 나눈 화면 값) — 금액은 서버가 셈 · 여기는 칸 · 단추만.
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { createButton } from "@ui/ui_template_button";
import { createCard } from "@ui/ui_template_card";
import { el } from "@ui/ui_template_elements";
import { createInputField } from "@ui/ui_template_input";
import { t as L } from "@ui/ui_template_locale";
import { createText } from "@ui/ui_template_text";
import type { SkAnswer, SkDoc } from "./logic/M02_Sangeun_Cover_Model";
export interface BaseHeadOptions {
doc: SkDoc;
ans: SkAnswer | null;
edit: boolean;
/** 칸에서 고른 새 수량(화면 값) */
onQty: (k: number, qty: number) => void;
onReset: (k: number) => void;
onDrop: (k: number, name: string) => void;
}
const shown = (v: number): string => String(Number(v.toPrecision(8)));
const same = (a: number, b: number): boolean => Math.abs(a - b) <= 1e-9 * Math.max(1, Math.abs(a));
/** 둘째 기초(더한줄 로직)마다 머리 줄 한 상자 — 없으면 빈 목록 */
export function baseHeads(o: BaseHeadOptions): HTMLElement[] {
const t = o.ans?.화면배 ?? 1;
const rows = (o.doc.더한줄 ?? []).flatMap((one, k) => {
if (one.종류 !== "로직") return [];
const line = o.ans?.줄.find((x) => x.cid === `add:#${k}`);
const name = line?.이름 || String(one.로직 ?? "");
const box = createInputField({
label: L("M02_SangeunBaseQty"),
value: shown(one.수량 / t),
compact: true,
size: 28,
});
box.input.inputMode = "decimal";
box.input.disabled = !o.edit;
box.input.dataset.baseQty = String(k);
box.input.addEventListener("change", () => {
const v = Number(box.input.value.replaceAll(",", "").trim());
if (box.input.value.trim() === "" || !Number.isFinite(v) || v < 0)
return box.setError(L("M02_SangeunBaseQtyBad"));
box.setError(undefined);
if (!same(v, one.수량 / t)) o.onQty(k, v);
});
const first = one.처음수량;
return [
el("div", {
className: "m02-sk__basehead",
attrs: { "data-base-head": String(k) },
children: [
el("span", { className: "m02-sk__basename", text: name }),
...(line?.꼬리표 ? [createBadge(line.꼬리표, "neutral")] : []),
box.root,
...(line?.단위 ? [createText(line.단위, "muted")] : []),
...(o.edit && first != null && !same(first, one.수량)
? [
createButton({
label: L("M02_SangeunQtyUndo"),
variant: "ghost",
size: 28,
onClick: () => o.onReset(k),
}),
]
: []),
...(o.edit
? [
createButton({
label: L("M02_SangeunDropBase"),
variant: "danger-ghost",
size: 28,
onClick: () => o.onDrop(k, name),
}),
]
: []),
],
}),
];
});
if (!rows.length) return [];
const card = createCard({ title: L("M02_SangeunBases"), pad: 12, body: rows });
card.classList.add("m01lab__ho-box"); // 상자 제목 · 안 간격 = 공용 상세 상자와 같음
card.dataset.baseHeads = "";
return [card];
}