/* ============================================================================= * M02_MasterTemplete_Labor_RowModal.ts * 일위대가표 줄 누름 모달(PLAN 723-2 · 723-3 · 요구서_내역16 2장 4 · 5) — 일위대가표에서 빠진 줄 동작을 모달 안으로. * · 로직 줄(단가산출 문서 없음) · 셈 주입 SK 줄 = 머리(이름 · 「1 ㎡ 당」) · * 단가산출 상세 줄 모드(717-2b · `Labor_RowSk` · 보기 고르기 = 모달 머리 · 옛 [항목 추가] · [율 줄 추가] 없음) · * 로직 자재(바꾸기 · 기본 · 세 칸 · `Labor_LogicMats`) * · [적용] = 그 줄 덮개(M02 변경 항목) + 옵션(상세 옵션 칸 → 그 줄 입력값) * · 808-1(요구 701-4 · 5) — 옛 [옵션] · [단가산출로도 저장] · [항목 추가] · [율 줄 추가] 걷음 · 일위대가 줄 = 저장 때 * M01 로직 줄 + 고침으로 펼쳐 담김(740-1 · `본뜬` = 온 단가산출) → 수식 문서 없음 = 수식 탭 · 보기 고르기 없이 단가산출 상세만 + * 안내(온 단가산출 이름 · 수식은 그 단가산출에서 고쳐 다시 담음) * · 사용자 자재 줄 = 자재 모달(세 칸 · `Labor_MatTable` 한 줄) * · 고침 = 그 줄 화면 캐시(저장은 [저장]) · 금액은 표가 다시 셈 → `watch` 로 받아 다시 그림 * ========================================================================== */ import { createBanner } from "@ui/ui_template_banner"; import { createButton, el, showToast } from "@ui/ui_template_elements"; import { askConfirm, openModal } from "@ui/ui_template_modal"; import { createText } from "@ui/ui_template_text"; import type { ItemLogic } from "./M02_MasterTemplete_Api_Fetch"; import type { LaborCalc, LaborMat, LaborRow, MatMarkKey } from "./M02_MasterTemplete_Labor_Api"; import { logicMatRows } from "./M02_MasterTemplete_Labor_LogicMats"; import { setLogicMark, setMark } from "./M02_MasterTemplete_Labor_MatMarks"; import { matTable } from "./M02_MasterTemplete_Labor_MatTable"; import { mountRowSk, type RowSkHandle } from "./M02_MasterTemplete_Labor_RowSk"; import { tc } from "./M02_MasterTemplete_Labor_Text"; import type { SkScope } from "./M02_MasterTemplete_Sangeun_Cover"; import { releaseTied } from "./M02_Labor_LogicMats_Model"; import { keepKnown, valueLine } from "./M02_Labor_Values_Model"; /** 표가 다시 센 답을 받음 — 돌려준 함수 = 그만 받음 */ export type CalcWatch = (fn: (calc: LaborCalc | null) => void) => () => void; export interface RowModalOptions { row: LaborRow; logic: ItemLogic | null; title: string; /** 일위대가 머리(이름 · 「1 ㎡ 당」) */ head: string; /** 줄 분모(SK 단위 기본) */ unit: string; scope: SkScope; readOnly: boolean; /** 셈 주입(프로젝트 복사본) — 단가산출 상세 없음 */ inject: boolean; /** 사용자 자재 코드 — 로직 자재 [바꾸기]로 같은 품목 막음 */ userMats: string[]; watch: CalcWatch; /** 고침 뒤 — `again` = 서버 다시 셈(입력 · 고름 · 별도계상) · 아니면 화면만 */ done: (again: boolean) => void; } const TEXT = { Apply: ["적용", "Apply"], NoDoc: [ "M01 로직을 바로 쓴 줄 — 수식 탭은 단가산출 문서가 있어야 열림(단가산출을 만들어 담음) · 여기서는 단가산출 상세만", "Uses the M01 logic directly — the equation tab needs a build-up document", ], FromSk: [ "단가산출 「{k}」 에서 담아 온 줄(담을 때 펼침) — 수식은 그 단가산출에서 고쳐 다시 담음 · 여기서는 단가산출 상세만", "Copied from build-up 「{k}」 — edit its equation there and re-add", ], Discard: ["적용 안 한 고침이 있음 — 버리고 닫을까?", "Discard unapplied edits?"], } as const; const t = (k: keyof typeof TEXT): string => TEXT[k][0]; export function openRowModal(o: RowModalOptions): void { const row = o.row; const edit = !o.readOnly; const info = el("div", { attrs: { "data-row-head": "" } }); const mats = el("div", { attrs: { "data-row-mats": "" } }); let calc: LaborCalc | null = null; const line = () => calc?.줄.find((l) => !row.참조 && l.로직 === row.로직); const draw = (): void => { const values = valueLine(o.logic?.입력 ?? [], row.입력값); info.replaceChildren( createText(o.head, "muted", 14), ...(values.text ? [createText(values.text)] : []), ...(values.empty ? [createText(tc("Labor_Empty").replace("{n}", String(values.empty)), "error")] : []), ...(row.켜쌓기율 != null ? [createText(tc("Labor_Layer").replace("{n}", String(row.켜쌓기율)), "muted")] : []), ...(row.메모 ? [createText(row.메모, "muted")] : []), ); const lead = row.참조 ? [] : logicMatRows([row], calc, !calc, { open: () => undefined, ...(edit ? { swap: (_r: number, at: number, key: string) => { if (o.userMats.includes(key)) return showToast(tc("Labor_AlreadyMat").replace("{value}", key), "error"); row.고름 = { ...row.고름, [String(at)]: key }; o.done(true); }, reset: (_r: number, at: number) => { if (!row.고름) return; delete row.고름[String(at)]; if (!Object.keys(row.고름).length) delete row.고름; o.done(true); }, mark: (_r: number, at: number, key: MatMarkKey, v: string) => { setLogicMark(row, at, key, v); o.done(key === "별도계상"); draw(); }, } : {}), }); mats.replaceChildren( ...(lead.length ? [el("h4", { text: tc("Labor_Mats") }), matTable({ mats: [], calc, lead })] : []), ); }; /** 옵션 → 그 줄 입력값(로직 줄만 · 아는 입력만) · 같으면 참 */ function setValues(next: Record): boolean { if (row.참조) return true; const values = o.logic ? keepKnown(o.logic.입력, next).kept : next; if (JSON.stringify(values) === JSON.stringify(row.입력값)) return true; const before = row.입력값; row.입력값 = values; releaseTied(row, before, line()?.재료 ?? []); // 바뀐 입력에 걸린 고름 = 기본(103-1) return false; } // 단가산출 상세 줄 모드(723-3) — 셈 주입(프로젝트 복사본)엔 없음 · 보기 고르기 = 모달 머리(808-1) const slot = el("span", { className: "m02-main__tools" }); const skHost = el("div", { attrs: { "data-row-sk-host": "" } }); let sk: RowSkHandle | null = null; const skMount = (): void => { if (o.inject) return; sk?.destroy(); sk = mountRowSk(skHost, { row, scope: o.scope, title: o.title, unit: o.unit, readOnly: !edit, slot, }); }; let close = (): void => {}; const finish = (): void => { stop(); sk?.destroy(); }; const apply = createButton({ label: t("Apply"), variant: "filled", onClick: () => { const got = sk?.take() ?? null; if (got) { if (got.patch) row.패치 = got.patch; else delete row.패치; setValues(got.values); o.done(true); draw(); } finish(); // [적용] = 그 줄에 남기고 닫음(저장은 페이지 [저장]) · 코드 닫기는 beforeClose 안 거침 close(); }, }); // 수식 문서 없는 줄(808-1) — 안내 한 줄(온 단가산출 = `본뜬`) const from = row.본뜬 && typeof row.본뜬.이름 === "string" ? row.본뜬.이름 : ""; const noDoc = !row.참조 && !o.inject ? createBanner({ tone: "info", text: from ? t("FromSk").replace("{k}", from) : t("NoDoc"), }) : null; apply.dataset.rowApply = ""; const stop = o.watch((c) => { calc = c; draw(); }); const tools = edit && !o.inject ? [apply] : []; ({ close } = openModal({ title: o.title, closeLabel: tc("In_Close"), width: "1100px", wide: true, actions: row.참조 ? [slot, ...tools] : tools, // 로직 줄 = 보기 고르기 없음(상세만) beforeClose: async () => { if (sk?.take() && !(await askConfirm({ message: t("Discard"), danger: true }))) return false; finish(); return true; }, mount: (body) => body.append(info, ...(noDoc ? [noDoc] : []), skHost, mats), })); draw(); skMount(); } export interface MatModalOptions { mat: LaborMat; watch: CalcWatch; readOnly: boolean; /** 세 칸 고침 뒤 — `money` = 별도계상(금액에 닿음) */ done: (money: boolean) => void; } /** 사용자 자재 줄 — 세 칸(148-4 · 222-1) · 가격 다섯 보기 */ export function openMatModal(o: MatModalOptions): void { const host = el("div", { attrs: { "data-row-mat": o.mat.코드 } }); let calc: LaborCalc | null = null; const draw = (): void => host.replaceChildren( matTable( { mats: [o.mat], calc }, o.readOnly ? { open: () => undefined } : { open: () => undefined, mark: (_i, key, v) => { setMark(o.mat, key, v); o.done(key === "별도계상"); draw(); }, }, ), ); const stop = o.watch((c) => { calc = c; draw(); }); openModal({ title: `${o.mat.코드} ${o.mat.이름 ?? ""}`.trim(), closeLabel: tc("In_Close"), width: "1100px", beforeClose: () => (stop(), true), mount: (body) => body.append(host), }); draw(); }