diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts index 93252fc9..cbdc2b1b 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts @@ -101,6 +101,9 @@ interface Entry { /** 서버 조각으로 그린 단가 자리 · 수량 풀이 — 줄 모달이 씀(붙기 전엔 없음) */ unitFrag?: Fragment[]; piecesFrag?: Fragment[]; + /** 서버 요소조각의 첫 요소 — 자리 표시 든 줄이 입력으로 풀린 키 · 글 */ + unitRef?: string; + unitText?: string; } /** 재료 고르기 줄 — `요소` 가 글 대신 {구분·상세구분·규격·대표} 객체 */ @@ -236,6 +239,8 @@ function entries(ctx: DetailContext): Entry[] { open: (self) => openMaterialModal({ unitFrag: self.unitFrag, + unitRef: self.unitRef, + unitText: self.unitText, qtyFrag: self.piecesFrag, title: item.이름 ?? (cond ? condLabel(cond) : item.요소), expr: item.수량, @@ -304,7 +309,12 @@ function attachPieces(all: Entry[], ctx: DetailContext): void { const line = [own, ...costs].map((c) => found.get(`${c}|${e.idx}`)).find(Boolean); const pieces = line?.조각; e.qtyFrag = e.piecesFrag = pieces?.length ? toFragments(pieces, ctx) : []; - if (line?.요소조각?.length) e.unitFrag = toFragments(line.요소조각, ctx); + if (line?.요소조각?.length) { + e.unitFrag = toFragments(line.요소조각, ctx); + const first = line.요소조각.find((piece) => piece.kind === "요소"); + e.unitRef = first?.참조?.키; + e.unitText = first?.글; + } } } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts index 4af2d533..76c267e9 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Modal.ts @@ -64,6 +64,9 @@ export interface ModalTarget { values: Record; /** 서버 `요소조각` — 단가 자리(요소 · 로직 부르기)를 조각으로 · 없으면 로직 키만 알약으로 */ unitFrag?: Fragment[]; + /** 서버가 입력으로 푼 단가 요소 키 · 글 — 자리 표시(`{…}`) 든 줄이 씀 */ + unitRef?: string; + unitText?: string; /** 서버 수량 조각 — 한 줄 풀이를 조각으로(알약 포함) · 없으면 식 풀이 글 */ qtyFrag?: Fragment[]; } @@ -333,10 +336,31 @@ export function openMaterialModal(target: ModalTarget): void { rate.append(muted("…")); // 후보 목록이 오면 그 줄로 채움 — 100건 밖이면 키로 따로 찾아 옴 } else if (cond) { rate.append(muted(tl("Pill_NoPick"))); + } else if (element.includes("{")) { + // 자리 표시 든 요소 — 날글 대신 서버가 푼 키로 단가 · 못 풀면 입력 이름만 + const ref = target.unitRef; + if (!ref) { + const names = [...element.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).join(" · "); + rate.append(muted(`${target.unitText || names} — ${tl("Pill_Unresolved")}`)); + } else { + const show = (brief: ElementBrief | null): void => { + rate.replaceChildren( + brief + ? priceView(ref, brief) + : muted(`${target.unitText ?? ref} — ${tl("Pill_NoValue")}`), + ); + setSum(brief); + }; + rate.append(muted("…")); + const cached = target.prices[ref]; + if (cached) show(cached); + else + void searchElements(groupOf(ref), ref) + .then((got) => show(got.items.find((it) => it.ref === ref) ?? null)) + .catch(() => show(null)); + } } else if (element) { - rate.append( - muted(`${element} — ${tl(element.includes("{") ? "Pill_NoPick" : "Pill_NoValue")}`), - ); + rate.append(muted(`${element} — ${tl("Pill_NoValue")}`)); } body.replaceChildren( rate, diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts index 9288970a..3e6367ee 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts @@ -59,6 +59,7 @@ const TEXT = { "고르기 조건에 맞는 마스터 줄이 없음 — 단가를 못 찾음", "No master row matches the pick conditions", ], + Pill_Unresolved: ["입력이 비어 풀리지 않음", "Not resolved — input empty"], Pill_NoValue: ["관리자 값 미확보 — 마스터에 단가가 없음", "No price in master yet"], Modal_Qty: ["수량 식", "Quantity formula"], Modal_Price: ["단가 자료", "Price source"],