- 기초단가 표: 칸 나갈 때 즉시 저장 없앰 · 고친 칸·새 줄·지울 줄은 초안(sessionStorage z01-draft:base:{kind})에 쌓고 [저장] 한 번에 보냄 · [고친 것 버리기]·[줄 추가]·[줄 삭제]·[초기값으로] · 409 면 초안 지키고 알림 · 서버가 revision 을 줄 때만 고치기 단추 섬
- 옛 덮개 길(칸 단위 PUT · 고친 것 모아 보기 · 덮개 판정 표시)과 그 사전 글자 뺌 — 계약 1-0 에서 서버 쪽 삭제
- 공종 편집(Z01_MasterData_UI_WorkItem.ts · _WorkItem_Rows.ts): 왼쪽 목록(상태별 개수·검색) / 머리 / 구성 줄 표(갈래별 · 자원 찾기 = 기초단가 검색) / 품셈 원문 표+[주] 읽기 전용 / [저장]·[초기값으로]·[검수 완료] · 초안 열쇠 z01-draft:work_item:{key}
- 공종 API(sub_laptop_3 e1c71c3e) 모양에 맞춤 — 줄은 id + 계약 칸 열하나만 보냄(extra=forbid) · 저장·초기값 응답(새 GET 모양)으로 바로 다시 그림 · 저장된 고침(changed_columns·is_added) 표시
- 초안 순수 함수 Z01_MasterData_UI_Draft.ts + 시험 test_z01_master_draft.py
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145cr1DMC5Ft8fouQSXtp69
162 lines
5.7 KiB
TypeScript
162 lines
5.7 KiB
TypeScript
/* =============================================================================
|
||
* Z01_MasterData_UI_Side.ts
|
||
* 좌측 패널 — B03~B09 와 같은 도킹 사이드바 안에 접히는 상자 둘(기초단가 · 마스터 트리)
|
||
*
|
||
* 상자 겉모습·접기는 공용(`ui-collapsible ui-sidebar-section`) · 누른 단추 하나만 강조
|
||
* (두 상자를 가로질러 한 개) (2026-09-15 브레인 — 좌측 패널 양식 갈음 · 기초단가 칸).
|
||
* ========================================================================== */
|
||
|
||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||
import { el } from "@ui/ui_template_elements";
|
||
import { t as L } from "@ui/ui_template_locale";
|
||
import {
|
||
fetchBasePrices,
|
||
fetchMasterRows,
|
||
resetBasePrices,
|
||
saveBasePrices,
|
||
type MasterGroup,
|
||
VALUES_TABLE_ID,
|
||
type WorkItemAxis,
|
||
} from "./Z01_MasterData_Api_Fetch";
|
||
import { groupKinds, type ServedKind } from "./Z01_MasterData_UI_Cells";
|
||
import type { RowsView } from "./Z01_MasterData_UI_Rows";
|
||
|
||
const AXES: WorkItemAxis[] = ["forest", "const"];
|
||
|
||
export function buildSidePanel(
|
||
groups: MasterGroup[],
|
||
kinds: ServedKind[],
|
||
view: RowsView,
|
||
openWorkItems: (axis: WorkItemAxis) => void,
|
||
): HTMLElement {
|
||
const panel = el("div", { className: "z01-master__side" });
|
||
const activate = (button: HTMLElement): void => {
|
||
panel.querySelector(".is-active")?.classList.remove("is-active");
|
||
button.classList.add("is-active");
|
||
};
|
||
|
||
// 갈래·kind·이름·줄 수 모두 서버 것 — 화면은 목록도 이름도 안 가짐(kind 가 늘면 저절로 섬).
|
||
const boxes = groupKinds(kinds).map((box) => {
|
||
const grid = el("div", { className: "z01-master__base-grid" });
|
||
for (const served of box.items) {
|
||
const button = el("button", {
|
||
className: "z01-master__base",
|
||
attrs: { type: "button" },
|
||
children: [
|
||
el("span", { text: served.label }),
|
||
el("span", {
|
||
className: "z01-master__key",
|
||
text: served.rows.toLocaleString("ko-KR"),
|
||
}),
|
||
],
|
||
});
|
||
button.addEventListener("click", () => {
|
||
activate(button);
|
||
view.show({
|
||
title: `${box.label} › ${served.label}`,
|
||
key: `base-prices/${served.kind}`,
|
||
load: (query) => fetchBasePrices(served.kind, query),
|
||
edit: {
|
||
scope: `base:${served.kind}`,
|
||
save: (body) => saveBasePrices(served.kind, body),
|
||
reset: (rowKeys) => resetBasePrices(served.kind, rowKeys),
|
||
},
|
||
});
|
||
});
|
||
grid.append(button);
|
||
}
|
||
return { label: box.label, grid };
|
||
});
|
||
|
||
// 공종 편집 — 구성 줄을 고치고 「입력 필요」 공종을 메움(PLAN 1-4).
|
||
const workGrid = el("div", { className: "z01-master__base-grid" });
|
||
for (const axis of AXES) {
|
||
const button = el("button", {
|
||
className: "z01-master__base",
|
||
attrs: { type: "button" },
|
||
text: L(axis === "forest" ? "Z01_WorkItem_Axis_Forest" : "Z01_WorkItem_Axis_Const"),
|
||
});
|
||
button.addEventListener("click", () => {
|
||
activate(button);
|
||
openWorkItems(axis);
|
||
});
|
||
workGrid.append(button);
|
||
}
|
||
|
||
const tree = el("nav", { className: "z01-master__tree" });
|
||
for (const group of groups) {
|
||
const groupBox = folder("z01-master__group", group.label, String(group.files.length));
|
||
groupBox.open = true;
|
||
// 부산물은 계산이 남긴 기록 — 고칠 정본이 아님(2026-09-15 브레인 갈래 나눔표).
|
||
if (group.key === "byproduct") {
|
||
groupBox.append(
|
||
el("p", { className: "z01-master__note", text: L("Z01_MasterData_Byproduct") }),
|
||
);
|
||
}
|
||
for (const file of group.files) {
|
||
const fileBox = folder(
|
||
"z01-master__file",
|
||
file.label,
|
||
file.label === file.key ? "" : file.key,
|
||
);
|
||
// 낱값 마디는 표들 밑에 — 누르면 여느 표와 같은 길로 그림(2026-09-15 브레인).
|
||
const ordered = [...file.tables].sort(
|
||
(a, b) => Number(a.id === VALUES_TABLE_ID) - Number(b.id === VALUES_TABLE_ID),
|
||
);
|
||
for (const table of ordered) {
|
||
const button = el("button", {
|
||
className: "z01-master__table",
|
||
attrs: { type: "button", title: table.key },
|
||
children: [
|
||
el("span", { text: table.label }),
|
||
el("span", {
|
||
className: "z01-master__key",
|
||
text: table.row_count.toLocaleString("ko-KR"),
|
||
}),
|
||
],
|
||
});
|
||
button.addEventListener("click", () => {
|
||
activate(button);
|
||
view.show({
|
||
title: `${file.label} › ${table.label}`,
|
||
key: `${file.key} / ${table.key}`,
|
||
load: (query) => fetchMasterRows(file.id, table.id, query),
|
||
});
|
||
});
|
||
fileBox.append(button);
|
||
}
|
||
groupBox.append(fileBox);
|
||
}
|
||
tree.append(groupBox);
|
||
}
|
||
|
||
panel.append(
|
||
...boxes.map((box) => section(box.label, box.grid)),
|
||
section(L("Z01_WorkItem_Title"), workGrid),
|
||
section(L("Z01_MasterData_Tree"), tree),
|
||
);
|
||
attachCollapsible(panel);
|
||
return panel;
|
||
}
|
||
|
||
function section(title: string, body: HTMLElement): HTMLElement {
|
||
return el("section", {
|
||
className: "z01-master__section ui-collapsible ui-sidebar-section",
|
||
children: [
|
||
el("p", { className: "z01-master__section-title ui-collapsible__title", text: title }),
|
||
body,
|
||
],
|
||
});
|
||
}
|
||
|
||
/** 트리 접는 칸 — 브라우저 기본 <details> 로 둠. */
|
||
function folder(className: string, label: string, aside: string): HTMLDetailsElement {
|
||
const summary = el("summary", {
|
||
children: [
|
||
el("span", { text: label }),
|
||
el("span", { className: "z01-master__key", text: aside }),
|
||
],
|
||
});
|
||
return el("details", { className, children: [summary] });
|
||
}
|