Files
Aislo/M01_MasterData/M01_MasterData_UI_Pick.ts
T
eomsangdonandClaude Opus 5.5 98f3ab59cb feat(830-10): M01 재료 찾기가 지역 칸을 봄 — 로직 1,520 시험 계산 전후 다름 0
- master_material.label = 이름 + 지역 칸 · 검색어 지역 낱말 · 자재지역 입력 · 고르는 차례(master_pick)가 이름 글 대신 지역 칸과 맞대짐
- 로직 1,520: 같음 1,517 · 전부터 멈춤 3(GC001199 ~ 1201) — 지역 칸만 고쳤을 때 다르던 11(시멘트 전국 · 포항 모래 · 포항 레미콘)이 원래 줄로
- 계산 줄 품목 칸에 지역 · 상하차 · M01 고르기 창(가격 연결 · 로직 재료 후보) 글에 지역 · 상하차(모름 「-」 뺌)
- M01 표 찾기(Store_Find) 낱말이 지역 칸에도 맞음 — 「레미콘 서울」 · 저장 뒤 찾기 기억 지움도 지역 칸 포함
- 새 줄 지역 기본 「-」 · test_m01_pick 합판 서울 기대를 지역 칸으로
- check_master 틀 · 본문 · 로직 · 조합 · 목차 0 · 단위 27 · 배 7(전과 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 20:42:29 +09:00

122 lines
4.8 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_Pick.ts
* 고르기 모달 — 가격 연결(자재품목) · 준용 직종을 이름·규격으로 찾아 고르면 ref 를 돌려줌
* ========================================================================== */
import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements";
import { createEmpty } from "@ui/ui_template_empty";
import { openModal } from "@ui/ui_template_modal";
import { createText } from "@ui/ui_template_text";
import { t as L } from "@ui/ui_template_locale";
import { fetchPick, placeNote, type PickItem, type PickKind } from "./M01_MasterData_Api_Fetch";
import { show } from "./M01_MasterData_UI_Cells";
/** 지역이 갈리는 재료 — 이름·규격만 적어 두고 지역은 계산 때 입력. */
export interface Cond {
이름: string;
규격: string;
지역: "입력";
}
export interface PickOptions {
kind: PickKind;
title: string;
/** 지금 연결 글 — 비면 「연결 끊기」 꺼짐. */
current: string;
/** 처음 찾을 글 — 그 재료 이름·규격 (없는 결과면 첫 낱말만으로 다시). */
seed: string;
/** 고르면 ref · 연결 끊기 = null. */
onPick: (ref: string | null, name?: string, value?: unknown) => void;
/** 있으면 「후보 조건으로 연결」 칸이 뜸. */
cond?: { 이름: string; 규격: string; onCond: (c: Cond) => void };
}
const button = (label: string): HTMLButtonElement => createButton({ label, variant: "ghost" });
const field = (value: string, placeholder: string): ReturnType<typeof createInputField> =>
createInputField({ type: "search", placeholder, value });
export function openPickModal(opt: PickOptions): void {
const searchField = field(opt.seed, L("M01_ProcureSearch"));
const search = searchField.input;
const list = el("div", { className: "m01-procure__list" });
const cut = button(L("M01_ProcureCut"));
cut.disabled = !opt.current;
cut.addEventListener("click", () => {
opt.onPick(null);
close();
});
const condRow: HTMLElement[] = [];
if (opt.cond) {
const { onCond } = opt.cond;
const name = field(opt.cond.이름, L("M01_PriceCondName")).input;
const spec = field(opt.cond.규격, L("M01_PriceCondSpec")).input;
const go = button(L("M01_PriceCond"));
go.addEventListener("click", () => {
if (!name.value.trim()) return;
onCond({ 이름: name.value.trim(), 규격: spec.value.trim(), 지역: "입력" });
close();
});
condRow.push(el("div", { className: "m01-procure__cond", children: [name, spec, go] }));
}
// 모달 부품 — 제목줄 [닫기] · 바깥 누르기 · Esc · 위 고정(공용 꼴) · [연결 끊기] 는 머리 단추 자리
const { close } = openModal({
title: opt.title,
closeLabel: L("M01_ProcureClose"),
width: "40rem",
dialogClass: "m01-procure__box",
actions: [cut],
mount: (body) => body.append(createText(opt.current), searchField.root, list, ...condRow),
});
const paint = (items: PickItem[], total: number): void => {
list.replaceChildren(
...(items.length
? items.map((it) => {
const tag =
(it.관급 ? ` · ${L("M01_Govt")}` : "") + (placeNote(it) && ` · ${placeNote(it)}`);
const b = el("button", {
className: "m01-procure__item ui-list-row ui-sidebar-row",
attrs: { type: "button", "data-size": "36" },
children: [
el("strong", { text: it.이름 }),
el("span", { text: `${it.규격} · ${it.단위} · ${show(it.값)}${tag}` }),
el("span", { className: "ui-text", text: it.ref }),
],
});
b.addEventListener("click", () => {
opt.onPick(it.ref, `${it.이름} ${it.규격}`.trim(), it.값);
close();
});
return b;
})
: [createEmpty({ text: L("M01_NoRows"), small: true })]),
...(total > items.length ? [createText(`${items.length} / ${total}`)] : []),
);
};
let timer: number | undefined;
let seq = 0; // 늦게 온 옛 응답은 버림(829-1)
const run = async (fallback = false): Promise<void> => {
const mine = ++seq;
try {
const q = search.value.trim();
const r = await fetchPick(opt.kind, q);
if (mine !== seq) return;
if (!r.total && fallback && q.includes(" ")) {
search.value = q.split(/\s+/)[0];
return run();
}
paint(r.items, r.total);
} catch (error) {
showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error");
}
};
search.addEventListener("input", () => {
window.clearTimeout(timer);
timer = window.setTimeout(() => void run(), 300);
});
void run(true);
}