- 왼쪽 패널 「일위대가 조합」 컨테이너(단가산출 로직 아래) · 구분/상세구분 거름 · 새 조합 · 지우기 · 저장 - 상세: 이름·구분·단위·비고 + 담은 로직 표(올리기/내리기/빼기) · 로직 줄을 누르면 단가산출 로직 상세로 감 - 로직 더하기 창: 구분 → 상세구분 → 찾기로 하나씩 고름 - 미리 보기: 담은 로직별 입력값으로 계산해 노무비·재료비·경비 합계(보기만) - 서버 길(조합 목록·저장·지우기)은 아직 없어 Combo_Api 한 곳에 가정 이름으로 둠 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_Combo_Pick.ts
|
|
* [로직 더하기] 창 — 구분 → 상세구분 → 찾기로 사용자가 로직을 하나씩 고름(자동 추천 없음)
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
createButton,
|
|
createInputField,
|
|
createSelectField,
|
|
el,
|
|
showToast,
|
|
} from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import {
|
|
fetchLogics,
|
|
fetchLogicSubs,
|
|
type LogicSummary,
|
|
type SubBrief,
|
|
} from "./M01_MasterData_UI_Logic_Api";
|
|
import { tc } from "./M01_MasterData_UI_Combo_Text";
|
|
|
|
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",
|
|
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);
|
|
|
|
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"),
|
|
);
|
|
}
|