/* ============================================================================= * M02_MasterTemplete_Hopyo_Lost.ts * 단가산출 못옮김 잇기 창(PLAN 729-3 · 729-4 이음 · 요구서 내역19 2장 3 · 4) — 실무 블록을 SK 로 옮길 때 우리 M01 에 * 바로 못 옮긴 엑셀 줄(노임 · 기계 · 자재)을 줄마다 M01 요소로 이음 → SK 더한줄(엑셀 수량) · [뺌] = 놓음(까닭 남김). * · 고름 = 캐시 `m02-hopyo-lost:{층}:{주인}:{키}:{번}` `{id: 잇기}` — 서버는 부를 때마다 그 블록 잇기 전부로 다시 셈 * (앞 잇기 안 남음) → [잇기 만들기] 는 캐시 모두를 보냄 · 바탕 = 그 SK 문서 바탕(로직 · 입력값 · 환산 그대로) * · 줄마다 M01 요소 후보 셋 단추(746-2 · 서버 `후보`) — 누르면 그 줄에 이음 고름(찾기와 같은 길) * · 남은 못옮김 = 서버 답 그대로 표 · 0 이면 그 SK 를 쓰는 일위대가 줄이 이어짐(남으면 만들기 409) * 계산은 서버 — 화면은 고름 · 보이기만 · 저장은 [잇기 만들기] 누를 때만. * ========================================================================== */ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; import { createTable } from "@ui/ui_template_table"; import { createText } from "@ui/ui_template_text"; import { readTemplate } from "./M02_MasterTemplete_Api_Fetch"; import { openFindPicker } from "./M02_MasterTemplete_Find_Pick"; import { makeSangeun, type HopyoScope, type SangeunBuilt, type SangeunLink, type SangeunLost, } from "./M02_MasterTemplete_Hopyo_Api"; import { elemChoices } from "./M02_MasterTemplete_Hopyo_Compare"; const why = (error: unknown): string => (error instanceof Error ? error.message : String(error)); const NONE = "우리에 없음"; export const lostKey = (scope: HopyoScope, key: string, no: number): string => `m02-hopyo-lost:${scope.layer}:${scope.owner ?? ""}:${key}:${no}`; /** 캐시 잇기 `{id: 잇기}` */ export function loadLinks(k: string): Record { try { const got = JSON.parse(sessionStorage.getItem(k) ?? "{}") as unknown; return got && typeof got === "object" ? (got as Record) : {}; } catch { return {}; } } const saveLinks = (k: string, v: Record): void => sessionStorage.setItem(k, JSON.stringify(v)); /** 고른 것 글 — 키 또는 뺌 */ const linkText = (x: SangeunLink | undefined): string => !x ? "" : x.뺌 ? `${L("M02_LostDrop")} · ${x.뺌}` : (x.노임 ?? x.기계 ?? x.자재 ?? ""); export interface LostOptions { scope: HopyoScope; key: string; /** 블록 번(단산N) */ 번: number; /** 그 블록 SK 키 — 바탕을 읽음 */ sk: string; 이름?: string; lost: SangeunLost[]; /** 잇기 만든 뒤(서버 답) */ done?: (got: SangeunBuilt) => void; } /** 못옮김 잇기 창 */ export function openLost(opt: LostOptions): void { const k = lostKey(opt.scope, opt.key, opt.번); let lost = opt.lost; openModal({ title: `${L("M02_LostTitle")} — 단산${opt.번} ${opt.이름 ?? ""}`.trim(), closeLabel: L("M02_Close"), width: "960px", dialogClass: "m02-modal", mount: (body, _close, actions) => { const list = el("div", { attrs: { "data-lost": "" } }); const put = (id: string, link: SangeunLink | null): void => { const all = loadLinks(k); if (link) all[id] = link; else delete all[id]; saveLinks(k, all); draw(); }; const make = createButton({ label: L("M02_LostMake"), variant: "filled", onClick: () => void run(), }); make.dataset.lostMake = ""; actions.append(make); body.append(createText(L("M02_LostHint"), "hint", 14), list); async function run(): Promise { const links = Object.values(loadLinks(k)); if (!links.length) return showToast(L("M02_WizSkPickFirst"), "warning"); make.disabled = true; try { const doc = (await readTemplate(opt.scope.layer, "sangeun", opt.sk, null, opt.scope.owner)) .문서 as { 바탕?: { 로직?: string } | null; 입력값?: Record; 환산?: { 계수?: number } }; // prettier-ignore const pick = { ...(doc.바탕?.로직 ? { 로직: doc.바탕.로직, 입력값: doc.입력값 ?? {} } : {}), ...(doc.환산?.계수 ? { 환산: { 계수: doc.환산.계수 } } : {}), 잇기: links, }; const got = await makeSangeun(opt.scope, opt.key, { [opt.번]: pick }); lost = got.만듦.find((x) => x.번 === opt.번)?.못옮김 ?? []; showToast(L("M02_LostLeft").replace("{n}", String(lost.length)), lost.length ? "warning" : "success"); // prettier-ignore opt.done?.(got); draw(); } catch (error) { showToast(why(error), "error"); } finally { make.disabled = false; } } function draw(): void { const all = loadLinks(k); list.dataset.lost = String(lost.length); const rows = lost.map((x) => { const kind = x.갈래; const find = createButton({ label: kind === "기계" ? L("M02_LeftMachine") : L("M02_LeftFind"), size: 24, variant: "ghost", onClick: () => // 736-2 줄 갈래대로 탭 하나(노임 = 인력 · 기계 = 기계 · 자재 = 재료) → 이음 칸 `노임` · `기계` · `자재` openFindPicker({ layer: opt.scope.layer, owner: opt.scope.owner, title: `${x.이름} ${x.규격 ?? ""}`.trim(), kinds: [kind === "노임" ? "labor" : kind === "기계" ? "machine" : "material"], query: x.이름.split(/[\s(]/)[0] ?? "", logic: () => showToast(L("M02_GridLinkNoLogic"), "info"), element: (e) => put(x.id, { id: x.id, [kind]: e.코드 }), machine: (e) => put(x.id, { id: x.id, 기계: e.코드 }), }), }); const drop = createButton({ label: NONE, size: 24, variant: "ghost", onClick: () => put(x.id, { id: x.id, 뺌: NONE }), }); drop.title = L("M02_LostDropTip"); const clear = createButton({ label: L("M02_LostClear"), size: 24, variant: "link", onClick: () => put(x.id, null), }); return [ kind, x.이름, x.규격 ?? "", `${x.수량 ?? ""} ${x.단위 ?? ""}`.trim(), x.글 ?? "", elemChoices(x.후보 ?? [], (c) => put(x.id, { id: x.id, [kind]: c.키 })) ?? "", linkText(all[x.id]), el("div", { className: "ui-toolbar", children: [find, drop, ...(all[x.id] ? [clear] : [])] }), // prettier-ignore ]; }); list.replaceChildren( createTable( [ { label: L("M02_LeftKind"), width: "56px" }, { label: L("M02_SangeunColName") }, { label: L("M02_LeftSpec") }, { label: L("M02_SangeunColQty"), width: "90px" }, { label: L("M02_LeftWhy") }, { label: L("M02_ElemTitle") }, { label: L("M02_LeftPicked") }, { label: L("M02_LeftPick"), width: "220px" }, ], rows, { density: "data", empty: L("M02_LostZero") }, ), ); } draw(); }, }); }