- 둘째 기초 세부 줄 품 칸 = 서버 `원문`(0.871 그대로) · 금액 · 비목은 상자 합 몫 그대로 · 세부 고침도 원문 몫(머리 수량 · 화면배 안 나눔) - 더한 기초 머리 상자(새 `Sangeun_BaseHead`) — 기초마다 이름 · 수량(기준단위 몇 개) 칸 · [되돌림] · [기초 빼기] - 새 `logic/M02_Sangeun_Undo` — 뺀더한줄 기억 · 되살림(열쇠 당김 · 밂) · 처음수량 되돌림 · 넣을 때 처음수량 적음 - 되살림 띠에 뺀 더한줄도 · 일위대가 줄 패치 뺀더한줄 이음(RowSk · LinePatch) - 낡은 시험 고침: test_727 세부 고침 ÷수량 기대 → 원문 그대로 · test_717_2b 모델 인라인에 Undo 추가 - 새 시험 test_748_0_sk_undo(거울 helper_748_0)
93 lines
3.8 KiB
TypeScript
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];
|
|
}
|