Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
202 lines
6.4 KiB
TypeScript
202 lines
6.4 KiB
TypeScript
/* =============================================================================
|
|
* 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<typeof b>): 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<LbCalc>): 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),
|
|
],
|
|
});
|
|
}
|