/* ============================================================================= * M02_MasterTemplete_Sangeun_Equation.ts * M02 단가산출(SK) 「수식」 보기(PLAN 796-2b · 요구 655 · 656 · 657 · 요구서_655 0 장) — M01 수식 탭 부품(`createLabEquation`)을 페이지 갈래로. * 길 = `POST /layers/{층}/sangeun/equation`(+ `/preview`) · 몸 = 화면 문서(캐시) · 고침 목록 = 문서 `수식고침`(사본 위 기록) · * 저장 = 상세 [저장](서버가 다시 얹어 검사 · 셈) · 옵션 = 상세 옵션 값(`onInput`) * 위 줄 = 로직 사본 — 「M01 새 판 있음」 배지 · [단가 갱신] · [재생성](`…/sangeun/copy/price|regen` → 문서 `사본할일` · [저장] 때 서버 적용) * 가져온 식 = 「엑셀 원래」 금액 글(셈 답 `원래` = 엑셀 금액) * ========================================================================== */ import { createBadge } from "@ui/ui_template_badge"; import { createButton } from "@ui/ui_template_button"; import { el, showToast } from "@ui/ui_template_elements"; import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createText } from "@ui/ui_template_text"; import { createToolbar } from "@ui/ui_template_toolbar"; import { formatMoney } from "../common_util/common_util_money"; import type { LogicRow } from "../M01_MasterData/M01_MasterData_UI_Logic_Api"; import { createLabEquation } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Equation"; import type { EqEdit } from "../M01_MasterData/logic/M01_Logic_Equation_Model"; import { call, type Layer } from "./M02_MasterTemplete_Api_Fetch"; import type { SkAnswer, SkDoc } from "./logic/M02_Sangeun_Cover_Model"; const TEXT = { Copy: ["로직 사본", "Logic copy"], NewM01: ["M01 새 판 있음", "New M01 version"], Price: ["단가 갱신", "Refresh prices"], Regen: ["재생성", "Regenerate"], Job: ["{n} · [저장] 때 적용", "{n} · applied on save"], Excel: ["엑셀 원래", "Excel original"], Lost: ["못 얹은 고침", "Not re-applied"], } as const satisfies Record; const t = (k: keyof typeof TEXT, n = ""): string => (TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0]).replace("{n}", n); const KEY = "수식고침"; const JOB = "사본할일"; interface CopyMark { M01새판?: boolean; 지운키?: string[]; } type Answer = SkAnswer & { 사본?: CopyMark; 원래?: Record | null; 가져온?: unknown; }; interface CopyReply { 문서: SkDoc; 전: Record | null; 후: Record | null; 바뀐자원?: unknown[]; 못얹은?: { cid: string; 까닭: string }[]; } export interface SkEquationOptions { scope: { layer: Layer; owner: string | null }; edit: boolean; touch: () => void; onInput: (name: string, value: string) => void; } export interface SkEquationDraw { doc: SkDoc; ans: SkAnswer | null; base: LogicRow | null; values: Record; } export function createSangeunEquation(opt: SkEquationOptions) { const { layer, owner } = opt.scope; const q = owner && layer !== "system" ? `?owner=${encodeURIComponent(owner)}` : ""; const bar = el("div", { attrs: { "data-sk-copy": "" } }); let last: SkEquationDraw | null = null; const doc = (): SkDoc => last?.doc ?? ({} as SkDoc); /** 고침 = 저장되는 SK 만(일위대가 줄 모달 `줄모드` = 보기만 — 줄로 못 옮김) · 처음 그릴 때 정함 */ const make = (d: SkEquationDraw) => createLabEquation({ admin: opt.edit && d.doc.줄모드 !== true, blocked: () => false, onSaved: () => undefined, page: { ask: (suffix, body, signal) => call(`/layers/${layer}/sangeun/equation${suffix}${q}`, { method: "POST", body: JSON.stringify({ ...body, 문서: doc() }), signal, }), edits: () => (doc()[KEY] as EqEdit[] | undefined) ?? [], keep: (edits) => { if (edits.length) doc()[KEY] = edits; else delete doc()[KEY]; opt.touch(); }, values: () => last?.values ?? {}, setValue: (name, value) => opt.onInput(name, value ?? ""), }, }); let lab: ReturnType | null = null; const root = el("div", { attrs: { "data-sk-equation": "" }, children: [bar] }); async function job(how: "price" | "regen"): Promise { try { const got = await call(`/layers/${layer}/sangeun/copy/${how}${q}`, { method: "POST", body: JSON.stringify({ 문서: doc(), 입력값: last?.values ?? {} }), }); doc()[JOB] = got.문서[JOB]; const sum = (v: Record | null): string => formatMoney(v?.계 ?? 0); const lost = got.못얹은 ?? []; showToast( `${t(how === "price" ? "Price" : "Regen")} ${sum(got.전)} → ${sum(got.후)}` + (lost.length ? ` · ${t("Lost")} ${lost.map((x) => x.cid).join(" · ")}` : ""), lost.length ? "warning" : "success", ); opt.touch(); } catch (error) { showToast(error instanceof Error ? error.message : String(error), "error"); } } function drawBar(d: SkEquationDraw): void { const ans = d.ans as Answer | null; const mark = ans?.사본; const pending = d.doc[JOB] as string | undefined; const items: (HTMLElement | "divider" | "spacer")[] = []; if (ans && "가져온" in ans) items.push(createText(`${t("Excel")} ${formatMoney(ans.원래?.계 ?? 0)}`, "muted", 14)); if (mark) { items.push(createText(t("Copy"), "muted", 14)); if (mark.M01새판) items.push(createBadge(t("NewM01"), "warning")); if (pending) items.push(createBadge(t("Job", pending), "info")); if (opt.edit) { items.push( "spacer", createButton({ label: t("Price"), variant: "ghost", size: 24, onClick: () => void job("price"), }), createButton({ label: t("Regen"), variant: "ghost", size: 24, onClick: () => void job("regen"), }), ); } } bar.replaceChildren( ...(items.length ? [createToolbar({ items, size: 28, label: t("Copy") })] : []), ); } return { root, draw: (d: SkEquationDraw): void => { last = d; drawBar(d); if (!d.base) return; if (!lab) root.append((lab = make(d)).root); const { [KEY]: _edits, ...rest } = d.doc; lab.draw(String(d.doc.키 ?? ""), JSON.stringify([rest, d.values]), d.base); }, /** [저장] 뒤 — 할일 칸은 서버가 적용 · 뗌(상세 `getDoc` 이 복사한 뒤) */ saved: (): void => queueMicrotask(() => { if (!last?.doc[JOB]) return; delete last.doc[JOB]; drawBar(last); }), }; }