feat(m02-df): 단가산출 흐름도 0걸음 — CostFlow 를 DataFlow 로 옮겨 이름 바꿈
- 파일 M02_CF_* → M02_DF_* · css m02-cf/m01-cf → m02-df · 서버 길 /api/m02/cf → /api/m02/df · 키 CF- → DF- · 사용자 요소 UE- 그대로 - _sample · .gitignore(_data git 밖) 옮김 · DataFlow 는 CostFlow 를 import 안 함 - 시험 test_m02_df_model · router · sample · inputpanel_build(옛 cf 시험은 그대로) - 서버 등록은 sub1 몫 — M02_MasterTemplete.DataFlow.M02_DF_Router 의 router Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/* =============================================================================
|
||||
* M02_DF_InputPanel_Vars.ts
|
||||
* 입력 상자 판 — 변수(그룹 밖 정보: 면적 · 길이 · 부피 …) 정의: 이름 · 갈래(숫자 / 선택 / 글) · 단위 · 범위 · 예시값
|
||||
* 변수마다 출력 알약 하나 · `M02_DF_InputPanel.ts` 가 부름 · 글 [한, 영] 짝은 아래
|
||||
* ========================================================================== */
|
||||
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements";
|
||||
import type { CfLogic, CfVariable } from "./M02_DF_Model";
|
||||
import { checkVariable, putVariable, removeVariable } from "./M02_DF_InputPanel_Build";
|
||||
|
||||
const TEXT = {
|
||||
List: ["변수 — 알약마다 하나", "Variables — one pill each"],
|
||||
None: ["아직 변수가 없음 — 아래에서 만듦", "No variable yet"],
|
||||
Name: ["이름", "Name"],
|
||||
Type: ["갈래", "Type"],
|
||||
Number: ["숫자", "Number"],
|
||||
Choice: ["선택", "Choice"],
|
||||
Text: ["글", "Text"],
|
||||
Unit: ["단위", "Unit"],
|
||||
Min: ["범위 아래", "Range from"],
|
||||
Max: ["범위 위", "Range to"],
|
||||
Choices: ["선택 목록(쉼표로 나눔)", "Choices (comma separated)"],
|
||||
Example: ["예시값", "Example"],
|
||||
Add: ["변수 넣기", "Add variable"],
|
||||
Save: ["고치기 끝", "Save change"],
|
||||
Edit: ["고치기", "Edit"],
|
||||
Remove: ["삭제", "Remove"],
|
||||
Cancel: ["취소", "Cancel"],
|
||||
Line: ["{n} · {u} · 예시 {e}", "{n} · {u} · e.g. {e}"],
|
||||
Range: ["범위 {a}~{b}", "range {a}~{b}"],
|
||||
} as const;
|
||||
|
||||
type TKey = keyof typeof TEXT;
|
||||
const t = (key: TKey, v: Record<string, string | number> = {}): string =>
|
||||
Object.entries(v).reduce(
|
||||
(s, [k, x]) => s.replace(`{${k}}`, String(x)),
|
||||
TEXT[key][currentLanguageIndex] as string,
|
||||
);
|
||||
|
||||
const numOf = (s: string): number | null =>
|
||||
s.trim() !== "" && Number.isFinite(Number(s)) ? Number(s) : null;
|
||||
|
||||
/** 변수 한 줄 글 */
|
||||
export function variableLine(v: CfVariable): string {
|
||||
const base = t("Line", {
|
||||
n: v.name,
|
||||
u: v.unit || "-",
|
||||
e: v.example === null ? "—" : String(v.example),
|
||||
});
|
||||
const range =
|
||||
v.type === "number" && v.range ? ` · ${t("Range", { a: v.range[0], b: v.range[1] })}` : "";
|
||||
const choices = v.type === "choice" ? ` · ${(v.choices ?? []).join(" / ")}` : "";
|
||||
return `${base}${range}${choices}`;
|
||||
}
|
||||
|
||||
export function renderVariables(
|
||||
host: HTMLElement,
|
||||
lg: CfLogic,
|
||||
boxId: string,
|
||||
on: { change(lg: CfLogic): void },
|
||||
): void {
|
||||
const box = lg.boxes.find((b) => b.id === boxId);
|
||||
const vars = box?.input?.variables ?? [];
|
||||
let editing: CfVariable | null = null;
|
||||
|
||||
const form = (): HTMLElement => {
|
||||
const cur = editing;
|
||||
const name = createInputField({
|
||||
type: "text",
|
||||
label: t("Name"),
|
||||
value: cur?.name ?? "",
|
||||
placeholder: "예: 면적",
|
||||
compact: true,
|
||||
});
|
||||
const type = createSelectField({
|
||||
label: t("Type"),
|
||||
options: [
|
||||
{ value: "number", text: t("Number") },
|
||||
{ value: "choice", text: t("Choice") },
|
||||
{ value: "text", text: t("Text") },
|
||||
],
|
||||
value: cur?.type ?? "number",
|
||||
compact: true,
|
||||
onChange: () => paint(),
|
||||
});
|
||||
const unit = createInputField({
|
||||
type: "text",
|
||||
label: t("Unit"),
|
||||
value: cur?.unit ?? "",
|
||||
placeholder: "예: ㎡",
|
||||
compact: true,
|
||||
});
|
||||
const lo = createInputField({
|
||||
type: "text",
|
||||
label: t("Min"),
|
||||
value: cur?.range ? String(cur.range[0]) : "",
|
||||
compact: true,
|
||||
});
|
||||
const hi = createInputField({
|
||||
type: "text",
|
||||
label: t("Max"),
|
||||
value: cur?.range ? String(cur.range[1]) : "",
|
||||
compact: true,
|
||||
});
|
||||
const choices = createInputField({
|
||||
type: "text",
|
||||
label: t("Choices"),
|
||||
value: (cur?.choices ?? []).join(", "),
|
||||
compact: true,
|
||||
});
|
||||
const example = createInputField({
|
||||
type: "text",
|
||||
label: t("Example"),
|
||||
value: cur?.example === null || cur?.example === undefined ? "" : String(cur.example),
|
||||
compact: true,
|
||||
});
|
||||
const extra = el("div", { className: "m02-dfip__stack" });
|
||||
const paint = (): void => {
|
||||
const ty = type.select.value;
|
||||
extra.replaceChildren(
|
||||
...(ty === "number" ? [lo.root, hi.root] : ty === "choice" ? [choices.root] : []),
|
||||
);
|
||||
};
|
||||
paint();
|
||||
const submit = (): void => {
|
||||
const ty = type.select.value as CfVariable["type"];
|
||||
const ex = example.input.value.trim();
|
||||
const v: CfVariable = {
|
||||
name: name.input.value.trim(),
|
||||
type: ty,
|
||||
unit: unit.input.value.trim(),
|
||||
example: ex === "" ? null : ty === "number" ? (numOf(ex) ?? ex) : ex,
|
||||
...(ty === "number" && numOf(lo.input.value) !== null && numOf(hi.input.value) !== null
|
||||
? { range: [Number(lo.input.value), Number(hi.input.value)] as [number, number] }
|
||||
: {}),
|
||||
...(ty === "choice"
|
||||
? {
|
||||
choices: choices.input.value
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
const why = checkVariable(
|
||||
v,
|
||||
vars.filter((x) => x.name !== cur?.name),
|
||||
);
|
||||
name.setError(why ?? undefined);
|
||||
if (why) return;
|
||||
on.change(putVariable(lg, boxId, v, cur?.name));
|
||||
};
|
||||
const ok = createButton({ label: cur ? t("Save") : t("Add"), onClick: submit });
|
||||
const cancel = cur
|
||||
? createButton({
|
||||
label: t("Cancel"),
|
||||
variant: "ghost",
|
||||
onClick: () => ((editing = null), draw()),
|
||||
})
|
||||
: null;
|
||||
return el("div", {
|
||||
className: "m02-dfip__form",
|
||||
children: [
|
||||
name.root,
|
||||
type.root,
|
||||
unit.root,
|
||||
extra,
|
||||
example.root,
|
||||
el("div", { className: "m02-dfip__chips", children: cancel ? [ok, cancel] : [ok] }),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const draw = (): void => {
|
||||
const list = vars.length
|
||||
? vars.map((v) =>
|
||||
el("div", {
|
||||
className: "m02-dfip__item",
|
||||
children: [
|
||||
el("span", { text: variableLine(v) }),
|
||||
createButton({
|
||||
label: t("Edit"),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => ((editing = v), draw()),
|
||||
}),
|
||||
createButton({
|
||||
label: t("Remove"),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => on.change(removeVariable(lg, boxId, v.name)),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("None") })];
|
||||
host.replaceChildren(el("h5", { text: t("List") }), ...list, form());
|
||||
};
|
||||
draw();
|
||||
}
|
||||
Reference in New Issue
Block a user