/* ============================================================================= * M01_MasterData_UI_Logic_CalcPick.ts * 시험 계산의 재료 · 기계 고르기 — 재료 줄마다 「잡힌 품목 + 품목 고르기」 · 기계 입력은 단추 하나 * 누르면 마스터 표 모달(`PickTable`) · 고른 재료는 `고름`(호표 줄 차례 → 키)에 실어 다시 계산 · 저장 안 함 * ========================================================================== */ import { createButton, el } from "@ui/ui_template_elements"; import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch"; import type { CalcLine, ElementBrief, LogicInput, LogicRow } from "./M01_MasterData_UI_Logic_Api"; import { openPickTable } from "./M01_MasterData_UI_Logic_PickTable"; import { tx } from "./M01_MasterData_UI_Logic_Text"; import { condText, headWord, type PickCond } from "./M01_MasterData_UI_LogicLab_Modal"; interface Chosen { ref: string; label: string; } /** 로직마다 넣은 값(`values`)에 딸린 고른 재료 — 다시 그려도 남고 서버에는 계산 요청에만 실림 */ const chosen = new WeakMap, Map>(); const picksOf = (values: Record): Map => { let mine = chosen.get(values); if (!mine) chosen.set(values, (mine = new Map())); return mine; }; /** 계산 요청 몸의 `고름` — 고른 것이 없으면 안 실음 */ export function pickBody(values: Record): { 고름?: Record } { const mine = picksOf(values); return mine.size ? { 고름: Object.fromEntries([...mine].map(([i, c]) => [String(i), c.ref])) } : {}; } const nameOf = (row: Row): string => `${row["이름"] ?? ""} ${row["규격"] ?? ""}`.trim(); const MATERIAL_KEYS = /^MT\d{6}$/; const MACHINE_CODE = /^\d{4}-\d{4}$/; const MACHINE_KEY = /^EQ\d{6}$/; /** 재료 키 목록 입력 — 표 모달로 바뀌어 걷음 */ export const isMaterialKeyInput = (spec: LogicInput): boolean => !!spec.고르기?.length && spec.고르기.every((o) => MATERIAL_KEYS.test(String(o))); const isMachineInput = (spec: LogicInput): boolean => !!spec.고르기?.length && spec.고르기.every((o) => MACHINE_CODE.test(String(o)) || MACHINE_KEY.test(String(o))); const pickButton = (label: string, onClick: () => void): HTMLButtonElement => createButton({ label, variant: "ghost", onClick }); const line = (label: string, current: HTMLElement, button: HTMLElement): HTMLElement => el("div", { className: "m01-logic__field m01-logic__field--wide", children: [el("span", { text: label }), el("div", { children: [current, button] })], }); /** 재료 줄마다 한 칸 — 지금 잡힌 품목 + 품목 고르기 */ export function materialRows( row: LogicRow, prices: Record, values: Record, again: () => void, ): HTMLElement[] { const mine = picksOf(values); return (row.호표 ?? []) .map((item, i) => ({ item, i })) .filter((x) => x.item.종류 === "재료") .map(({ item, i }) => { const cond = typeof item.요소 === "object" && item.요소 !== null ? (item.요소 as unknown as PickCond) : null; const brief = (cond ? prices[condText(cond)] : prices[item.요소]) ?? null; const named = item.이름 && !/^[A-Z]{2}\d{6}/.test(item.이름) ? item.이름 : ""; const label = named || (brief?.이름 as string | undefined) || ""; const held = mine.get(i); const nowName = held?.label ?? (brief ? `${brief.이름 ?? ""} ${brief.규격 ?? ""}`.trim() : "") ?? ""; const seeds = [cond?.["검색어"], cond?.이름, cond?.상세구분, cond?.구분, label] .map(headWord) .filter((w, k, all) => w && all.indexOf(w) === k); const current = el("span", { className: "m01-logic__muted", text: `${nowName || tx("Mat_NoPrice")} `, attrs: { "data-mat-idx": String(i), "data-ref": brief?.ref ?? "" }, }); const button = pickButton(tx("Pick_Item"), () => openPickTable({ kind: "재료", title: `${tx("Pick_MatTitle")} · ${label}`, seed: seeds[0] ?? "", alt: seeds.slice(1), isCurrent: (r) => String(r["키"]) === (mine.get(i)?.ref ?? current.dataset.ref), onPick: (r) => { mine.set(i, { ref: String(r["키"]), label: nameOf(r) }); current.textContent = `${nameOf(r)} `; again(); }, }), ); return line(`${label}${item.규격 ? ` ${item.규격}` : ""}`, current, button); }); } /** 계산 답의 `품목` 을 잡힌 품목 글로 — 고른 재료가 있는 줄은 그대로 */ export function showItems( host: HTMLElement, values: Record, lines: CalcLine[], ): void { const mine = picksOf(values); host.querySelectorAll("[data-mat-idx]").forEach((span) => { const i = Number(span.dataset.matIdx); const item = lines[i]?.품목; if (!item || mine.has(i)) return; span.dataset.ref = item.키; span.textContent = `${`${item.이름 ?? ""} ${item.규격 ?? ""}`.trim() || item.키} `; }); } /** 기계 입력 칸 — 표 모달로 고르면 입력값(원문 분류번호 · 키 목록이면 키) */ const machines = new Map(); async function machineRow(code: string): Promise { if (!machines.has(code)) { let found: Row | null = null; try { const got = await fetchRows("기계.json", 1, 5, code); found = got.rows.find((r) => r["원문번호"] === code || r["키"] === code) ?? null; } catch { found = null; } machines.set(code, found); } return machines.get(code) ?? null; } export function machineControl( spec: LogicInput, values: Record, again: () => void, ): HTMLElement | null { if (!isMachineInput(spec)) return null; const current = el("span", { className: "m01-logic__muted" }); const paint = (): void => { const code = values[spec.이름] ?? ""; const known = machines.get(code); current.textContent = `${code ? `${code}${known ? ` · ${nameOf(known)}` : ""}` : tx("Mat_NoPrice")} `; if (code && !machines.has(code)) void machineRow(code).then(paint); }; paint(); const button = pickButton(tx("Pick_Machine"), () => { const code = values[spec.이름] ?? ""; const known = machines.get(code); openPickTable({ kind: "기계", title: `${tx("Pick_Machine")} · ${spec.이름}`, seed: String(known?.["이름"] ?? spec.이름), isCurrent: (r) => !!code && (r["원문번호"] === code || r["키"] === code), onPick: (r) => { const useKey = MACHINE_KEY.test(String(spec.고르기?.[0])); const value = String(useKey ? r["키"] : r["원문번호"]); machines.set(value, r); values[spec.이름] = value; paint(); again(); }, }); }); return el("div", { children: [current, button] }); }