diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts index 9582a37b..471f8675 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Calc.ts @@ -8,6 +8,7 @@ import { createButton, createSelectField, el, showToast } from "@ui/ui_template_ import { runCalc, runText, + searchElements, type CalcAnswer, type CalcLine, type TextAnswer, @@ -16,6 +17,28 @@ import { import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; import { tx } from "./M01_MasterData_UI_Logic_Text"; +/** 고르기 값이 마스터 키(자재·기계·직종)면 이름으로 바꿔 보임 — 값 칸은 키 그대로 보냄 */ +const KEY_RE = /^[A-Z]{2}\d{6}$/; +const KEY_GROUP: Record = { MT: "재료", EQ: "기계", LB: "인력" }; +/** 로직마다 다시 그려도 남는 이름 캐시 — 같은 키를 두 번 묻지 않음 */ +const nameCache = new Map(); + +async function keyName(key: string): Promise { + if (nameCache.has(key)) return nameCache.get(key)!; + const group = KEY_GROUP[key.slice(0, 2)]; + if (!group) return null; + let name: string | null = null; + try { + const found = await searchElements(group, key); // q = 키 그대로 — 「키」 칸 부분일치라 그 줄만 걸림 + const hit = found.items.find((it) => it.ref === key); + name = hit ? `${hit.이름 ?? ""}${hit.규격 ? ` ${hit.규격}` : ""}`.trim() || null : null; + } catch { + name = null; + } + nameCache.set(key, name); + return name; +} + export interface CalcContext { /** 저장된 키 — 새 로직은 null */ savedKey: string | null; @@ -33,17 +56,31 @@ export interface CalcContext { const SUMS = ["노무비", "재료비", "경비", "계"]; export function buildCalc(host: HTMLElement, ctx: CalcContext): void { + // 그릇 폭 280px 고정 칸은 옛 테스트 컨테이너(UI_Test*, 걷힘)의 죽은 CSS 규칙 — + // 지금 쓰는 LogicLab 화면은 그 칸에 안 걸려 폭이 이미 넓음(실측 730px) · 따로 안 풂 + const optionText = (o: string | number): string => + (typeof o === "string" && nameCache.get(o)) || String(o); const fields = (ctx.row.입력 ?? []).map((spec) => { const name = spec.이름; let control: HTMLElement; - if (spec.고르기?.length) { + const picks = spec.고르기; + if (picks?.length) { const pick = createSelectField({ - options: ["", ...spec.고르기.map(String)].map((o) => ({ value: o, text: o })), + options: ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), value: ctx.values[name] ?? "", compact: true, onChange: (v) => (ctx.values[name] = v), }); control = pick.root; + const keys = picks.filter((o): o is string => typeof o === "string" && KEY_RE.test(o)); + if (keys.length) { + void Promise.all(keys.map(keyName)).then(() => { + pick.setOptions( + ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })), + ctx.values[name] ?? "", + ); + }); + } } else { const box = el("input", { className: "m01-logic__input", @@ -55,8 +92,10 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { control = box; } const label = spec.단위 ? `${name} (${spec.단위})` : name; + // 긴 이름 줄만 한 칸 다 씀 — 고르기 줄은 짧으면 그대로 2열에 맞춰 나란히 + const wide = label.length > 10; return el("label", { - className: "m01-logic__field", + className: `m01-logic__field${wide ? " m01-logic__field--wide" : ""}`, children: [el("span", { text: label }), control], }); }); @@ -89,7 +128,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void { host.replaceChildren( el("h3", { text: tx("Calc_Title") }), ...(fields.length - ? fields + ? [el("div", { className: "m01-logic__head", children: fields })] // 요소 표가 쓰는 auto-fill 그리드 재사용 — 2열 : [el("p", { className: "m01-logic__muted", text: tx("Calc_NoInputs") })]), createButton({ label: tx("Calc_Run"), onClick: () => void run() }), out,