/* ============================================================================= * M01_MasterData_UI_LogicLab_New_Find.ts * 새 로직 만들기의 고르기 창 둘 * ① 마스터 요소 — 종류(인력·재료·기계) → 구분 → 상세구분 → 이름 찾기 → 한 줄 고르기 * ② 표 찾기 — 표(소요량·계수)와 값 칸을 고른 뒤 표의 조건 칸마다 식을 적음 * ========================================================================== */ import { createButton, createInputField, createSelectField, el, showToast, } from "@ui/ui_template_elements"; import { fetchFiles, fetchRows, fetchSubsAll, type FileInfo, type SubInfo, } from "./M01_MasterData_Api_Fetch"; import type { DraftFind } from "./M01_MasterData_UI_Logic_Api"; import { formatNumber } from "./M01_MasterData_UI_Logic_Edit"; import { loadTable } from "./M01_MasterData_UI_Logic_Note"; import { openPicker } from "./M01_MasterData_UI_Logic_Pick"; import { tn } from "./M01_MasterData_UI_LogicLab_New_Text"; export interface PickedElement { ref: string; /** 이름 + 규격 */ name: string; /** 수량에 쓰는 단위(「원/일」 → 「일」) */ unit: string; group: string; } const GROUPS = ["인력", "재료", "기계"]; const SHOWN = 50; /** 창 뼈대 — 제목 · 닫기 · 몸통 */ function shell(title: string, body: HTMLElement[]): { close: () => void } { const close = (): void => backdrop.remove(); const dialog = el("div", { className: "m01-logic__pick", attrs: { role: "dialog", "aria-label": title }, children: [ el("div", { className: "m01-logic__section-head", children: [ el("h3", { text: title }), createButton({ label: tn("Close"), variant: "ghost", onClick: close }), ], }), ...body, ], }); const backdrop = el("div", { className: "m01-logic__backdrop", children: [dialog] }); backdrop.addEventListener("click", (ev) => ev.target === backdrop && close()); backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close()); document.body.append(backdrop); return { close }; } /** 그룹의 파일 — 재료는 자재품목(구분·상세구분이 있는 것) */ async function fileOf(group: string): Promise { const files = await fetchFiles(group); return group === "재료" ? (files.find((f) => f.file.includes("자재품목")) ?? files[0]) : files[0]; } const options = (values: string[]): { value: string; text: string }[] => [ { value: "", text: tn("All") }, ...values.map((v) => ({ value: v, text: v })), ]; export function openElementPick(done: (picked: PickedElement) => void): void { let file: FileInfo | undefined; let subs: SubInfo[] = []; const groupBox = createSelectField({ options: GROUPS.map((g) => ({ value: g, text: g })), value: GROUPS[0], compact: true, label: tn("Group"), }); const subBox = createSelectField({ options: options([]), value: "", compact: true, label: tn("Sub"), }); const detailBox = createSelectField({ options: options([]), value: "", compact: true, label: tn("Detail"), }); const searchField = createInputField({ type: "search", placeholder: tn("Search") }); const count = el("span", { className: "m01-logic__muted" }); const list = el("div", { className: "m01-logic__pick-list" }); const win = shell(tn("K_master"), [ el("div", { className: "m01-logic__pick-bar", children: [groupBox.root, subBox.root, detailBox.root, searchField.root, count], }), list, ]); let seq = 0; const load = async (): Promise => { if (!file) return; const mine = ++seq; try { const got = await fetchRows( file.file, 1, SHOWN, searchField.input.value.trim(), false, subBox.select.value, detailBox.select.value, ); if (mine !== seq) return; count.textContent = tn("Count", { n: got.total, m: Math.min(got.total, SHOWN) }); list.replaceChildren( ...got.rows.map((row) => { const name = `${row["이름"] ?? ""} ${row["규격"] ?? ""}`.trim(); const unitText = String(row["단위"] ?? ""); const unit = groupBox.select.value === "인력" ? "인" : (unitText.split("/").pop() ?? ""); const button = el("button", { className: "m01-logic__pick-row m01-logic__pick-row--button", attrs: { type: "button", "data-ref": String(row["키"]) }, children: [ el("div", { className: "m01-logic__stack", children: [ el("strong", { text: name }), el("span", { className: "m01-logic__muted", text: String(row["키"]) }), ], }), el("span", { className: "m01-logic__money", text: `${formatNumber(row["값"])} ${unitText}`.trim(), }), ], }); button.addEventListener("click", () => { win.close(); done({ ref: String(row["키"]), name, unit, group: groupBox.select.value }); }); return button; }), ); } catch (error) { showToast(error instanceof Error ? error.message : tn("Failed"), "error"); } }; const changeGroup = async (): Promise => { file = await fileOf(groupBox.select.value); subs = file ? (await fetchSubsAll({ file: file.file })).subs : []; subBox.setOptions(options(subs.map((s) => s.name)), ""); detailBox.setOptions(options([]), ""); detailBox.select.disabled = true; await load(); }; groupBox.select.addEventListener("change", () => void changeGroup()); subBox.select.addEventListener("change", () => { const details = subs.find((s) => s.name === subBox.select.value)?.details ?? []; detailBox.setOptions(options(details), ""); detailBox.select.disabled = details.length === 0; void load(); }); detailBox.select.addEventListener("change", () => void load()); let timer = 0; searchField.input.addEventListener("input", () => { window.clearTimeout(timer); timer = window.setTimeout(() => void load(), 250); }); void changeGroup(); } /** 표 찾기 — 표·값 칸을 고른 뒤 조건 칸마다 식을 받음 · 표의 조건 칸이 없으면 바로 끝 */ export function openTablePick(done: (find: DraftFind, label: string) => void): void { openPicker("table", "소요량", (item, column) => { void (async () => { const table = await loadTable(item.ref); const conds = Object.keys(table?.조건 ?? {}); const label = `${item.이름 ?? item.ref} · ${item.ref} → ${column ?? ""}`; const find = { 표: item.ref, 값칸: column ?? "", 조건: {} as Record }; if (!conds.length) { done(find, label); return; } const inputs = conds.map((c) => { const box = createInputField({ type: "text", value: c }); box.input.setAttribute("data-cond", c); return { c, box }; }); const win = shell(tn("TableTitle"), [ el("p", { className: "m01-logic__muted", text: label }), el("p", { text: tn("Cond") }), ...inputs.map(({ c, box }) => el("label", { className: "m01-logic__field", children: [el("span", { text: c }), box.root], }), ), createButton({ label: tn("Ok"), onClick: () => { for (const { c, box } of inputs) find.조건[c] = box.input.value.trim() || c; win.close(); done(find, label); }, }), ]); })(); }); }