/* ============================================================================= * M02_MasterTemplete_Labor_Input.ts * 일위대가 입력 모달(39-3) — 단가산출 로직 줄을 누르면 그 로직 입력 전부(빈 칸 = 자동값) · 메모. * 수량은 이 모달에 없음(43-2) — 수량산출 칸 누름 = `Labor_QtyModal`(면적산출 시트를 모달 안에). * [확인] = 줄 값만 바꿈(화면 캐시) · 저장은 상세 [저장]. 값 칸 규칙 = `M02_Labor_Values`(한 벌). * ========================================================================== */ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; import type { ItemInput, ItemLogic } from "./M02_MasterTemplete_Api_Fetch"; import { tc } from "./M02_MasterTemplete_Labor_Text"; import { createValueControl } from "./M02_Labor_Values"; import { keepKnown, valueHint, type LaborValues } from "./M02_Labor_Values_Model"; import "./M02_Labor_Values.css"; import "./M02_MasterTemplete_Labor.css"; const fill = (text: string, value: string): string => text.replace("{value}", value); /** 면적산출 상세로 감 — 옮겨 갔으면 참(물음에서 취소 = 거짓) */ export type GoArea = () => Promise; function footer(readOnly: boolean, ok: () => void, close: () => void): HTMLElement { return el("div", { className: "m02-labin__foot", children: readOnly ? [createButton({ label: tc("In_Close"), variant: "ghost", onClick: close })] : [ createButton({ label: tc("In_Cancel"), variant: "ghost", onClick: close }), createButton({ label: tc("In_Ok"), variant: "filled", onClick: ok }), ], }); } export interface LaborInputResult { 입력값: LaborValues; 메모: string | null; } export interface LaborInputOptions { /** 「GF000219 메쌓기」 */ title: string; /** 로직 입력 목록(없으면 = 못 읽음 · 메모만) */ logic: ItemLogic | null; values: LaborValues; memo: string | null; readOnly: boolean; done: (next: LaborInputResult) => void; } /** 담은 로직 줄 입력 모달 */ export function openLaborInput(opt: LaborInputOptions): void { const inputs: ItemInput[] = opt.logic?.입력 ?? []; const { kept, lost } = keepKnown(inputs, opt.values); const draft: LaborValues = { ...kept }; const bad = new Set(); openModal({ title: fill(tc("In_Title"), opt.title), closeLabel: tc("In_Close"), dialogClass: "m02-modal m02-labin", mount: (body, close) => { const rows = inputs.map((input) => { const field = createValueControl(input, draft[input.이름] ?? null, (value, isBad) => { if (isBad) bad.add(input.이름); else bad.delete(input.이름); if (value === null) delete draft[input.이름]; // 고르기 · 범위 없는 칸도 수로 읽히면 수로 둠(M01 넣을 값과 같게) else draft[input.이름] = typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)) ? Number(value) : value; }); field.control.disabled = opt.readOnly; field.control.setAttribute("data-labor-input", input.이름); const hint = valueHint(input); return el("div", { className: "m02-inputval__row", children: [ el("span", { className: "m02-inputval__name", text: input.단위 ? `${input.이름} (${input.단위})` : input.이름, attrs: { title: [input.설명, hint].filter(Boolean).join(" · ") }, }), el("div", { className: "m02-inputval__cell", children: [field.control, field.error] }), ], }); }); const memo = el("input", { className: "ui-input m02-inputval__control", attrs: { type: "text", maxlength: "200", "data-labor-memo": "" }, }) as HTMLInputElement; memo.value = opt.memo ?? ""; memo.readOnly = opt.readOnly; const ok = (): void => { if (bad.size) return showToast(tc("In_Bad"), "error"); opt.done({ 입력값: { ...draft }, 메모: memo.value.trim() || null }); close(); }; body.append( el("div", { className: "m02-inputval", children: [ ...(lost.length ? [ el("p", { className: "m02-labin__lost", text: fill(tc("Labor_Lost"), lost.join(", ")), }), ] : []), ...(rows.length ? rows : [el("p", { className: "m02-labin__muted", text: tc("In_None") })]), el("div", { className: "m02-inputval__row", children: [ el("span", { className: "m02-inputval__name", text: tc("In_Memo") }), memo, ], }), ], }), footer(opt.readOnly, ok, close), ); }, }); }