wip(M01): 테스트 컨테이너 후보 값 다섯 표시 · 옛 줄 검색을 품셈재료 이름으로 · 멈춤 사유 쉬운 말 · 빈 입력 안내

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-20 22:39:17 +09:00
co-authored by Claude Sonnet 5
parent 31654138da
commit dfad8bffeb
5 changed files with 46 additions and 15 deletions
+3 -3
View File
@@ -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) }),
],
}),
];
+6 -3
View File
@@ -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<typeof setTimeout> | 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) }),
],
}),
);
}
+3 -3
View File
@@ -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;
+14 -4
View File
@@ -37,14 +37,24 @@ export function swappedRow(logic: LogicRow, swaps: Swaps): LogicRow | undefined
};
}
type Brief = ElementBrief & { 구분?: string; 상세구분?: string };
type Brief = ElementBrief & {
구분?: string;
상세구분?: string;
값들?: Record<string, unknown>;
};
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) => {
+20 -2
View File
@@ -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, string | number> = {}): string {
const entry = TEXT[key];
const text: string = entry[currentLanguageIndex as 0 | 1] ?? entry[0];