Files
Aislo/M01_MasterData/LogicBuilder/M01_LB_Steps_Calc.ts
T

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),
],
});
}