fix(M01): 시험 계산 · 조합 미리 보기 견본 딱지 없앰 — 자동 채움 · 열자마자 계산은 그대로 (PLAN 7장 첫 줄)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-24 19:09:19 +09:00
co-authored by Claude Sonnet 5
parent 51af428b5c
commit f29dc14c9a
3 changed files with 5 additions and 30 deletions
@@ -9,7 +9,6 @@ import { fetchAuto, fetchLogic, type LogicInput } from "./M01_MasterData_UI_Logi
import { previewCombo, type Combo } from "./M01_MasterData_UI_Combo_Api";
import { tc, type ComboTextKey } from "./M01_MasterData_UI_Combo_Text";
import { formatMoney } from "./M01_MasterData_UI_Logic_Money";
import { tx } from "./M01_MasterData_UI_Logic_Text";
const COSTS = ["노무비", "재료비", "경비"] as const;
const LABEL: Record<(typeof COSTS)[number], ComboTextKey> = {
@@ -30,7 +29,6 @@ function inputCell(
input: LogicInput,
key: string,
values: Record<string, Record<string, string>>,
sample: Set<string>,
onEdit: () => void,
): HTMLElement {
const mine = (values[key] ??= {});
@@ -43,14 +41,8 @@ function inputCell(
const control = "select" in field ? field.select : field.input;
control.value = value;
control.setAttribute("data-input", `${key}|${input.이름}`);
const tag = sample.has(`${key}|${input.이름}`)
? el("span", { className: "m01-logic__tag", text: tx("Calc_Sample") })
: undefined;
if (tag) field.root.append(tag);
const edit = (): void => {
mine[input.이름] = control.value;
sample.delete(`${key}|${input.이름}`);
tag?.remove();
};
control.addEventListener("input", edit);
control.addEventListener("change", () => {
@@ -70,8 +62,7 @@ export function buildPreview(combo: () => Combo): PreviewHandle {
const values: Record<string, Record<string, string>> = {};
/** 서버가 받을 꼴 — 원래 숫자·고르기 값으로 되돌림 */
const inputs = new Map<string, LogicInput[]>();
/** 견본(자동값)으로 채운 칸 `로직|입력` — 사용자가 고치면 빠짐 · 로직마다 자동값은 한 번만 받음 */
const sample = new Set<string>();
/** 로직마다 자동값은 한 번만 받음 */
const asked = new Set<string>();
const form = el("div", { className: "m01-logic__stack" });
const out = el("div", {
@@ -159,7 +150,6 @@ export function buildPreview(combo: () => Combo): PreviewHandle {
const got = (auto as Record<string, unknown>)[i.이름];
if (got === undefined || got === null || (mine[i.이름] ?? "").trim() !== "") continue;
mine[i.이름] = String(got);
sample.add(`${row.로직}|${i.이름}`);
}
inputs.set(row.로직, one.logic.입력);
blocks.push(
@@ -170,9 +160,7 @@ export function buildPreview(combo: () => Combo): PreviewHandle {
className: "m01lab__cell--wide",
text: `${row.로직} ${one.logic.이름}`,
}),
...one.logic.입력.map((i) =>
inputCell(i, row.로직, values, sample, () => void run()),
),
...one.logic.입력.map((i) => inputCell(i, row.로직, values, () => void run())),
],
}),
);
+3 -15
View File
@@ -57,19 +57,13 @@ export interface CalcContext {
const SUMS = ["노무비", "재료비", "경비", "계"];
/** 로직마다 자동값을 한 번만 받음(다시 그려도 안 부름) · 견본으로 채운 입력 이름 — 사용자가 고치면 빠짐 */
/** 로직마다 자동값을 한 번만 받음(다시 그려도 안 부름) */
const autoAsked = new WeakSet<Record<string, string>>();
const sampled = new WeakMap<Record<string, string>, Set<string>>();
/** 로직을 열 때마다 새로 부르는 미리보기 — 늦게 온 응답이 새 로직을 덮지 않게 순번으로 거름 */
let previewSeq = 0;
export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
const marks = sampled.get(ctx.values) ?? new Set<string>();
const edited = (name: string, tag?: HTMLElement): void => {
marks.delete(name);
tag?.remove();
};
// 그릇 폭 280px 고정 칸은 옛 테스트 컨테이너(UI_Test*, 걷힘)의 죽은 CSS 규칙 —
// 지금 쓰는 LogicLab 화면은 그 칸에 안 걸려 폭이 이미 넓음(실측 730px) · 따로 안 풂
const optionText = (o: string | number): string =>
@@ -78,9 +72,6 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
const name = spec.이름;
let control: HTMLElement;
const picks = spec.고르기;
const tag = marks.has(name)
? el("span", { className: "m01-logic__tag", text: tx("Calc_Sample") })
: undefined;
if (picks?.length) {
const pick = createSelectField({
options: ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })),
@@ -88,7 +79,6 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
compact: true,
onChange: (v) => {
ctx.values[name] = v;
edited(name, tag);
},
});
control = pick.root;
@@ -110,7 +100,6 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
box.value = ctx.values[name] ?? "";
box.addEventListener("input", () => {
ctx.values[name] = box.value;
edited(name, tag);
});
control = box;
}
@@ -119,7 +108,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
const wide = label.length > 10;
return el("label", {
className: `m01-logic__field${wide ? " m01-logic__field--wide" : ""}`,
children: [el("span", { text: label }), ...(tag ? [tag] : []), control],
children: [el("span", { text: label }), control],
});
});
const out = el("div", { className: "m01-logic__calc-out" });
@@ -152,8 +141,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
.filter((n) => auto[n] !== undefined && auto[n] !== null && !(ctx.values[n] ?? "").trim());
if (!names.length || previewSeq !== mySeqAtOpen) return; // 값이 없거나 다른 로직이 이미 열림
for (const n of names) ctx.values[n] = String(auto[n]);
sampled.set(ctx.values, new Set([...marks, ...names]));
buildCalc(host, ctx); // 채운 값·견본 딱지를 새로 그리고 바로 계산(결과 · 텍스트 수식까지)
buildCalc(host, ctx); // 채운 값을 새로 그리고 바로 계산(결과 · 텍스트 수식까지)
host.querySelector("button")?.click();
});
}
@@ -60,7 +60,6 @@ const TEXT = {
Calc_Title: ["시험 계산", "Test calculation"],
Calc_Run: ["계산", "Calculate"],
Calc_NoInputs: ["받을 값 없음", "No inputs"],
Calc_Sample: ["견본", "Sample"],
Calc_Draft: ["고친 줄로 계산(저장 전)", "Using unsaved edits"],
Calc_Stopped: ["멈춤", "Stopped"],
Calc_Result: ["결과", "Result"],