Files
Aislo/M01_MasterData/M01_MasterData_UI_LogicLab_FlowView.ts
T
eomsangdonandClaude Opus 5.5 d2ab8ee8cc fix(m02-df): 변환 흐름 화면 흠 넷 — 변수 예시값 · 표 찾기 실패 같은 뜻 · 다른 로직 서버 값 · 합계 끝수
- 변환: 설계 값 변수 예시값 = 줄 시험입력 · 없으면 check_master.시험입력 기본(글 수 → 수) · totalRound = null(옛 계는 끝수 없음)
- Model CfLogic.totalRound(없음 = 기본 계금 1원 미만 버림 · null = 끝수 없음) · Round totalRoundFor · roundBy/roundText 가 null 받음 · Summary · M01 흐름 보기 합계가 그것을 씀
- Compute: 함수 결과 오류는 오류 값(「#N/A」 등)으로 넘김(화면 Eval 과 같음 · 받는 칸으로 번짐 · 옛 엔진은 그 줄을 비움) — 서버가 빈 칸 0 으로 계속 세던 차이 없앰
- 화면 값 물음(Entry_Detail queriesOf): 인력 · 기계 · 유가 조건 찾기도 자리 물음 · 재료 검색어 틀은 채운 글 · 다른 로직 입력은 서버 /logic-value 로 받은 설계 값째 셈(그 안 조건 찾기 · 단가까지) · Eval 은 서버 출력이 있으면 그것
- Values.one_value: 인력 · 기계 · 유가 자리 물음 = group_find
- 전수 대조 시험입력 1,414/1,472(96.1%) · 기본 입력 1,429(97.1%) — 남은 49 는 sub4 범위규칙 칸 · 검색어 찾기 · 재료 값 차이

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
2026-10-02 00:56:28 +09:00

191 lines
6.9 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_LogicLab_FlowView.ts
* 흐름 로직의 왼쪽 「지금 내용」 — ② 단가산출 상세(출력 상자를 비용 종류별 줄 · 소계 · 계) · ③ 텍스트 수식(함수 상자마다
* 식 두 줄 — sub2 `formulaLines`). 기본정보 · 시험 계산은 Detail 이 둠.
* 값은 DataFlow 계산(Eval · 같은 코드)으로 바로 — 오른쪽 패널이 고치면 LogicLab 이 이 함수를 다시 부름.
* 화면은 `buildFlowView(host, lg, values)` 하나. 이 파일은 DataFlow 를 정적으로 가져오므로 LogicLab 은 import() 로 늦게 부름.
* 모양은 docs/raw/M01_쉬운단가/흐름도_적용_정함.md 7 · 8.
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { el } from "@ui/ui_template_elements";
import {
evalLogic,
type CfEvalOptions,
type CfEvalResult,
type CfValues,
} from "../M02_MasterTemplete/DataFlow/M02_DF_Eval";
import {
cellValuesOf,
formulaLines,
type FormulaLine,
} from "../M02_MasterTemplete/DataFlow/M02_DF_FormulaView";
import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model";
import { itemRoundNow, totalRoundFor } from "../M02_MasterTemplete/DataFlow/M02_DF_Round";
import {
flowRows,
type FlowFunc,
type FlowGroup,
type FlowGroupKey,
} from "./M01_MasterData_UI_LogicLab_FlowRows";
import "./M01_MasterData_UI_LogicLab_FlowView.css";
const TEXT = {
Detail: ["단가산출 상세", "Unit-price detail"],
Formula: ["텍스트 수식", "Text formulas"],
Labor: ["인력 · 노무비", "Labor · labor cost"],
Material: ["재료 · 재료비", "Material · material cost"],
Cost: ["경비", "Expense"],
Plain: ["값(비용 아님)", "Values (not costs)"],
HName: ["이름", "Name"],
HUnit: ["단위", "Unit"],
HValue: ["값", "Value"],
Sub: ["소계", "Subtotal"],
Total: ["계", "Total"],
TotalNote: ["합계 끝수 기본 = 1원 미만 버림", "Total rounding default: floor to 1 won"],
None: ["출력 상자가 없음", "No output box"],
NoFunc: ["함수 상자가 없음", "No function box"],
} as const;
type TextKey = keyof typeof TEXT;
const tf = (key: TextKey): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0];
const GROUP_TITLE: Record<FlowGroupKey, TextKey> = {
labor: "Labor",
material: "Material",
cost: "Cost",
plain: "Plain",
};
const section = (title: string, body: HTMLElement[]): HTMLElement =>
el("section", {
className: "m01-logic__section m01lab__box",
children: [
el("div", { className: "m01-logic__section-head", children: [el("h3", { text: title })] }),
...body,
],
});
const groupTable = (g: FlowGroup): HTMLElement => {
const sub =
g.sub === null
? []
: [
el("tr", {
className: "m01lab__sub",
children: [
el("td", { text: tf("Sub"), attrs: { colspan: "2" } }),
el("td", { className: "m01-logic__money", text: g.subText }),
],
}),
];
return el("div", {
className: "m01flow__group",
children: [
el("h4", { text: tf(GROUP_TITLE[g.key]) }),
el("div", {
className: "m01-logic__scroll",
children: [
el("table", {
className: "m01-logic__grid",
children: [
el("thead", {
children: [
el("tr", {
children: [tf("HName"), tf("HUnit"), tf("HValue")].map((h) =>
el("th", { text: h }),
),
}),
],
}),
el("tbody", {
children: [
...g.lines.map((l) =>
el("tr", {
children: [
el("td", { text: l.name }),
el("td", { text: l.unit }),
el("td", { className: "m01-logic__money", text: l.text }),
],
}),
),
...sub,
],
}),
],
}),
],
}),
],
});
};
/** 함수 상자 하나의 글 줄 — 식 보기가 있으면 두 줄(+ 갈래) · 없으면 이름 = 값 */
function funcBlock(f: FlowFunc, lines: FormulaLine[] | null): HTMLElement {
const unit = f.unit && f.unit !== "-" ? ` ${f.unit}` : "";
const head = el("p", { className: "m01flow__func", text: `${f.name} = ${f.text}${unit}` });
if (!lines?.length) return el("div", { className: "m01flow__block", children: [head] });
return el("div", {
className: "m01flow__block",
children: [
head,
...lines.flatMap((l) => [
el("p", { className: "m01lab__expr", text: `${l.name} = ${l.named}` }),
el("p", { className: "m01lab__expr", text: `${l.name} = ${l.valued}` }),
...(l.branches ?? []).map((b) =>
el("p", {
className: `m01flow__branch${b.now ? " m01flow__branch--now" : ""}`,
text: `${b.now ? "▶ " : ""}${b.when} → ${b.then}`,
}),
),
]),
],
});
}
/** 왼쪽 「지금 내용」 — 단가산출 상세 + 텍스트 수식을 `host` 에 그림(`values` = 입력 값 · 다시 부르면 다시 그림) */
export function buildFlowView(
host: HTMLElement,
lg: CfLogic,
values: CfValues,
opts: { logics?: CfEvalOptions["logics"]; result?: CfEvalResult } = {},
): void {
let ev: CfEvalResult;
try {
ev = opts.result ?? evalLogic(lg, values, { logics: opts.logics });
} catch (e) {
host.replaceChildren(
section(tf("Detail"), [el("p", { className: "m01-logic__muted", text: String(e) })]),
);
return;
}
const rows = flowRows(lg, ev, { item: itemRoundNow(), total: totalRoundFor(lg) });
const detail = section(tf("Detail"), [
...(rows.groups.length
? rows.groups.map(groupTable)
: [el("p", { className: "m01-logic__muted", text: tf("None") })]),
el("p", {
className: "m01lab__total",
attrs: { "data-sum": String(rows.total) },
text: `${tf("Total")} ${rows.totalText}`,
}),
el("p", { className: "m01-logic__muted", text: tf("TotalNote") }),
...rows.errors.slice(0, 3).map((t) => el("p", { className: "m01flow__warn", text: t })),
]);
/** 함수 상자마다 식 두 줄(sub2 식 보기 · 받는 알약 값을 칸에 넣고 셈) — 못 그리면 이름 = 값만 */
const linesOf = (id: string): FormulaLine[] | null => {
const box = lg.boxes.find((b) => b.id === id);
if (!box?.func) return null;
try {
return formulaLines(box, cellValuesOf(box, ev.arrived?.[id]));
} catch {
return null;
}
};
const bodies = rows.funcs.map((f) => funcBlock(f, linesOf(f.box)));
const text = section(
tf("Formula"),
bodies.length ? bodies : [el("p", { className: "m01-logic__muted", text: tf("NoFunc") })],
);
host.replaceChildren(detail, text);
}