diff --git a/M01_MasterData/M01_MasterData_UI_Test_A.ts b/M01_MasterData/M01_MasterData_UI_Test_A.ts index 0b9abe99..b2f08693 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 { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; +import { guideLines, plainReason, tt } from "./M01_MasterData_UI_Test_Text"; type Step = { kind: "job" } | { kind: "ask"; spec: LogicInput } | { kind: "pick" } | { kind: "result" }; @@ -81,7 +81,10 @@ function wizard(host: HTMLElement, one: LogicOne): void { frame( `${logic.원문번호} ${logic.이름}`, tt("Job_Note"), - [el("p", { text: `${tt("Job_Title")} · ${logic.결과단위}` })], + [ + el("p", { text: `${tt("Job_Title")} · ${logic.결과단위}` }), + el("ul", { children: guideLines("A").map((t) => el("li", { text: t })) }), + ], true, ); } else if (step.kind === "ask") drawAsk(step.spec); diff --git a/M01_MasterData/M01_MasterData_UI_Test_B.ts b/M01_MasterData/M01_MasterData_UI_Test_B.ts index 5ca6df62..482edaab 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 { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; +import { guideLines, plainReason, tt } from "./M01_MasterData_UI_Test_Text"; /* ── 원문 표(소요량·계수) 미리보기 — /elements 로 파일을 찾고 /table 로 통째 읽음 ── */ @@ -232,7 +232,12 @@ export function render(host: HTMLElement, logicKey: string): void { const swaps: Swaps = new Map(); const state: State = { one, - values: {}, + // 고르기 칸은 첫 값으로 시작 — 방식 C 와 같게(바로 계산이 돎) + values: Object.fromEntries( + (one.logic.입력 ?? []) + .filter((spec) => spec.고르기?.length) + .map((spec) => [spec.이름, String(spec.고르기?.[0])]), + ), answer: null, selected: 0, calcSeq: 0, @@ -322,6 +327,10 @@ function paint(host: HTMLElement, state: State): void { main.style.cssText = "flex:1 1 60%;min-width:0;"; main.append( el("h3", { text: `${row.원문번호} ${row.이름}` }), + el("ul", { + className: "m01-logic__muted", + children: guideLines("B").map((t) => el("li", { text: t })), + }), inputWrap, ...(hasPickable(state.one.logic) ? [ @@ -387,7 +396,35 @@ function buildHoTable(host: HTMLElement, state: State): HTMLElement { }), ], }), - el("tbody", { children: rows }), + el("tbody", { + children: [ + ...rows, + // 하위 로직이 여러 줄로 풀린 경우 — 호표 줄 수보다 많은 줄도 빠짐없이 보임 + ...(lines ?? []).slice((state.one.logic.호표 ?? []).length).map((calc) => + el("tr", { + children: [ + el("td", { text: calc.이름 }), + el("td", { text: "" }), + el("td", { text: `${formatNumber(calc.수량)} ${calc.단위}` }), + el("td", { className: "m01-logic__money", text: formatNumber(calc.금액) }), + ], + }), + ), + ...(state.answer?.ok && state.answer.sums + ? [ + el("tr", { + attrs: { "data-total": String(state.answer.sums["계"] ?? 0) }, + children: [ + el("td", { text: tt("Sum_Total") }), + el("td", { text: "" }), + el("td", { text: "" }), + el("td", { text: formatNumber(state.answer.sums["계"] ?? 0) }), + ], + }), + ] + : []), + ], + }), ], }); } diff --git a/M01_MasterData/M01_MasterData_UI_Test_C.ts b/M01_MasterData/M01_MasterData_UI_Test_C.ts index 99f5da94..c2ca1baf 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 { plainReason, tt } from "./M01_MasterData_UI_Test_Text"; +import { guideLines, plainReason, tt } from "./M01_MasterData_UI_Test_Text"; import "./M01_MasterData_UI_Test_C_Style.css"; const TEXT = { @@ -212,6 +212,10 @@ function mount(host: HTMLElement, one: LogicOne): void { ...(one.reasons.length ? [el("p", { className: "m01c__bad", text: one.reasons.join(" · ") })] : []), + el("ul", { + className: "m01c__muted", + children: guideLines("C").map((t) => el("li", { text: t })), + }), stopped, ...(hasPickable(row) ? [ diff --git a/M01_MasterData/M01_MasterData_UI_Test_Text.ts b/M01_MasterData/M01_MasterData_UI_Test_Text.ts index a3c80723..fc551b2c 100644 --- a/M01_MasterData/M01_MasterData_UI_Test_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_Test_Text.ts @@ -46,6 +46,26 @@ const TEXT = { "넣은 값에 맞는 표 줄이 없습니다 — 값을 바꿔 보세요", "No table row fits these values — try other values", ], + Stop_Spec: [ + "「{name}」 규격이 여럿이라 정하지 못함 — 위 「쓰이는 재료·기계」에서 하나를 골라 주세요", + "「{name}」 has several specs — pick one in the materials list above", + ], + Stop_NoPrice: [ + "「{name}」 단가가 자료에 비어 있어 셈하지 못함 — 자료를 채워야 함", + "「{name}」 has no price in the data yet", + ], + Guide_A: [ + "한 번에 하나씩 묻는 대로 답하면 끝에 결과 표가 나옴|값을 잘못 넣었으면 「이전」으로 돌아가 고침|재료·기계를 바꿔 보는 칸은 뒤쪽 단계에 있음", + "Answer one question at a time; the result table comes last|Use Back to fix a value|Swap materials in a later step", + ], + Guide_B: [ + "위쪽 칸에 값을 넣으면 아래 호표(재료·인력·기계 줄)의 수량과 금액이 바로 바뀜|줄을 누르면 오른쪽에 그 수량이 어디서 왔는지 나옴|맨 아래 「합계」가 최종 금액", + "Type values above and the table below updates at once|Click a row to see where its quantity comes from|The last row is the total", + ], + Guide_C: [ + "왼쪽 「설계 값」을 넣으면 오른쪽으로 수량 → 단가 → 합계 순으로 흐름이 이어짐|상자를 누르면 값과 출처가 펼쳐짐|빨간 글이 뜨면 그 까닭 그대로 값을 채우거나 바꿈", + "Enter design values on the left; quantity, price and total follow to the right|Click a box for details|Red text says what is missing", + ], Enter_Value: ["값을 넣어 주세요", "Enter a value"], Result_Title: ["결과", "Result"], Result_Stopped: ["계산이 멈춤", "Calculation stopped"], @@ -63,9 +83,17 @@ 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"); + const spec = /「([^」]+)」 후보 여럿/.exec(reason); + if (spec) return tt("Stop_Spec", { name: spec[1] }); + const noPrice = /「([^」]+)」 값 없음/.exec(reason); + if (noPrice) return tt("Stop_NoPrice", { name: noPrice[1] }); return reason; } +/** 방식 첫 화면 「이 방식은 이렇게 씀」 — 줄 목록 */ +export const guideLines = (id: "A" | "B" | "C"): string[] => + tt(`Guide_${id}` as TestTextKey).split("|"); + export function tt(key: TestTextKey, fill: Record = {}): string { const entry = TEXT[key]; const text: string = entry[currentLanguageIndex as 0 | 1] ?? entry[0];