Files
Aislo/M01_MasterData/M01_MasterData_UI_Logic_Wizard_Source.ts
T

179 lines
6.1 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Logic_Wizard_Source.ts
* 만들기 둘째 걸음 — 원문 절 고르기(책 → 부문 → 장 → 절) · 그 절(아래 절 포함)의 표를 용도와 함께 제시
* 절 목록 = `GET /sections` · 표 = `GET /tables?section=` — 번호를 손으로 넣지 않음
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import {
fetchSections,
fetchSectionTables,
type SectionBrief,
type TableBrief,
} from "./M01_MasterData_UI_Logic_Wizard_Api";
import { select, textBox } from "./M01_MasterData_UI_Logic_Wizard_Atom";
import type { Draft } from "./M01_MasterData_UI_Logic_Wizard_Model";
import { tw } from "./M01_MasterData_UI_Logic_Wizard_Text";
import { fail, note, page, type Step } from "./M01_MasterData_UI_Logic_Wizard_Ui";
const sections = new Map<string, Promise<SectionBrief[]>>();
const load = (book: string): Promise<SectionBrief[]> => {
let got = sections.get(book);
if (!got) sections.set(book, (got = fetchSections(book)));
return got;
};
const distinct = (rows: SectionBrief[], key: "division" | "chapter"): string[] => [
...new Set(rows.map((r) => r[key]).filter(Boolean)),
];
export const sourceStep: Step = {
key: "S_Source",
question: "Q_Source",
ready: (d) => d.section.trim() !== "",
render: ({ d, changed }) => {
const filters = el("div", { className: "m01w__inline" });
const list = el("div", { className: "m01w__found" });
const picked = el("p", { className: "m01w__picked" });
const tables = el("div", { className: "m01w__stack" });
let all: SectionBrief[] = [];
let division = "";
let chapter = "";
let q = "";
const drawTables = (): void =>
tables.replaceChildren(
...(d.section && !d.tables.length
? [note(tw("Section_None")), note(tw("Section_Skip"))]
: d.tables.map((t) => tableRow(t, d, changed))),
);
const choose = (s: SectionBrief): void => {
d.section = s.section;
d.tables = [];
picked.textContent = `${tw("Section_Picked")} · ${s.section} ${s.title}`;
changed();
drawList();
void fetchSectionTables(d.book, s.section)
.then((found) => {
d.tables = [...found]; // 처음에는 이 절의 표를 모두 씀 — 빼려면 체크를 풂
drawTables();
changed();
})
.catch(fail);
};
const options = (values: string[]): { value: string; text: string }[] =>
values.map((v) => ({ value: v, text: v }));
const draw = (): void => {
const inDivision = all.filter((s) => !division || s.division === division);
const chapters = distinct(inDivision, "chapter");
if (!chapters.includes(chapter)) chapter = chapters[0] ?? "";
const divisions = distinct(all, "division");
filters.replaceChildren(
select(
["산림품셈", "건설품셈"].map((b) => ({ value: b, text: b })),
d.book,
(v) => {
d.book = v;
d.section = "";
d.tables = [];
division = chapter = "";
picked.textContent = "";
drawTables();
changed();
start();
},
tw("Book"),
),
...(divisions.length
? [
select(
options(divisions),
division || divisions[0],
(v) => ((division = v), (chapter = ""), draw()),
tw("Division"),
),
]
: []),
select(options(chapters), chapter, (v) => ((chapter = v), draw()), tw("Chapter")),
textBox(q, tw("Section_Search"), (v) => ((q = v.trim()), drawList())),
);
drawList();
};
const drawList = (): void => {
const inChapter = all.filter(
(s) =>
(!division || s.division === division) &&
s.chapter === chapter &&
(!q || s.section.startsWith(q) || s.title.includes(q)),
);
list.replaceChildren(
...inChapter.slice(0, 80).map((s) => {
const row = el("button", {
className: `m01w__row${s.section === d.section ? " is-active" : ""}`,
attrs: { type: "button", "data-section": s.section },
text: `${s.section} ${s.title} · ${tw("Section_Counts", { t: s.tables, l: s.logics })}`,
});
row.addEventListener("click", () => choose(s));
return row;
}),
);
};
const start = (): void => {
void load(d.book)
.then((rows) => {
all = rows;
const divisions = distinct(rows, "division");
division = divisions[0] ?? "";
const own = rows.find((s) => s.section === d.section);
if (own) {
division = own.division;
chapter = own.chapter;
picked.textContent = `${tw("Section_Picked")} · ${own.section} ${own.title}`;
}
draw();
})
.catch(fail);
};
start();
drawTables();
return page("Q_Source", filters, list, picked, tables);
},
};
function tableRow(t: TableBrief, d: Draft, changed: () => void): HTMLElement {
const check = el("input", { attrs: { type: "checkbox", "data-table": t. } });
check.checked = d.tables.some((x) => x. === t.);
check.addEventListener("change", () => {
d.tables = check.checked ? [...d.tables, t] : d.tables.filter((x) => x. !== t.);
changed();
});
const usage = t.용도?.대상?.join("·") ?? "";
return el("label", {
className: "m01w__table",
children: [
check,
el("div", {
className: "m01w__stack",
children: [
el("strong", { text: `${t.원문번호} ${t.이름} · ${t.}` }),
el("span", {
className: "m01w__muted",
text: [
t.기준 ? `${tw("Table_Base")} ${t.기준}` : "",
`${tw("Table_Cols")} ${Object.keys(t.값칸 ?? {}).join("·")}`,
usage ? `${tw("Table_Usage")} ${usage}` : "",
]
.filter(Boolean)
.join(" · "),
}),
],
}),
],
});
}