- 호표 묶음 소계가 텍스트 수식 소계와 같아짐(로직을 부르는 줄도 노무비·재료비·경비로 나뉘어 「그 밖」이 사라짐) - 표 줄의 빈 칸은 조건에서 빼 서버 찾기()와 같게 걸린 줄을 맞힘 - 표 꼬리표를 「표」로 줄이고 걸린 줄 없음 안내글을 바로잡음 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
172 lines
6.2 KiB
TypeScript
172 lines
6.2 KiB
TypeScript
/* =============================================================================
|
|
* M01_MasterData_UI_LogicLab_Modal.ts
|
|
* 호표 줄 자료 모달 — 그 줄이 쓰는 자료를 보임
|
|
* 표(소요량·계수)는 표 그대로 + 지금 넣은 값으로 걸린 줄 강조 · 단일 값(단가)은 값과 출처
|
|
* 표 읽기 = `/elements` → `/table`(`Logic_Note.loadTable`) · 단가 = 로직 화면이 이미 받은 `prices`
|
|
* ========================================================================== */
|
|
|
|
import { createButton, el } from "@ui/ui_template_elements";
|
|
import type { ElementBrief, NamedFormula } from "./M01_MasterData_UI_Logic_Api";
|
|
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
|
|
import {
|
|
loadTable,
|
|
matchRow,
|
|
relatedFinds,
|
|
type RelatedFind,
|
|
type TableRow,
|
|
} from "./M01_MasterData_UI_Logic_Note";
|
|
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
|
|
|
export interface ModalTarget {
|
|
title: string;
|
|
/** 수량 식(호표 줄) · 덧줄은 그 식 */
|
|
expr: string;
|
|
/** 호표 줄의 단가 요소 키 — 덧줄은 없음 */
|
|
element?: string;
|
|
prices: Record<string, ElementBrief | null>;
|
|
middles: NamedFormula[];
|
|
/** 시험 계산 칸에 넣은 값 — 걸린 줄을 맞히는 데 씀 */
|
|
values: Record<string, string>;
|
|
}
|
|
|
|
function tableView(
|
|
find: RelatedFind,
|
|
table: TableRow,
|
|
values: Record<string, string>,
|
|
): HTMLElement {
|
|
// 표 줄의 빈 칸은 「무엇이든」 — 서버 찾기()와 같게 그 칸은 조건에서 뺌
|
|
const hit =
|
|
(table.줄 ?? []).find((line) =>
|
|
matchRow(
|
|
{ ...table, 줄: [line] },
|
|
find.find.conds.filter(
|
|
([col]) => line[col] !== undefined && line[col] !== null && line[col] !== "",
|
|
),
|
|
{ values, middle: {} },
|
|
),
|
|
) ?? null;
|
|
const cols = [...Object.keys(table.조건 ?? {}), ...Object.keys(table.값칸 ?? {})];
|
|
const rows = (table.줄 ?? []).map((line) =>
|
|
el("tr", {
|
|
className: hit === line ? "m01lab__hit" : "",
|
|
attrs: hit === line ? { "data-hit": "1" } : {},
|
|
children: cols.map((c) => el("td", { text: formatNumber(line[c]) })),
|
|
}),
|
|
);
|
|
const use = table.용도
|
|
? [table.용도.공종, (table.용도.대상 ?? []).join("·")].filter(Boolean).join(" · ")
|
|
: "";
|
|
return el("div", {
|
|
className: "m01lab__data",
|
|
attrs: { "data-table": table.키 },
|
|
children: [
|
|
el("strong", { text: `${table.원문번호 ?? ""} ${table.이름 ?? table.키} · ${table.키}` }),
|
|
el("p", {
|
|
className: "m01-logic__muted",
|
|
text: [`${tl("Modal_Source")} ${table.출처 ?? ""}`, use].filter(Boolean).join(" · "),
|
|
}),
|
|
el("p", {
|
|
className: "m01-logic__muted",
|
|
text: `${find.source} → ${find.find.col} · ${hit ? tl("Modal_Hit") : tl("Modal_NoHit")}`,
|
|
}),
|
|
el("div", {
|
|
className: "m01-logic__scroll",
|
|
children: [
|
|
el("table", {
|
|
className: "m01-logic__grid m01lab__table",
|
|
children: [
|
|
el("thead", {
|
|
children: [el("tr", { children: cols.map((c) => el("th", { text: c })) })],
|
|
}),
|
|
el("tbody", { children: rows }),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
}
|
|
|
|
function priceView(ref: string, brief: ElementBrief): HTMLElement {
|
|
const cols = Object.entries(brief.값칸 ?? {});
|
|
return el("div", {
|
|
className: "m01lab__data",
|
|
attrs: { "data-price": ref },
|
|
children: [
|
|
el("strong", { text: `${brief.이름 ?? ref} · ${ref}` }),
|
|
el("dl", {
|
|
className: "m01-logic__sums",
|
|
children: [
|
|
...(brief.규격
|
|
? [el("dt", { text: tl("Modal_Spec") }), el("dd", { text: brief.규격 })]
|
|
: []),
|
|
el("dt", { text: tl("Modal_Value") }),
|
|
el("dd", { className: "m01-logic__money", text: formatNumber(brief.값) }),
|
|
...(brief.file
|
|
? [el("dt", { text: tl("Modal_Source") }), el("dd", { text: brief.file })]
|
|
: []),
|
|
...cols.flatMap(([k, v]) => [el("dt", { text: k }), el("dd", { text: String(v) })]),
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
}
|
|
|
|
/** 모달을 엶 */
|
|
export function openMaterialModal(target: ModalTarget): void {
|
|
const body = el("div", { className: "m01lab__modal-body" });
|
|
const close = (): void => backdrop.remove();
|
|
const dialog = el("div", {
|
|
className: "m01-logic__pick m01lab__modal",
|
|
attrs: { role: "dialog", "aria-label": target.title },
|
|
children: [
|
|
el("div", {
|
|
className: "m01lab__modal-top",
|
|
children: [
|
|
el("h3", { text: target.title }),
|
|
createButton({ label: tl("Modal_Close"), variant: "ghost", onClick: close }),
|
|
],
|
|
}),
|
|
body,
|
|
],
|
|
});
|
|
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 parts: HTMLElement[] = [
|
|
el("p", { className: "m01-logic__muted", text: tl("Modal_Qty") }),
|
|
el("pre", { className: "m01lab__expr", text: target.expr }),
|
|
];
|
|
const finds = relatedFinds(target.expr, target.middles);
|
|
const price = target.element ? target.prices[target.element] : undefined;
|
|
if (target.element?.startsWith("로직(")) {
|
|
parts.push(el("p", { text: `${tl("Modal_Logic")} · ${target.element}` }));
|
|
} else if (price) {
|
|
parts.push(
|
|
el("p", { className: "m01-logic__muted", text: tl("Modal_Price") }),
|
|
priceView(target.element as string, price),
|
|
);
|
|
}
|
|
const tables = el("div", { className: "m01lab__tables" });
|
|
parts.push(tables);
|
|
if (!finds.length && !price && !target.element?.startsWith("로직(")) {
|
|
parts.push(el("p", { className: "m01-logic__muted", text: tl("Modal_NoData") }));
|
|
}
|
|
body.replaceChildren(...parts);
|
|
document.body.append(backdrop);
|
|
dialog.tabIndex = -1;
|
|
dialog.focus();
|
|
|
|
void Promise.all(finds.map(async (f) => ({ f, table: await loadTable(f.find.table) }))).then(
|
|
(got) =>
|
|
tables.replaceChildren(
|
|
...got.map(({ f, table }) =>
|
|
table
|
|
? tableView(f, table, target.values)
|
|
: el("p", { className: "m01-logic__bad", text: `${f.find.table} ?` }),
|
|
),
|
|
),
|
|
);
|
|
}
|