Files
Aislo/M01_MasterData/M01_MasterData_UI_Logic_Wizard_Atom.ts
T

263 lines
8.0 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Logic_Wizard_Atom.ts
* 값 하나를 고르는 칸 — 직접 값 / 설계 입력 / 표에서 찾기 / 다른 로직 부르기
* 수량 · 조건 나누기의 각 갈래가 같이 씀 · 고른 것은 넘겨받은 Atom 에 바로 씀(식은 Model 이 조립)
* ========================================================================== */
import { createInputField, createSelectField, el, showToast } from "@ui/ui_template_elements";
import {
fetchTableInfo,
searchLogics,
type LogicBrief,
type TableBrief,
} from "./M01_MasterData_UI_Logic_Wizard_Api";
import { condChoices, tableInfo, type Atom } from "./M01_MasterData_UI_Logic_Wizard_Model";
import { tw } from "./M01_MasterData_UI_Logic_Wizard_Text";
type Option = { value: string; text: string };
export const select = (
options: Option[],
value: string,
onChange: (v: string) => void,
label?: string,
): HTMLElement => createSelectField({ options, value, onChange, compact: true, label }).root;
export const textBox = (
value: string,
placeholder: string,
onInput: (v: string) => void,
label?: string,
): HTMLElement => createInputField({ value, placeholder, onInput, label, type: "text" }).root;
/** 원자를 통째로 바꿔 씀(다른 갈래로 옮길 때) */
function reset(atom: Atom, next: Atom): void {
for (const k of Object.keys(atom)) delete (atom as unknown as Record<string, unknown>)[k];
Object.assign(atom, next);
}
const fresh = (kind: Atom["kind"]): Atom =>
kind === "value"
? { kind, value: "" }
: kind === "input"
? { kind, name: "" }
: kind === "logic"
? { kind, key: "", args: "" }
: { kind, table: "", col: "", cond: {} };
/**
* `tables` = 원문 절 단계에서 고른 표 · `onChange` = 고른 것이 바뀔 때마다
* `kinds` = 이 자리에서 고를 수 있는 갈래(기본 넷 다)
*/
export function atomEditor(
atom: Atom,
tables: TableBrief[],
onChange: () => void,
kinds: Atom["kind"][] = ["value", "table", "input", "logic"],
): HTMLElement {
const host = el("div", { className: "m01w__atom" });
const body = el("div", { className: "m01w__atom-body" });
const names: Record<Atom["kind"], string> = {
value: tw("Qty_Value"),
table: tw("Qty_Table"),
input: tw("Qty_Input"),
logic: tw("Qty_Logic"),
};
const draw = (): void => {
body.replaceChildren();
if (atom.kind === "value") body.append(valueBox(atom, onChange));
else if (atom.kind === "input") body.append(inputBox(atom, onChange));
else if (atom.kind === "logic") body.append(logicBox(atom, onChange));
else body.append(tableBox(atom, tables, onChange));
};
host.append(
select(
kinds.map((k) => ({ value: k, text: names[k] })),
atom.kind,
(v) => {
reset(atom, fresh(v as Atom["kind"]));
draw();
onChange();
},
),
body,
);
draw();
return host;
}
function valueBox(atom: Extract<Atom, { kind: "value" }>, onChange: () => void): HTMLElement {
return textBox(atom.value, tw("Qty_Number_Ph"), (v) => {
atom.value = v;
onChange();
});
}
function inputBox(atom: Extract<Atom, { kind: "input" }>, onChange: () => void): HTMLElement {
return textBox(atom.name, tw("Qty_Name"), (v) => {
atom.name = v.trim();
onChange();
});
}
function logicBox(atom: Extract<Atom, { kind: "logic" }>, onChange: () => void): HTMLElement {
const list = el("div", { className: "m01w__found" });
const key = createInputField({ value: atom.key, placeholder: tw("Qty_LogicKey"), type: "text" });
key.input.addEventListener("input", () => {
atom.key = key.input.value.trim();
onChange();
});
const find = createInputField({ placeholder: tw("Line_Logic_Find"), type: "search" });
let timer = 0;
find.input.addEventListener("input", () => {
window.clearTimeout(timer);
timer = window.setTimeout(() => {
const q = find.input.value.trim();
if (!q) return list.replaceChildren();
void searchLogics(q)
.then((r) => showLogics(r.logics.slice(0, 8)))
.catch((e: unknown) => showToast(e instanceof Error ? e.message : tw("Failed"), "error"));
}, 250);
});
const showLogics = (found: LogicBrief[]): void => {
list.replaceChildren(
...found.map((l) => {
const row = el("button", {
className: "m01w__row",
attrs: { type: "button" },
text: `${l.원문번호} ${l.이름} · ${l.결과단위} · ${l.}`,
});
row.addEventListener("click", () => {
atom.key = l.;
key.input.value = l.;
list.replaceChildren();
onChange();
});
return row;
}),
);
};
const args = createInputField({
value: atom.args,
placeholder: tw("Qty_LogicArgs"),
type: "text",
});
args.input.addEventListener("input", () => {
atom.args = args.input.value;
onChange();
});
return el("div", { className: "m01w__stack", children: [find.root, list, key.root, args.root] });
}
function tableBox(
atom: Extract<Atom, { kind: "table" }>,
tables: TableBrief[],
onChange: () => void,
): HTMLElement {
const host = el("div", { className: "m01w__stack" });
const detail = el("div", { className: "m01w__stack" });
const drawDetail = (): void => {
const info = tableInfo.get(atom.table);
detail.replaceChildren();
if (!info) return;
detail.append(
select(
[
{ value: "", text: "—" },
...Object.keys(info.값칸 ?? {}).map((c) => ({ value: c, text: c })),
],
atom.col,
(v) => {
atom.col = v;
onChange();
},
tw("Qty_Col"),
),
);
for (const cond of Object.keys(info.조건 ?? {})) detail.append(condRow(atom, cond, onChange));
};
const load = (key: string): void => {
const brief = tables.find((t) => t. === key);
if (!brief) return (drawDetail(), onChange());
void fetchTableInfo(brief.file, key)
.then((full) => {
tableInfo.set(key, full);
for (const c of Object.keys(full.조건 ?? {})) atom.cond[c] ??= { ask: true };
drawDetail();
onChange();
})
.catch((e: unknown) => showToast(e instanceof Error ? e.message : tw("Failed"), "error"));
};
const choose = (key: string): void => {
atom.table = key;
atom.col = "";
atom.cond = {};
load(key);
};
host.append(
select(
[
{ value: "", text: "—" },
...tables.map((t) => ({ value: t.키, text: `${t.이름} · ${t.}` })),
],
atom.table,
choose,
tw("Qty_Table_Pick"),
),
detail,
);
if (atom.table && !tableInfo.has(atom.table)) load(atom.table);
else drawDetail();
return host;
}
/** 표의 조건 칸 하나 — 설계자에게 물음 / 붙박이 값 */
function condRow(
atom: Extract<Atom, { kind: "table" }>,
cond: string,
onChange: () => void,
): HTMLElement {
atom.cond[cond] ??= { ask: true };
const choices = condChoices(atom.table, cond);
const value = el("div", { className: "m01w__inline" });
const set = (v: string): void => {
atom.cond[cond] = { ask: false, value: v };
onChange();
};
const drawValue = (): void => {
value.replaceChildren();
const bind = atom.cond[cond];
if (bind.ask) return;
if (choices) {
const now = bind.value || String(choices[0]);
atom.cond[cond] = { ask: false, value: now };
value.append(
select(
choices.map((c) => ({ value: String(c), text: String(c) })),
now,
set,
),
);
} else value.append(textBox(bind.value, tw("Qty_Number_Ph"), set));
};
const mode = select(
[
{ value: "ask", text: tw("Qty_Ask") },
{ value: "fixed", text: tw("Qty_Fixed") },
],
atom.cond[cond].ask ? "ask" : "fixed",
(v) => {
atom.cond[cond] = v === "ask" ? { ask: true } : { ask: false, value: "" };
drawValue();
onChange();
},
);
drawValue();
return el("div", {
className: "m01w__inline",
children: [el("strong", { text: cond }), mode, value],
});
}