- 변환: 설계 값 변수 예시값 = 줄 시험입력 · 없으면 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
200 lines
7.0 KiB
TypeScript
200 lines
7.0 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,
|
|
roundText,
|
|
totalRoundNow,
|
|
totalRoundOf,
|
|
} 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: ["합계 끝수", "Total rounding"],
|
|
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 totalRound = totalRoundOf(lg, totalRoundNow());
|
|
const rows = flowRows(lg, ev, { item: itemRoundNow(), total: totalRound });
|
|
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")}: ${roundText(totalRound)}`,
|
|
}),
|
|
...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);
|
|
}
|