/* ============================================================================= * M01_MasterData_UI_Logic_PickTable.ts * 시험 계산의 재료 · 기계 고르기 모달 — 마스터 컨테이너와 같은 표(`GET /rows`) · 쪽 나눔 · 칸 글 * 검색 칸을 채워 열고 · 잡힌 품목 칠함 · 한 줄 누르면 고름 · 저장 안 함(읽기만) * ========================================================================== */ import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements"; import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch"; import { buildPager, flatten, show } from "./M01_MasterData_UI_Cells"; import { tx } from "./M01_MasterData_UI_Logic_Text"; export type PickTableKind = "재료" | "기계"; const FILES: Record = { 재료: "재료_자재품목.json", 기계: "기계.json" }; /** 표에 보일 칸 — 컨테이너 표의 칸 이름(묶음 값은 「값›가격」) */ const COLS: Record = { 재료: [ "키", "구분", "상세구분", "이름", "규격", "단위", "물가자료", "유통물가", "물가정보", "거래가격", ], 기계: ["원문번호", "구분", "상세구분", "이름", "규격", "값›가격"], }; const SIZE = 30; export interface PickTableOptions { kind: PickTableKind; title: string; /** 검색 칸을 채워 엶 — 결과가 없으면 첫 낱말만으로 다시 · 그래도 없으면 `alt` 를 차례로 */ seed: string; alt?: string[]; /** 지금 잡힌 줄이면 true — 그 줄을 칠함 */ isCurrent: (row: Row) => boolean; onPick: (row: Row) => void; } export function openPickTable(opt: PickTableOptions): void { const close = (): void => back.remove(); const searchField = createInputField({ type: "search", placeholder: tx("Pick_Search"), value: opt.seed, }); const search = searchField.input; const host = el("div", { className: "m01-logic__pick-list" }); const dialog = el("div", { className: "m01-logic__pick m01-logic__pick--table", attrs: { role: "dialog", "aria-label": opt.title }, children: [ el("div", { className: "m01-logic__section-head", children: [ el("h3", { text: opt.title }), createButton({ label: tx("Pick_Close"), variant: "ghost", onClick: close }), ], }), searchField.root, host, ], }); const back = el("div", { className: "m01-logic__backdrop", children: [dialog] }); back.addEventListener("click", (ev) => ev.target === back && close()); back.addEventListener("keydown", (ev) => ev.key === "Escape" && close()); let page = 1; const tried = new Set([opt.seed]); let seq = 0; const load = async (fallback = false): Promise => { const mine = ++seq; try { const q = search.value.trim(); const got = await fetchRows(FILES[opt.kind], page, SIZE, q); if (mine !== seq) return; // 최신 응답만 if (!got.total && fallback) { const next = [...(q.includes(" ") ? [q.split(/\s+/)[0]] : []), ...(opt.alt ?? [])].find( (w) => w && !tried.has(w), ); if (next) { tried.add(next); search.value = next; return load(true); } } const cols = COLS[opt.kind]; const body = got.rows.map((row) => { const flat = flatten(row); const tr = el("tr", { className: `m01lab__pickrow${opt.isCurrent(row) ? " is-picked" : ""}`, children: cols.map((c) => el("td", { text: show(flat[c]), attrs: { title: show(flat[c]) } }), ), }); tr.addEventListener("click", () => { close(); opt.onPick(row); }); return tr; }); host.replaceChildren( buildPager(got.total, SIZE, got.page, (next) => { page = next; void load(); }), el("div", { className: "m01-master__grid-wrap", children: [ el("table", { className: "m01-master__grid", children: [ el("thead", { children: [el("tr", { children: cols.map((c) => el("th", { text: c })) })], }), el("tbody", { children: body }), ], }), ], }), ); host.querySelector(".is-picked")?.scrollIntoView({ block: "nearest" }); } catch (error) { showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error"); } }; let timer = 0; search.addEventListener("input", () => { window.clearTimeout(timer); timer = window.setTimeout(() => { page = 1; void load(); }, 300); }); document.body.append(back); search.focus(); void load(true); }