feat(M01): 로직 개선 시험 텍스트 수식 두 칸 — 왼쪽 변수 이름 식 · 오른쪽 값 식 · 짝 번호 줄 맞춤과 밝히기 (PLAN 5-1)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-22 00:00:50 +09:00
co-authored by Claude Sonnet 5
parent 29fad2057c
commit c4a5087fb1
4 changed files with 94 additions and 13 deletions
@@ -0,0 +1,46 @@
/* M01 로직 개선 시험 — 텍스트 수식 두 칸(왼쪽 이름 식 · 오른쪽 값 식) */
.m01lab-fx {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: var(--spacing-12);
min-width: 0;
}
.m01lab-fx__head,
.m01lab-fx__row {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
.m01lab-fx__head {
padding-bottom: var(--spacing-4);
border-bottom: 1px solid var(--color-border);
}
.m01lab-fx__wide {
grid-column: 1 / -1;
}
.m01lab-fx__row {
cursor: pointer;
border-radius: var(--radius-lg);
}
.m01lab-fx__row.is-on,
.m01lab-fx__row.is-pin {
background: color-mix(in srgb, gold 35%, transparent);
}
.m01lab-fx__cell {
min-width: 0;
padding: var(--spacing-4);
}
@media (max-width: 600px) {
.m01lab-fx,
.m01lab-fx__head,
.m01lab-fx__row {
grid-template-columns: minmax(0, 1fr);
}
}
@@ -1,7 +1,8 @@
/* =============================================================================
* M01_MasterData_UI_LogicLab_Formula.ts
* 「텍스트 수식」 컨테이너 — 서버(`POST /text`)가 준 줄을 그대로 찍음(화면에서 다시 계산하지 않음)
* 비목 묶음 제목 · 줄(`이름 : 글`) · 소계 · 끝수 한 마디 · 까닭 있는 줄은 옅게
* 두 칸 — 왼쪽 = 변수 이름 식(`이름글`) · 오른쪽 = 값이 들어간 식(`글`) · 줄은 `짝` 번호로 같은 높이
* 한쪽에 올리거나 누르면 같은 줄이 밝아짐 · 비목 제목 · 소계 · 끝수 한 마디 · 까닭 있는 줄은 옅게
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
@@ -9,6 +10,7 @@ import type { TextAnswer } from "./M01_MasterData_UI_Logic_Api";
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
import { tx } from "./M01_MasterData_UI_Logic_Text";
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
import "./M01_MasterData_UI_LogicLab_Formula.css";
export interface FormulaContext {
text: TextAnswer | null;
@@ -31,30 +33,57 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
);
return;
}
const heads = el("div", {
className: "m01lab-fx__head",
children: [
el("strong", { text: tl("Formula_Left") }),
el("strong", { text: tl("Formula_Right") }),
],
});
const rows: HTMLElement[] = [];
const light = (pair: string, on: boolean): void =>
rows.forEach((r) => r.dataset.pair === pair && r.classList.toggle("is-on", on));
const blocks = text.groups.flatMap((g) => [
el("h4", { className: "m01lab__group", text: g.비목 }),
...g..map((line) =>
el("div", {
className: `m01lab__text${line. ? " m01lab__text--off" : ""}`,
attrs: { "data-text-line": line. },
el("h4", { className: "m01lab__group m01lab-fx__wide", text: g.비목 }),
...g..map((line, i) => {
const pair = String(line. ?? `${g.}${i}`);
const row = el("div", {
className: `m01lab-fx__row m01lab__text${line. ? " m01lab__text--off" : ""}`,
attrs: { "data-text-line": line., "data-pair": pair },
children: [
el("div", { text: `${line.} : ${line.}` }),
...(line. ? [el("div", { className: "m01-logic__muted", text: line.까닭 })] : []),
el("div", { className: "m01lab-fx__cell", text: line.이름글 ?? line. }),
el("div", {
className: "m01lab-fx__cell",
children: [
el("div", { text: `${line.} : ${line.}` }),
...(line. ? [el("div", { className: "m01-logic__muted", text: line.까닭 })] : []),
],
}),
],
}),
),
});
row.addEventListener("mouseenter", () => light(pair, true));
row.addEventListener("mouseleave", () => light(pair, false));
row.addEventListener("click", () => {
const pinned = row.classList.toggle("is-pin");
rows.forEach((r) => r !== row && r.classList.remove("is-pin"));
if (!pinned) row.classList.remove("is-pin");
});
rows.push(row);
return row;
}),
el("p", {
className: "m01lab__subtotal-text",
className: "m01lab__subtotal-text m01lab-fx__wide",
attrs: { "data-text-sub": String(g.) },
text: `${g.} ${tl("Subtotal")} ${formatNumber(g.)}${g. ? ` (${g.})` : ""}`,
}),
]);
blocks.unshift(heads);
blocks.push(
el("p", {
className: "m01lab__total",
className: "m01lab__total m01lab-fx__wide",
attrs: { "data-text-sum": String(text.) },
text: `${tl("Total")} ${formatNumber(text.)}`,
}),
);
host.replaceChildren(...blocks);
host.replaceChildren(el("div", { className: "m01lab-fx", children: blocks }));
}
@@ -45,6 +45,8 @@ const TEXT = {
],
Click_Hint: ["줄을 누르면 그 줄이 쓰는 자료가 뜸", "Click a line to see the data it uses"],
Modal_Close: ["닫기", "Close"],
Formula_Left: ["변수 이름 식", "Formula with names"],
Formula_Right: ["값이 들어간 식", "Formula with values"],
Modal_Sum: ["한 줄 풀이", "In one line"],
Pill_Hint: ["쓰인 자료 — 누르면 펼침", "Data used — tap to open"],
Modal_UnitPrice: ["단가", "Unit price"],
@@ -100,6 +100,10 @@ export type CalcAnswer =
export interface TextLine {
이름: string;
: string;
/** 같은 줄을 변수 이름으로 적은 식 — 왼쪽 칸 */
이름글?: string;
/** 줄 번호(답 전체에서 하나씩) — 두 칸 줄 맞춤 · 밝히기 */
짝?: number;
금액: number;
까닭?: string;
}