refactor(M02): 자재 조합 표를 마스터 템플릿으로 옮김 · M02 좌측 「자재 조합」 컨테이너

- 표 resources/master_data/자재조합.json → resources/master_template/자재조합.json(git mv) · 키 UM 은 표 안 「다음」 번호 · _키대장 · master_keys 의 UM 뺌 · check_master 틀이 새 자리를 같이 봄
- 서버: M01 /api/m01/matset* 뺌 · M02 저장소 M02_MasterTemplete_Matset.py(목록 · 새로 · 고치기 · 지우기 · 쓰는 열 검사) · 길 GET/POST /api/m02/material-combos · PUT/DELETE /api/m02/material-combos/{키} · 갈라 내기(branchMaterialCombo)도 이 저장소로
- 화면: M01 좌측 「자재 조합」 컨테이너 뺌 · M02 좌측 「자재 조합」 컨테이너(찾기 · 키 맨 앞 목록 · 새로 · 본떠 만들기 · 지우기) · 메인 칸 상세(이름 · 비고 · 본뜬키 · 담은 자재 표 · 자재 더하기 · 빼기 · 저장) · 쓰는 열이 있으면 지우기는 확인 창 없이 알림
- 시험: test_m01_matset → test_m02_matset · test_m02_combo_branch 사본 자리 · 실제 자재 코드

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwJQ5eRrCKf3nH9EFeKvJz
This commit is contained in:
2026-09-28 22:25:30 +09:00
co-authored by Claude Opus 5.5
parent 6606ef167a
commit 4abfc5b265
28 changed files with 753 additions and 882 deletions
+2 -15
View File
@@ -11,7 +11,6 @@ import { t as L } from "@ui/ui_template_locale";
import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch";
import { tx } from "./M01_MasterData_UI_Logic_Text";
import { tc } from "./M01_MasterData_UI_Combo_Text";
import { tm } from "./M01_MasterData_UI_Matset_Text";
import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree";
/** 컨테이너마다 기억하는 화면 상태 — 찾기 글 · 쪽 · 소요량·계수 표의 용도 대상 거름 (거름은 `m01.filter.*`) */
@@ -89,8 +88,6 @@ export interface SideHandle {
logicHost: HTMLElement;
/** 일위대가 조합 컨테이너 안(구분 · 상세구분 거름이 들어갈 자리) */
comboHost: HTMLElement;
/** 자재 조합 컨테이너 안(찾기 칸이 들어갈 자리) */
matsetHost: HTMLElement;
/** 그룹의 파일 판본을 다시 받음(저장 뒤) — 돌려받는 것 = 새 목록 */
refresh: (group: string) => Promise<FileInfo[]>;
/** 그룹의 파일 목록(이미 받은 것) */
@@ -149,13 +146,11 @@ const LEAF: Group[] = ["환율", "요율"];
const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"];
const LOGIC_ID = "로직|";
const COMBO_ID = "조합|";
const MATSET_ID = "자재조합|";
export function buildSide(
onOpen: (pick: Pick) => void,
onLogic: () => void,
onCombo: () => void = () => {},
onMatset: () => void = () => {},
): SideHandle {
const lists = new Map<Group, FileInfo[]>();
const bodies = new Map<Group, HTMLElement>();
@@ -465,17 +460,9 @@ ${first.file}`
onCombo();
});
const matsetHost = el("div", { className: "m01-side__items ui-sidebar-list" });
const matset = section(tm("Title"), matsetHost);
matset.querySelector(".ui-collapsible__title")?.addEventListener("click", () => {
if (!matset.classList.contains("is-collapsed")) return;
setActive(buttons.has(MATSET_ID) ? MATSET_ID : null);
onMatset();
});
const root = el("div", {
className: "m01-side ui-sidebar-panel",
children: [...groups, logic, combo, matset],
children: [...groups, logic, combo],
});
attachCollapsible(root);
void Promise.all(GROUPS.map(refresh)).catch((error) =>
@@ -485,5 +472,5 @@ ${first.file}`
onPicked = fn;
};
const files = (group: string): FileInfo[] => lists.get(group as Group) ?? [];
return { root, logicHost, comboHost, matsetHost, refresh, files, setActive, setOnPicked, filter };
return { root, logicHost, comboHost, refresh, files, setActive, setOnPicked, filter };
}