diff --git a/M01_MasterData/M01_MasterData_UI_Test_A.ts b/M01_MasterData/M01_MasterData_UI_Test_A.ts index 8ca8a183..0b9abe99 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_A.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_A.ts @@ -15,7 +15,7 @@ import { } from "./M01_MasterData_UI_Logic_Api"; import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; import { hasPickable, pickPanel, swappedRow } from "./M01_MasterData_UI_Test_Pick"; -import { tt } from "./M01_MasterData_UI_Test_Text"; +import { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; type Step = { kind: "job" } | { kind: "ask"; spec: LogicInput } | { kind: "pick" } | { kind: "result" }; @@ -107,7 +107,7 @@ function wizard(host: HTMLElement, one: LogicOne): void { } else { const box = el("input", { className: "m01-test__input", - attrs: { type: "text", inputmode: "decimal" }, + attrs: { type: "text", inputmode: "decimal", placeholder: tt("Enter_Value") }, }); box.value = values[name] ?? ""; box.addEventListener("input", () => changed(box.value)); @@ -154,7 +154,7 @@ function resultView(answer: CalcAnswer): HTMLElement[] { className: "m01-test__reasons", children: [ el("strong", { text: tt("Result_Stopped") }), - el("div", { text: answer.reason }), + el("div", { text: plainReason(answer.reason) }), ], }), ]; diff --git a/M01_MasterData/M01_MasterData_UI_Test_B.ts b/M01_MasterData/M01_MasterData_UI_Test_B.ts index 262cdf21..5ca6df62 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_B.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_B.ts @@ -27,7 +27,7 @@ import { } from "./M01_MasterData_UI_Logic_Api"; import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; import { hasPickable, pickPanel, swappedRow, type Swaps } from "./M01_MasterData_UI_Test_Pick"; -import { tt } from "./M01_MasterData_UI_Test_Text"; +import { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; /* ── 원문 표(소요량·계수) 미리보기 — /elements 로 파일을 찾고 /table 로 통째 읽음 ── */ @@ -298,7 +298,7 @@ function paint(host: HTMLElement, state: State): void { } else { const box = el("input", { className: "m01-logic__input", - attrs: { type: "text", inputmode: "decimal" }, + attrs: { type: "text", inputmode: "decimal", placeholder: tt("Enter_Value") }, }); box.value = state.values[spec.이름] ?? ""; let timer: ReturnType | undefined; @@ -337,7 +337,10 @@ function paint(host: HTMLElement, state: State): void { main.append( el("div", { className: "m01-logic__reasons", - children: [el("strong", { text: "멈춤" }), el("div", { text: state.answer.reason })], + children: [ + el("strong", { text: "멈춤" }), + el("div", { text: plainReason(state.answer.reason) }), + ], }), ); } diff --git a/M01_MasterData/M01_MasterData_UI_Test_C.ts b/M01_MasterData/M01_MasterData_UI_Test_C.ts index 808410da..99f5da94 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_C.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_C.ts @@ -24,7 +24,7 @@ import { type FlowKind, } from "./M01_MasterData_UI_Test_C_Model"; import { hasPickable, pickPanel, swappedRow, type Swaps } from "./M01_MasterData_UI_Test_Pick"; -import { tt } from "./M01_MasterData_UI_Test_Text"; +import { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; import "./M01_MasterData_UI_Test_C_Style.css"; const TEXT = { @@ -169,7 +169,7 @@ function mount(host: HTMLElement, one: LogicOne): void { } rest.replaceChildren(...nodes); const reason = answer && !answer.ok ? answer.reason : ""; - stopped.textContent = reason ? `${tc("Stopped")} — ${reason}` : ""; + stopped.textContent = reason ? `${tc("Stopped")} — ${plainReason(reason)}` : ""; stopped.hidden = !reason; }; @@ -255,7 +255,7 @@ function inputColumn( className: "m01c__input", attrs: { type: "text", inputmode: "decimal" }, }); - if (spec.범위) box.placeholder = `${spec.범위[0]} ∼ ${spec.범위[1]}`; + box.placeholder = spec.범위 ? `${spec.범위[0]} ∼ ${spec.범위[1]}` : tt("Enter_Value"); box.value = values[spec.이름] ?? ""; box.addEventListener("input", () => { values[spec.이름] = box.value; diff --git a/M01_MasterData/M01_MasterData_UI_Test_Pick.ts b/M01_MasterData/M01_MasterData_UI_Test_Pick.ts index 3b46c80a..c587668b 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_Pick.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_Pick.ts @@ -37,14 +37,24 @@ export function swappedRow(logic: LogicRow, swaps: Swaps): LogicRow | undefined }; } -type Brief = ElementBrief & { 구분?: string; 상세구분?: string }; +type Brief = ElementBrief & { + 구분?: string; + 상세구분?: string; + 값들?: Record; +}; const distinct = (items: Brief[], key: "구분" | "상세구분"): string[] => [ ...new Set(items.map((i) => i[key]).filter((v): v is string => !!v)), ]; -const label = (it: Brief): string => - [`${it.이름 ?? ""} ${it.규격 ?? ""}`.trim(), formatNumber(it.값)].filter(Boolean).join(" · "); +/** 후보 글 — 값 열이 여럿이면 있는 열을 다 보임(계산은 그 가운데 낮은 값) */ +const label = (it: Brief): string => { + const cols = Object.entries(it.값들 ?? {}) + .filter(([, v]) => v !== null && v !== undefined) + .map(([k, v]) => `${k} ${formatNumber(v)}`); + const prices = cols.length ? cols : [formatNumber(it.값) || tt("Mat_NoPrice")]; + return [`${it.이름 ?? ""} ${it.규격 ?? ""}`.trim(), ...prices].join(" · "); +}; function pickRow(one: LogicOne, line: HoLine, i: number, swaps: Swaps, onChange: () => void) { const cond = @@ -109,7 +119,7 @@ function pickRow(one: LogicOne, line: HoLine, i: number, swaps: Swaps, onChange: region: "울진", }) : line.종류 === "재료" - ? searchPrice((line.이름 ?? "").split(/[\s(·]/)[0]) // 옛 줄 — 이름 첫 낱말로 자재품목(구분 붙음) + ? searchPrice(one.prices[line.요소 as string]?.이름 ?? (line.이름 ?? "").split(/[\s(·]/)[0]) // 옛 줄(고르기 조건 없음) — 품셈재료 이름 · 없으면 이름 첫 낱말 : searchElements(line.종류, line.이름 ?? ""); void found .then((r) => { diff --git a/M01_MasterData/M01_MasterData_UI_Test_Text.ts b/M01_MasterData/M01_MasterData_UI_Test_Text.ts index 80a3337a..a3c80723 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_Text.ts @@ -29,14 +29,24 @@ const TEXT = { Ask_Range: ["범위 {min} ∼ {max}", "Range {min} ∼ {max}"], Mat_Title: ["쓰이는 재료·기계", "Materials and machines"], Mat_Note: [ - "기본은 원문 그대로 — 바꿔 보면 결과가 달라짐(저장 안 함)", - "Default is the source as is — swapping only changes this trial", + "기본은 원문 그대로 — 바꿔 보면 결과가 달라짐(저장 안 함) · 값이 여럿이면 그중 낮은 값으로 셈", + "Default is the source as is — swapping only changes this trial; the lowest listed price is used", ], Mat_None: ["이 공종은 재료·기계를 따로 고르지 않습니다", "No materials or machines to pick"], Mat_Default: ["기본 (원문 그대로)", "Default (as in source)"], Pick_All: ["(전체)", "(All)"], Use_Of: ["이 표의 용도", "Used for"], Mat_Price: ["단가", "Unit price"], + Mat_NoPrice: ["단가 없음", "No price"], + Stop_Missing: [ + "「{name}」 값을 아직 안 넣었습니다 — 값을 넣으면 계산됩니다", + "「{name}」 is empty — enter it to calculate", + ], + Stop_NoRow: [ + "넣은 값에 맞는 표 줄이 없습니다 — 값을 바꿔 보세요", + "No table row fits these values — try other values", + ], + Enter_Value: ["값을 넣어 주세요", "Enter a value"], Result_Title: ["결과", "Result"], Result_Stopped: ["계산이 멈춤", "Calculation stopped"], Col_Name: ["이름", "Name"], @@ -48,6 +58,14 @@ const TEXT = { export type TestTextKey = keyof typeof TEXT; +/** 엔진이 멈춘 까닭을 설계자 말로 — 모르는 글은 그대로 */ +export function plainReason(reason: string): string { + const missing = /입력 「([^」]+)」 없음/.exec(reason); + if (missing) return tt("Stop_Missing", { name: missing[1] }); + if (/맞는 줄 0개/.test(reason)) return tt("Stop_NoRow"); + return reason; +} + export function tt(key: TestTextKey, fill: Record = {}): string { const entry = TEXT[key]; const text: string = entry[currentLanguageIndex as 0 | 1] ?? entry[0];