Files
Aislo/Z01_MasterData/Z01_MasterData_UI_Side.ts
T
eomsangdonandClaude Opus 5 feab848e69 feat(Z01): 기초단가 표 [저장] 방식 교체 · 공종 편집 화면 신설 — PLAN 1-4
- 기초단가 표: 칸 나갈 때 즉시 저장 없앰 · 고친 칸·새 줄·지울 줄은 초안(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
2026-09-18 23:55:45 +09:00

162 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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] });
}