/* ============================================================================= * M01_LB_Steps_Calc.ts * 로직 만들기 판 문장 보기 ③ 계산 — 값 · 곱나눔 · 할증 · 조건 목록 · 최소/최대 · 올림 * ========================================================================== */ import { el } from "@ui/ui_template_elements"; import type { CondOp, CondRead, CondShape, CondSlot } from "../EasyCalc/M01_EasyCalc_Cond"; import { setCalc, type LbCalc } from "./M01_LB_Blocks"; import { chip, middleOf, note, numBox, refBox, selectBox, sentence, span, textBox, tx, valueNote, type Ctx, type TEXT, } from "./M01_LB_Steps_Cell"; const OPS: [CondOp, keyof typeof TEXT][] = [ ["==", "OpEq"], ["!=", "OpNe"], [">", "OpGt"], [">=", "OpGe"], ["<", "OpLt"], ["<=", "OpLe"], ]; function slotBox(ctx: Ctx, slot: CondSlot, put: (s: CondSlot) => void): HTMLElement { if (slot.kind === "num") return numBox(ctx, slot.value, (n) => put({ kind: "num", value: n })); if (slot.kind === "text") return textBox(ctx, slot.value, (v) => put({ kind: "text", value: v })); if (slot.kind === "input") { const options = ctx.lb.inputs.map((i) => ({ value: i.이름, text: i.이름 })); return selectBox(ctx, options, slot.name, (v) => put({ kind: "input", name: v }), tx("S1")); } return chip(tx(slot.kind === "table" ? "SlotTable" : "SlotCalc")); } function shapeView(ctx: Ctx, shape: CondShape, put: (s: CondShape) => void): HTMLElement { if (shape.type === "pick") { const items: (HTMLElement | string)[] = []; shape.items.forEach((s, i) => { if (i) items.push(tx("And")); items.push( slotBox(ctx, s, (v) => put({ ...shape, items: shape.items.map((x, j) => (j === i ? v : x)) }), ), ); }); const fn = [ { value: "최소", text: tx("Small") }, { value: "최대", text: tx("Big") }, ]; return sentence( ...items, selectBox(ctx, fn, shape.fn, (v) => put({ ...shape, fn: v as "최소" | "최대" }), tx("S3")), ); } if (shape.type === "round") { const fns = [ { value: "올림", text: tx("Up") }, { value: "버림", text: tx("Down") }, { value: "반올림", text: tx("Half") }, ]; return sentence( slotBox(ctx, shape.value, (v) => put({ ...shape, value: v })), tx("RoundA"), numBox(ctx, shape.places, (n) => put({ ...shape, places: n, hadPlaces: true })), tx("RoundB"), selectBox( ctx, fns, shape.fn, (v) => put({ ...shape, fn: v as "올림" | "버림" | "반올림" }), tx("S3"), ), ); } const ops = OPS.map(([value, key]) => ({ value, text: tx(key) })); const rows = shape.branches.map((b, i) => { const edit = (patch: Partial): void => put({ ...shape, branches: shape.branches.map((x, j) => (j === i ? { ...x, ...patch } : x)) }); return el("li", { className: "m01-lbsteps-branch", children: [ sentence( span("m01-lbsteps-order", String(i + 1)), slotBox(ctx, b.test.left, (v) => edit({ test: { ...b.test, left: v } })), tx("Subject"), slotBox(ctx, b.test.right, (v) => edit({ test: { ...b.test, right: v } })), selectBox( ctx, ops, b.test.op, (v) => edit({ test: { ...b.test, op: v as CondOp } }), tx("S3"), ), "→", slotBox(ctx, b.value, (v) => edit({ value: v })), ), ], }); }); const last = el("li", { className: "m01-lbsteps-branch", children: [ sentence( tx("Otherwise"), slotBox(ctx, shape.otherwise, (v) => put({ ...shape, otherwise: v })), ), ], }); return el("div", { className: "m01-lbsteps-shape", children: [ note(tx("Cond1")), el("ol", { className: "m01-lbsteps-branches", children: [...rows, last] }), ], }); } function placeText(ctx: Ctx, calc: LbCalc): string { if (calc.name !== null) return calc.name; const { part, index } = calc.at; if (part === "결과") return tx("PlaceResult"); const card = ctx.lb.doc.cards.find( (c) => c.source?.index === index && c.source.part === (part === "수량" ? "호표" : "덧줄"), ); return `${card?.name ?? ""} ${tx(part === "수량" ? "PlaceQty" : "PlaceRate")}`.trim(); } export function calcBody(ctx: Ctx, calc: LbCalc): HTMLElement { const put = (patch: Partial): void => ctx.commit(() => setCalc(ctx.lb, calc, patch)); const refAt = (i: number): HTMLElement => refBox(ctx, calc.parts[i], calc, (r) => put({ parts: calc.parts.map((p, j) => (j === i ? r : p)) }), ); if (calc.cond && calc.cond.blocks.length) { const read: CondRead = calc.cond; return el("div", { className: "m01-lbsteps-shapes", children: read.blocks.map((b, bi) => shapeView(ctx, b.shape, (shape) => put({ cond: { ...read, blocks: read.blocks.map((x, j) => (j === bi ? { ...x, shape } : x)) }, }), ), ), }); } if (calc.kind === "값" && calc.parts.length) return sentence(refAt(0)); if (calc.kind === "할증" && calc.parts.length >= 2) { return sentence(refAt(0), tx("Mark1"), refAt(1), tx("Mark2")); } if ((calc.kind === "곱나눔" || calc.kind === "더하기") && calc.parts.length) { const ops = calc.kind === "곱나눔" ? ["*", "/"] : ["+", "-"]; const glyph = (o: string): string => tx(o === "*" ? "Mul" : o === "/" ? "Div" : o === "+" ? "Plus" : "Minus"); const out: (HTMLElement | string)[] = [refAt(0)]; calc.parts.slice(1).forEach((_, i) => { const op = calc.ops?.[i] ?? ops[0]; const choices = ops.map((o) => ({ value: o, text: glyph(o) })); out.push( selectBox( ctx, choices, op, (v) => put({ ops: calc.parts .slice(1) .map((_, j) => (j === i ? v : (calc.ops?.[j] ?? ops[0])) as "*" | "/" | "+" | "-"), }), tx("S3"), ), refAt(i + 1), ); }); return sentence(...out); } return sentence(note(tx("Steps"))); } export function calcView(ctx: Ctx, calc: LbCalc, n: number): HTMLElement { return el("li", { className: "m01-lbsteps-row", children: [ sentence( span("m01-lbsteps-name", `${tx("Calc")}${n}`), chip(placeText(ctx, calc)), valueNote(middleOf(ctx, calc.name)), ), calcBody(ctx, calc), ], }); }