Files
Aislo/M01_MasterData/M01_MasterData_UI_Logic_CalcPick.ts
T

156 lines
6.3 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Logic_CalcPick.ts
* 시험 계산의 재료 · 기계 고르기 — 재료 줄마다 「잡힌 품목 + 품목 고르기」 · 기계 입력은 단추 하나
* 누르면 마스터 표 모달(`PickTable`) · 고른 재료는 `고름`(호표 줄 차례 → 키)에 실어 다시 계산 · 저장 안 함
* ========================================================================== */
import { createButton, el } from "@ui/ui_template_elements";
import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch";
import type { ElementBrief, LogicInput, LogicRow } from "./M01_MasterData_UI_Logic_Api";
import { openPickTable } from "./M01_MasterData_UI_Logic_PickTable";
import { tx } from "./M01_MasterData_UI_Logic_Text";
import { condText, type PickCond } from "./M01_MasterData_UI_LogicLab_Modal";
interface Chosen {
ref: string;
label: string;
}
/** 로직마다 넣은 값(`values`)에 딸린 고른 재료 — 다시 그려도 남고 서버에는 계산 요청에만 실림 */
const chosen = new WeakMap<Record<string, string>, Map<number, Chosen>>();
const picksOf = (values: Record<string, string>): Map<number, Chosen> => {
let mine = chosen.get(values);
if (!mine) chosen.set(values, (mine = new Map()));
return mine;
};
/** 계산 요청 몸의 `고름` — 고른 것이 없으면 안 실음 */
export function pickBody(values: Record<string, string>): { 고름?: Record<string, string> } {
const mine = picksOf(values);
return mine.size
? { 고름: Object.fromEntries([...mine].map(([i, c]) => [String(i), c.ref])) }
: {};
}
const nameOf = (row: Row): string => `${row["이름"] ?? ""} ${row["규격"] ?? ""}`.trim();
const firstWord = (text: string): string => text.trim().split(/\s+/)[0] ?? "";
const MATERIAL_KEYS = /^MT\d{6}$/;
const MACHINE_CODE = /^\d{4}-\d{4}$/;
const MACHINE_KEY = /^EQ\d{6}$/;
/** 재료 키 목록 입력 — 표 모달로 바뀌어 걷음 */
export const isMaterialKeyInput = (spec: LogicInput): boolean =>
!!spec.고르기?.length && spec.고르기.every((o) => MATERIAL_KEYS.test(String(o)));
const isMachineInput = (spec: LogicInput): boolean =>
!!spec.고르기?.length &&
spec.고르기.every((o) => MACHINE_CODE.test(String(o)) || MACHINE_KEY.test(String(o)));
const pickButton = (label: string, onClick: () => void): HTMLButtonElement =>
createButton({ label, variant: "ghost", onClick });
const line = (label: string, current: HTMLElement, button: HTMLElement): HTMLElement =>
el("div", {
className: "m01-logic__field m01-logic__field--wide",
children: [el("span", { text: label }), el("div", { children: [current, button] })],
});
/** 재료 줄마다 한 칸 — 지금 잡힌 품목 + 품목 고르기 */
export function materialRows(
row: LogicRow,
prices: Record<string, ElementBrief | null>,
values: Record<string, string>,
again: () => void,
): HTMLElement[] {
const mine = picksOf(values);
return (row.호표 ?? [])
.map((item, i) => ({ item, i }))
.filter((x) => x.item.종류 === "재료")
.map(({ item, i }) => {
const cond =
typeof item.요소 === "object" && item.요소 !== null
? (item.요소 as unknown as PickCond)
: null;
const brief = (cond ? prices[condText(cond)] : prices[item.요소]) ?? null;
const label = item.이름 ?? (brief?.이름 as string | undefined) ?? "";
const held = mine.get(i);
const nowRef = held?.ref ?? brief?.ref;
const nowName =
held?.label ?? (brief ? `${brief.이름 ?? ""} ${brief.규격 ?? ""}`.trim() : "") ?? "";
const seeds = [cond?.["검색어"], cond?.이름, cond?.상세구분, cond?.구분, label]
.map((w) => firstWord(String(w ?? "")))
.filter((w, k, all) => w && all.indexOf(w) === k);
const current = el("span", {
className: "m01-logic__muted",
text: `${nowName || tx("Mat_NoPrice")} `,
});
const button = pickButton(tx("Pick_Item"), () =>
openPickTable({
kind: "재료",
title: `${tx("Pick_MatTitle")} · ${label}`,
seed: seeds[0] ?? "",
alt: seeds.slice(1),
isCurrent: (r) => String(r["키"]) === nowRef,
onPick: (r) => {
mine.set(i, { ref: String(r["키"]), label: nameOf(r) });
current.textContent = `${nameOf(r)} `;
again();
},
}),
);
return line(`${label}${item.규격 ? ` ${item.규격}` : ""}`, current, button);
});
}
/** 기계 입력 칸 — 표 모달로 고르면 입력값(원문 분류번호 · 키 목록이면 키) */
const machines = new Map<string, Row | null>();
async function machineRow(code: string): Promise<Row | null> {
if (!machines.has(code)) {
let found: Row | null = null;
try {
const got = await fetchRows("기계.json", 1, 5, code);
found = got.rows.find((r) => r["원문번호"] === code || r["키"] === code) ?? null;
} catch {
found = null;
}
machines.set(code, found);
}
return machines.get(code) ?? null;
}
export function machineControl(
spec: LogicInput,
values: Record<string, string>,
again: () => void,
): HTMLElement | null {
if (!isMachineInput(spec)) return null;
const current = el("span", { className: "m01-logic__muted" });
const paint = (): void => {
const code = values[spec.이름] ?? "";
const known = machines.get(code);
current.textContent = `${code ? `${code}${known ? ` · ${nameOf(known)}` : ""}` : tx("Mat_NoPrice")} `;
if (code && !machines.has(code)) void machineRow(code).then(paint);
};
paint();
const button = pickButton(tx("Pick_Machine"), () => {
const code = values[spec.이름] ?? "";
const known = machines.get(code);
openPickTable({
kind: "기계",
title: `${tx("Pick_Machine")} · ${spec.이름}`,
seed: String(known?.["이름"] ?? spec.이름),
isCurrent: (r) => !!code && (r["원문번호"] === code || r["키"] === code),
onPick: (r) => {
const useKey = MACHINE_KEY.test(String(spec.고르기?.[0]));
const value = String(useKey ? r["키"] : r["원문번호"]);
machines.set(value, r);
values[spec.이름] = value;
paint();
again();
},
});
});
return el("div", { children: [current, button] });
}