Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
179 lines
6.1 KiB
TypeScript
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(" · "),
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
}
|