feat(M02): 좌측 층 단추 셋 · 나무를 좌측 패널로 · 탐색기 모양 나무 · 사람 고르기 · 해시 company (PLAN 29-2)

- 「일위대가 라이브러리」 · 「구조물 조합」 줄 아래 층 단추 셋(마스터 · 회사 · 개인 · 권한 없으면 「읽기」) → 고른 층의 나무를 좌측 패널에 · 상세는 표 · 편집만
- 찾기 칸은 상세 위쪽(구조물 조합일 때 · 일위대가 화면 맨 위)
- 회사 → 사람 펼침 없앰 · 「사람 고르기」 select(같은 줄 · 회사 관리자 · 시스템 관리자) → 해시 owner
- 시스템 관리자 볼 회사 = 해시 company · sessionStorage aislo.adminCompany · 자기 회사(viewCompany)
- 공용 나무(ui_template_tree) — 들여쓰기 안내선 · 폴더 · 항목 아이콘 · 폴더 굵게(M01 · 고르기 모달도 같이)
- 시험 — combo_screen · labor_tree · layer_screen 새 배치 · viewCompany Node 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 17:37:46 +09:00
co-authored by Claude Opus 5.5
parent a01cf96769
commit cd2ee5664d
15 changed files with 342 additions and 239 deletions
+23 -19
View File
@@ -1,8 +1,8 @@
/* =============================================================================
* M02_MasterTemplete_Labor.ts
* 좌측 「일위대가 조합」 컨테이너(구조물 조합 위 · PLAN 27-2b) — 누르면 메인 칸에 일위대가 라이브러리:
* 왼쪽 뿌리 셋(마스터 · 회사 · 개인 · `TreePanel_Roots`) 아래 조합 나무(폴더 [구분, 상세구분] 깊이 2 ·
* 구조물 라이브러리와 같은 `TreePanel`) · 오른쪽 조합 목록 · 상세(`Labor_Edit`).
* 좌측 「일위대가 조합」 컨테이너(구조물 조합 위 · PLAN 27-2b) — 「일위대가 라이브러리」 줄 아래 층 단추
* (`TreePanel_Roots`) · 조합 나무(폴더 [구분, 상세구분] 깊이 2 · 구조물 라이브러리와 같은 `TreePanel` ·
* 좌측 패널 안 · PLAN 29-2) · 줄 · 나무 항목을 누르면 메인 칸에 찾기 칸 + 조합 목록 · 상세(`Labor_Edit`).
* 나무 고침 = `POST /api/m02/layers/{층}/combos/tree` · 「조합 더하기」 = 그 폴더를 채운 빈 조합(저장해야 생김).
* ========================================================================== */
@@ -33,12 +33,15 @@ function treeError(error: unknown): Error {
export interface LaborLibraryOptions {
scope: LaborScope;
canEdit: boolean;
/** 뿌리 셋 — 고른 뿌리 아래에 이 나무(`panel`)를 붙인 것 */
roots: (panel: HTMLElement) => HTMLElement;
/** 나무 항목을 누름 — 메인 칸에 일위대가 화면을 먼저 띄움 */
reveal: () => Promise<void>;
}
export interface LaborLibrary {
/** 메인 칸 — 찾기 칸 + 조합 목록 · 상세 */
root: HTMLElement;
/** 좌측 패널 나무 */
tree: HTMLElement;
refresh: () => Promise<void>;
/** 메인 칸 「저장 안 한 고침」 손잡이 */
bind: (setDirty: (dirty: boolean) => void) => void;
@@ -65,7 +68,6 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary {
const tree = createTreePanel({
layer: scope.layer,
owner: scope.owner,
label: tc("Title"),
canEdit: opt.canEdit,
maxDepth: 2,
item: { add: tc("AddCombo"), name: tc("ComboName"), onAdd: (path) => editor.startNew(path) },
@@ -81,7 +83,8 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary {
return toView(took(got), noteOf(edit, got.지운조합));
},
},
onPick: (item) => {
onPick: async (item) => {
await opt.reveal();
tree.select(item.key);
void editor.open(item.key);
},
@@ -100,15 +103,9 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary {
else editor.showList();
},
});
const aside = el("aside", {
className: "m02-shell__tree",
children: [
el("h3", { className: "m02-shell__title", text: tc("Library") }),
opt.roots(tree.root),
],
});
return {
root: el("div", { className: "m02-shell m02-labor", children: [aside, editor.root] }),
root: el("div", { className: "m02-labor", children: [tree.search, editor.root] }),
tree: tree.root,
refresh: () => tree.refresh(),
bind: (fn) => (setDirty = fn),
};
@@ -121,7 +118,10 @@ export interface LaborSectionOptions {
confirmLeave: () => Promise<boolean>;
/** 화면을 열었음 — 좌측 다른 고름 표시를 지움 */
onOpen: () => void;
make: () => LaborLibrary;
/** 라이브러리 — `reveal` = 메인 칸에 일위대가 화면을 띄움(나무 항목을 누를 때) */
make: (reveal: () => Promise<void>) => LaborLibrary;
/** 줄 아래 층 단추(PLAN 29-2) */
bar?: HTMLElement;
}
export interface LaborSection {
@@ -138,8 +138,6 @@ export function buildLaborSection(opt: LaborSectionOptions): LaborSection {
text: tc("Library"),
attrs: { type: "button", "data-labor-row": "" },
});
const body = el("div", { className: "m02-side__items ui-sidebar-list", children: [row] });
let library: LaborLibrary | null = null;
let opened = false;
const mark = (on: boolean): void => {
opened = on;
@@ -148,12 +146,18 @@ export function buildLaborSection(opt: LaborSectionOptions): LaborSection {
async function open(): Promise<void> {
if (opened || !(await opt.confirmLeave())) return;
library ??= opt.make();
library.bind(opt.show(tc("Library"), library.root));
opt.onOpen();
mark(true);
await library.refresh();
}
// 나무는 좌측 패널에 늘 있음(PLAN 29-2) — 처음부터 만들고 읽음
const library = opt.make(open);
void library.refresh();
const body = el("div", {
className: "m02-side__items ui-sidebar-list",
children: [row, ...(opt.bar ? [opt.bar] : []), library.tree],
});
row.addEventListener("click", () => void open());
return { title: tc("Title"), body, open, clear: () => mark(false) };
}