Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Labor_Pick.ts
T
eomsangdonandClaude Opus 5.5 f8302149a9 refactor(M02): 일위대가 조합 화면 부품 M01 → M02 옮김 — Labor_Api(층 · 키 하나 · 세 층 · 나무 · 담을 로직) · Pick · Preview · Text · css(PLAN 27-2b 앞)
- 조합 Api 가 층 길(/api/m02/layers/{layer}/combos) · 키 하나는 /api/m02/combos/key 로 — 회사 · 개인 조합도 읽음
- 로직 고르기 · 미리 보기는 /api/m02/logic-lib(로그인) 로 읽음
- M01 조합 화면 · M02 구조물 조합 쪽 부름을 새 자리로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
2026-09-30 13:22:13 +09:00

120 lines
4.5 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Labor_Pick.ts
* [로직 더하기] 창 — 구분 → 상세구분 → 찾기로 사용자가 로직을 하나씩 고름(자동 추천 없음)
* ========================================================================== */
import {
createButton,
createInputField,
createSelectField,
el,
showToast,
} from "@ui/ui_template_elements";
import { t as L } from "@ui/ui_template_locale";
import type { LogicSummary, SubBrief } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
import {
fetchLibLogics as fetchLogics,
fetchLibLogicSubs as fetchLogicSubs,
} from "./M02_MasterTemplete_Labor_Api";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css"; // 창 겉틀
import "./M02_MasterTemplete_Labor.css";
const opts = (values: string[]): { value: string; text: string }[] =>
[L("M01_All"), ...values].map((v, i) => ({ value: i ? v : "", text: v }));
export function openLogicPicker(done: (logic: LogicSummary) => void): void {
const subs: SubBrief[] = [];
let all: LogicSummary[] = [];
const sub = createSelectField({ options: opts([]), compact: true, label: tc("Sub") });
const detail = createSelectField({ options: opts([]), compact: true, label: tc("Detail") });
detail.select.disabled = true;
const searchField = createInputField({ type: "search", placeholder: tc("Pick_Search") });
const list = el("div", { className: "m01-logic__pick-list" });
const hint = el("p", { className: "m01-logic__muted", text: tc("Pick_Hint") });
const close = (): void => backdrop.remove();
const find = (): void => {
const q = searchField.input.value.trim().toLowerCase();
const found = all.filter(
(x) =>
(!sub.select.value || x.구분 === sub.select.value) &&
(!detail.select.value || x.상세구분 === detail.select.value) &&
(!q || `${x.키} ${x.이름}`.toLowerCase().includes(q)),
);
hint.textContent = found.length ? `${found.length} / ${all.length}` : tc("Pick_None");
list.replaceChildren(
...found.slice(0, 100).map((x) => {
const row = el("button", {
className: "m01-logic__pick-row m01-logic__pick-row--button",
attrs: { type: "button", "data-pick": x.키 },
children: [
el("div", {
className: "m01-logic__stack",
children: [
el("strong", { text: x.이름 }),
el("span", {
className: "m01-logic__muted",
text: `${x.키} · ${x.구분} ${x.상세구분}`.trim(),
}),
],
}),
el("span", { text: `${x.결과단위}${x.blocked ? ` · ${tc("Pick_Blocked")}` : ""}` }),
],
});
row.addEventListener("click", () => {
close();
done(x);
});
return row;
}),
);
};
sub.select.addEventListener("change", () => {
const d = subs.find((s) => s.name === sub.select.value)?.details ?? [];
detail.setOptions(opts(d), "");
detail.select.disabled = d.length === 0;
});
const dialog = el("div", {
className: "m01-logic__pick m01-combo",
attrs: { role: "dialog", "aria-label": tc("Pick_Title") },
children: [
el("div", {
className: "m01-logic__section-head",
children: [
el("h3", { text: tc("Pick_Title") }),
createButton({ label: tc("Pick_Close"), variant: "ghost", onClick: close }),
],
}),
el("div", {
className: "m01-logic__filters",
children: [
sub.root,
detail.root,
searchField.root,
createButton({ label: tc("Pick_Find"), variant: "filled", onClick: find }),
],
}),
hint,
list,
],
});
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);
searchField.input.addEventListener("keydown", (ev) => ev.key === "Enter" && find());
searchField.input.focus();
void Promise.all([fetchLogicSubs(), fetchLogics()])
.then(([s, l]) => {
subs.push(...s);
all = l;
sub.setOptions(opts(s.map((x) => x.name)), "");
})
.catch((error) =>
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error"),
);
}