/* ============================================================================= * M01_MasterData_UI_LogicLab_Cover.ts * 단가산출 상세 줄 「그 자리 고침」(PLAN 722-2 · 내역 16 · 17-2) — 줄 모달 안 수량 · 단가 칸. * 고침 꼴 = M02 단가산출 덮개 칸과 같음(`qty` · `labor` · `material` · `expense` — `M02_Sangeun_Doc._covered` 규칙 중 품 · 단가) * M02(724) = 고침 칸의 값을 고침 고리(`HoEdit.set`)로 받아 덮개로 보관 — M01 시험 고침은 840-1 에 걷음 * 서버 셈 = 줄 `단가`(손 단가) · 로직 줄 `값묶음`(노 · 재 · 경) · `수량` 글 — `master_pick._priced` · `bundled` 가 읽음 * ========================================================================== */ import { createButton, createInputField, el } from "@ui/ui_template_elements"; import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createText } from "@ui/ui_template_text"; /* ── 순수 셈 ── */ /** 줄 하나 고침 — 품(수량) · 비목 단가(로직 줄은 셋 · 그 밖은 그 줄 비목 하나) */ export interface LineCover { qty?: number; labor?: number; material?: number; expense?: number; } export const COVER_PARTS = { labor: "노무비", material: "재료비", expense: "경비" } as const; export type CoverPart = keyof typeof COVER_PARTS; /* ── 순수 셈 끝 ── */ const TEXT = { Head: ["그 자리 고침 (시험 값)", "Edit here (trial value)"], Note: [ "정본 · 마스터 값(노임 · 자재)은 안 바뀜 · 로직을 다시 열면 사라짐", "Source and master prices stay unchanged · cleared when reopened", ], NoteM02: [ "고친 값은 이 단가산출의 변경 항목으로 저장 — M01 은 안 바뀜", "Saved as this item's override — M01 stays unchanged", ], Qty: ["수량(품)", "Quantity"], Price: ["단가", "Unit price"], Apply: ["적용", "Apply"], Undo: ["되돌림", "Undo"], Bad: ["0 이상 수만", "Numbers ≥ 0 only"], } as const satisfies Record; export const tc = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0]; export interface CoverFormOptions { /** 지금 값(덮기 전 셈 값) — 칸 처음 값 */ qty: number | null; /** 단가 칸 — 로직 줄 = 셋(1 단위 비목 값) · 그 밖 = 그 줄 비목 하나 */ prices: Partial>; /** 지금 든 고침(있으면 칸 처음 값 · [되돌림]) */ cover?: LineCover; /** 「m02」 = 덮개 보관 안내 */ scope?: "m01" | "m02"; onApply: (cover: LineCover | null) => void; /** 모달 머리 단추 자리(닫기 왼쪽 · 748-1) — 주면 [적용] 이 거기로 */ head?: HTMLElement; } const num = (raw: string): number | null => { const v = Number(raw.replaceAll(",", "").trim()); return raw.trim() !== "" && Number.isFinite(v) && v >= 0 ? v : null; }; /** 고침 칸 — 수량 · 단가(들) · [적용] [되돌림] · 바뀐 칸만 고침으로 넘김 */ export function coverForm(o: CoverFormOptions): HTMLElement { const parts = Object.keys(o.prices) as CoverPart[]; const field = (label: string, raw: number | null, mine?: number) => { const now = raw === null ? null : Number(raw.toPrecision(10)); // 칸 글 = 유효 10 자리 · 손대지 않으면 고침 아님 const box = createInputField({ label, value: String(mine ?? now ?? ""), compact: true, type: "text", }); box.input.inputMode = "decimal"; return { box, now }; }; const qty = field(tc("Qty"), o.qty, o.cover?.qty); const prices = parts.map((k) => ({ k, ...field(parts.length > 1 ? COVER_PARTS[k] : tc("Price"), o.prices[k] ?? null, o.cover?.[k]), })); const error = createText("", "error"); error.hidden = true; const apply = (): void => { const next: LineCover = {}; let bad = false; const take = (f: { box: { input: HTMLInputElement }; now: number | null }, was?: number) => { const v = num(f.box.input.value); if (v === null) return f.box.input.value.trim() === "" ? undefined : ((bad = true), undefined); return v !== f.now || was !== undefined ? v : undefined; // 셈 값 그대로면 고침 아님 }; const q = take(qty, o.cover?.qty); if (q !== undefined) next.qty = q; for (const p of prices) { const v = take(p, o.cover?.[p.k]); if (v !== undefined) next[p.k] = v; } error.textContent = tc("Bad"); error.hidden = !bad; if (!bad) o.onApply(Object.keys(next).length ? next : null); }; const applyButton = createButton({ label: tc("Apply"), variant: "filled", size: 32, onClick: apply, }); o.head?.append(applyButton); const actions = el("div", { className: "m01lab-cover__actions", children: [ ...(o.head ? [] : [applyButton]), ...(o.cover ? [ createButton({ label: tc("Undo"), variant: "ghost", size: 32, onClick: () => o.onApply(null), }), ] : []), ], }); actions.hidden = !actions.childElementCount; return el("section", { className: "m01lab-cover", attrs: { "data-cover-form": "" }, children: [ el("strong", { text: tc("Head") }), createText(tc(o.scope === "m02" ? "NoteM02" : "Note"), "hint"), el("div", { className: "m01lab-cover__fields", children: [qty.box.root, ...prices.map((p) => p.box.root)], }), error, actions, ], }); }