feat(ui_template): 항목 고르기 모달 · 공용 나무 보기 — PLAN 21-5

- ui_template_tree: M01 renderTree 를 공용으로 올림(M01 은 같은 이름으로 그대로 부름) · 기본 줄 createTreeRow · 클래스 ui-tree__*
- ui_template_item_picker: openItemPicker({current, canCopy}) → apply(summary_item) | clear | cancel · 왼쪽 마스터 · 회사 · 개인 → 종류 → 분류1 → 항목 나무 + 찾기 · 오른쪽 폴더 모양(키는 툴팁) · 개인용으로 복사 · 관리 화면 이동
- 목록 GET /api/m02/items?all=1 · 복사 POST /api/m02/layers/personal/items/copy — 서버가 없으면(404) 21-1 모양 가짜 응답
- 자료 쪽(나무 · 찾기 · 폴더 줄)은 화면 없는 모듈로 나눠 시험

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K7JS47SacjPxZ718QsZKyr
This commit is contained in:
2026-09-29 12:00:13 +09:00
co-authored by Claude Sonnet 5.5
parent 38b3459efb
commit 927e85d110
11 changed files with 875 additions and 59 deletions
+2 -40
View File
@@ -1,44 +1,6 @@
/* =============================================================================
* M01_MasterData_UI_Tree.ts
* 왼쪽 패널 트리 — 부모(접기/펴기) · 자식(들여쓰기) · 줄 수 표시
* 왼쪽 패널 트리 — 공용 `ui_template_tree` 를 그대로 다시 내보냄(부르는 쪽 import 불변)
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
export interface TreeNode {
id: string;
label: string;
/** 줄 수 — null 이면 안 보임 */
count: number | null;
/** 눌렀을 때 — 없으면 접기/펴기만 */
run?: () => void;
children?: TreeNode[];
/** 처음부터 펴짐 */
open?: boolean;
}
/** 줄(버튼) 만들기 — 돌려받은 것 = 그 버튼 · click 은 부르는 쪽이 이음 */
export type MakeRow = (
node: TreeNode,
caret: string,
depth: number,
onClick: () => void,
) => HTMLElement;
export function renderTree(nodes: TreeNode[], make: MakeRow, depth = 0): HTMLElement[] {
return nodes.map((node) => {
if (!node.children?.length) return make(node, "", depth, () => node.run?.());
const kids = el("div", {
className: "m01-tree__kids",
children: renderTree(node.children, make, depth + 1),
});
kids.hidden = !node.open;
const row = make(node, kids.hidden ? "▸" : "▾", depth, () => {
kids.hidden = !kids.hidden;
const caret = row.querySelector(".m01-tree__caret");
if (caret) caret.textContent = kids.hidden ? "▸" : "▾";
node.run?.();
});
return el("div", { className: "m01-tree__branch", children: [row, kids] });
});
}
export { renderTree, type MakeRow, type TreeNode } from "@ui/ui_template_tree";