From c4a5087fb1edef325e78f3d3643a805ce2254e71 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 22 Sep 2026 00:00:50 +0900 Subject: [PATCH] =?UTF-8?q?feat(M01):=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EC=8B=9C=ED=97=98=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=8B=9D=20=EB=91=90=20=EC=B9=B8=20=E2=80=94=20?= =?UTF-8?q?=EC=99=BC=EC=AA=BD=20=EB=B3=80=EC=88=98=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=8B=9D=20=C2=B7=20=EC=98=A4=EB=A5=B8=EC=AA=BD=20=EA=B0=92=20?= =?UTF-8?q?=EC=8B=9D=20=C2=B7=20=EC=A7=9D=20=EB=B2=88=ED=98=B8=20=EC=A4=84?= =?UTF-8?q?=20=EB=A7=9E=EC=B6=A4=EA=B3=BC=20=EB=B0=9D=ED=9E=88=EA=B8=B0=20?= =?UTF-8?q?(PLAN=205-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ --- .../M01_MasterData_UI_LogicLab_Formula.css | 46 ++++++++++++++++ .../M01_MasterData_UI_LogicLab_Formula.ts | 55 ++++++++++++++----- .../M01_MasterData_UI_LogicLab_Text.ts | 2 + M01_MasterData/M01_MasterData_UI_Logic_Api.ts | 4 ++ 4 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 M01_MasterData/M01_MasterData_UI_LogicLab_Formula.css diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.css b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.css new file mode 100644 index 00000000..3a69e809 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.css @@ -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); + } +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts index 12b14727..c021fae0 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Formula.ts @@ -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 })); } diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts index 92387f43..8d21e699 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab_Text.ts @@ -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"], diff --git a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts index b520087e..fc50d374 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_Api.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_Api.ts @@ -100,6 +100,10 @@ export type CalcAnswer = export interface TextLine { 이름: string; 글: string; + /** 같은 줄을 변수 이름으로 적은 식 — 왼쪽 칸 */ + 이름글?: string; + /** 줄 번호(답 전체에서 하나씩) — 두 칸 줄 맞춤 · 밝히기 */ + 짝?: number; 금액: number; 까닭?: string; }