feat(M01): 관리자 화면 로직 칸 — 일위대가표(호표) 고치기 · 시험 계산 · 요소 찾기 창
- 화면: 왼쪽 로직 목록(원문·장·이름 찾기·막힘) / 가운데 머리·받을 값·호표·중간 값·덧줄·끝수 / 오른쪽 시험 계산 - 고친 것은 캐시에 쌓고 [저장] 한 번에 · [고친 것 버리기] · 줄 더하기·지우기 · 로직 새로 만들기·지우기 - 진입은 mountM01Logic(host) — 공용 진입 파일(sub_laptop_3 몫)이 한 줄로 붙임 - 서버: 로직 하나에 단가 자동(prices) · 요소 찾기(GET /elements) · 저장 전 시험 계산(calc 에 row·file) - 계약 문서 갱신 · 시험 1개 더함(9개 통과) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_Logic_Pick.ts
|
||||
* 요소 찾기 창 — 그룹 전체에서 이름·열쇠로 찾아 호표 줄에 넣음(`GET /elements`)
|
||||
* 표 고르기(소요량·계수)는 값칸 단추를 눌러 `찾기(…).칸` 으로 수량에 넣음
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
||||
import { searchElements, type ElementBrief } from "./M01_MasterData_UI_Logic_Api";
|
||||
import { formatNumber, type PickDone, type PickMode } from "./M01_MasterData_UI_Logic_Edit";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
|
||||
const GROUPS: Record<PickMode, string[]> = {
|
||||
element: ["인력", "재료", "기계", "로직"],
|
||||
table: ["소요량", "계수"],
|
||||
};
|
||||
|
||||
export function openPicker(mode: PickMode, group: string, done: PickDone): void {
|
||||
const groups = GROUPS[mode];
|
||||
const select = el("select", { className: "m01-logic__input" });
|
||||
for (const g of groups) select.append(el("option", { text: g, attrs: { value: g } }));
|
||||
select.value = groups.includes(group) ? group : groups[0];
|
||||
const search = el("input", {
|
||||
className: "m01-logic__input",
|
||||
attrs: { type: "search", placeholder: tx("Pick_Search") },
|
||||
});
|
||||
const count = el("span", { className: "m01-logic__muted" });
|
||||
const list = el("div", { className: "m01-logic__pick-list" });
|
||||
const close = (): void => backdrop.remove();
|
||||
const dialog = el("div", {
|
||||
className: "m01-logic__pick",
|
||||
attrs: { role: "dialog", "aria-label": tx("Pick_Title") },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: tx("Pick_Title") }),
|
||||
createButton({ label: tx("Pick_Close"), variant: "ghost", onClick: close }),
|
||||
],
|
||||
}),
|
||||
el("div", { className: "m01-logic__pick-bar", children: [select, search, count] }),
|
||||
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());
|
||||
|
||||
const choose = (item: ElementBrief, column?: string): void => {
|
||||
close();
|
||||
done(item, column);
|
||||
};
|
||||
let seq = 0;
|
||||
const load = async (): Promise<void> => {
|
||||
const mine = ++seq;
|
||||
try {
|
||||
const found = await searchElements(select.value, search.value.trim());
|
||||
if (mine !== seq) return; // 최신 응답만
|
||||
count.textContent = tx("Pick_Total", { n: found.total });
|
||||
list.replaceChildren(...found.items.map((item) => itemRow(item, mode, choose)));
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error");
|
||||
}
|
||||
};
|
||||
let timer = 0;
|
||||
search.addEventListener("input", () => {
|
||||
window.clearTimeout(timer);
|
||||
timer = window.setTimeout(load, 250);
|
||||
});
|
||||
select.addEventListener("change", load);
|
||||
document.body.append(backdrop);
|
||||
search.focus();
|
||||
void load();
|
||||
}
|
||||
|
||||
function itemRow(
|
||||
item: ElementBrief,
|
||||
mode: PickMode,
|
||||
choose: (item: ElementBrief, column?: string) => void,
|
||||
): HTMLElement {
|
||||
const text = el("div", {
|
||||
className: "m01-logic__stack",
|
||||
children: [
|
||||
el("strong", { text: `${item.이름 ?? ""} ${item.규격 ?? ""}`.trim() }),
|
||||
el("span", { className: "m01-logic__muted", text: item.ref }),
|
||||
],
|
||||
});
|
||||
if (mode === "table") {
|
||||
const columns = Object.keys(item.값칸 ?? {});
|
||||
const chips = columns.map((column) =>
|
||||
createButton({ label: column, variant: "pill", onClick: () => choose(item, column) }),
|
||||
);
|
||||
return el("div", {
|
||||
className: "m01-logic__pick-row",
|
||||
children: [text, el("div", { className: "m01-logic__chips", children: chips })],
|
||||
});
|
||||
}
|
||||
const unit = typeof item.단위 === "string" ? item.단위 : "";
|
||||
const value = typeof item.값 === "object" && item.값 !== null ? "{…}" : formatNumber(item.값);
|
||||
const row = el("button", {
|
||||
className: "m01-logic__pick-row m01-logic__pick-row--button",
|
||||
attrs: { type: "button" },
|
||||
children: [
|
||||
text,
|
||||
el("span", { className: "m01-logic__money", text: `${value} ${unit}`.trim() }),
|
||||
],
|
||||
});
|
||||
row.addEventListener("click", () => choose(item));
|
||||
return row;
|
||||
}
|
||||
Reference in New Issue
Block a user