- 층 권한 한 벌 M02_Layer_Access — 나무 · 항목(Router_Items) · 양식(Router_Layers) 같은 규칙 · owner 인자 · 남의 층은 읽기만 · 읽기로 남의 층에 뼈대 안 씀
- 남의 층 목록 GET /api/m02/layers/{company|personal}/owners — 시스템 관리자 회사 목록 · 회사 관리자 같은 회사 사람
- ?layer= 페이지 분리 폐기 → 한 화면 뿌리 셋(마스터 · 회사 · 개인) · 뿌리 부품 TreePanel_Roots(펼칠 때 불러옴) · TreePanel 은 owner · api 를 받는 공용 부품
- 진입 해시 ?open=구조물조합|일위대가|옵션|도면&root=&owner= · 항목 고르기 [관리 화면에서 고치기]도 이 해시
- 「항목 나무」 → 「구조물 라이브러리」
- 시험 test_m02_layer_access 새로 · layer_screen · template_layers 새 규칙으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
5.6 KiB
TypeScript
126 lines
5.6 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_UI_Page.ts
|
|
* 마스터 템플릿 · 구조물 관리 화면 — 좌측 도킹 패널(양식 목록) / 우측 편집 칸(표·도면 부품) ·
|
|
* 「구조물 조합」을 열면 편집 칸 왼쪽에 구조물 라이브러리(뿌리 셋 · 주 · 고치는 자리) · 오른쪽 참조표(PLAN 26-7c)
|
|
*
|
|
* 한 화면에 뿌리 셋(마스터 · 회사 · 개인 · PLAN 27-1) — 고른 뿌리 = 진입 해시 `?open=&root=&owner=`
|
|
* (`M02_MasterTemplete_Layer.ts`) · 다른 뿌리를 누르면 해시를 바꿔 이 화면을 다시 세움.
|
|
* 화면은 로그인만 확인 — 고치기는 뿌리별 권한일 때만 켬(서버도 막음 · `accessFor`).
|
|
* ========================================================================== */
|
|
|
|
import "@ui/ui_template_workflow_layout.css";
|
|
import { ROUTES } from "@config/config_frontend";
|
|
import { el } from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
|
import { navigateTo, type MountedPage } from "../A00_Common/router";
|
|
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
|
import { setActiveLayer } from "./M02_MasterTemplete_Api_Fetch";
|
|
import {
|
|
accessFor,
|
|
buildReadOnlyBar,
|
|
entryFromHash,
|
|
entryQuery,
|
|
layerOfRoot,
|
|
ownOwner,
|
|
type Root,
|
|
} from "./M02_MasterTemplete_Layer";
|
|
import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure";
|
|
import { createTreePanel } from "./M02_MasterTemplete_TreePanel";
|
|
import { createLibraryRoots } from "./M02_MasterTemplete_TreePanel_Roots";
|
|
import { canEdit, createMain, type Selection } from "./M02_MasterTemplete_UI_Main";
|
|
import { buildSide } from "./M02_MasterTemplete_UI_Side";
|
|
import "./M02_MasterTemplete_UI_Style.css";
|
|
|
|
export async function renderM02MasterTemplate(root: HTMLElement): Promise<MountedPage> {
|
|
const user = await fetchSessionUser().catch(() => null);
|
|
const entry = entryFromHash();
|
|
let layer = layerOfRoot(entry.root);
|
|
// 자기 층 주인을 적어도 자기 층 · 회사 없는 사람의 회사 · 개인 뿌리 = 마스터(뿌리 자리에 안내)
|
|
let owner = entry.owner && entry.owner !== ownOwner(layer, user) ? entry.owner : null;
|
|
if (accessFor(layer, owner, user).needCompany) {
|
|
[entry.root, layer, owner] = ["master", "system", null];
|
|
}
|
|
setActiveLayer(layer, owner);
|
|
const isAdmin = accessFor(layer, owner, user).canWrite; // 이 뿌리를 고칠 수 있나(하위 부품의 「관리자」)
|
|
// 자기 회사용을 읽기만 볼 때 — 안내 + [개인용으로 복사]
|
|
const readOnlyBar = layer === "company" && !owner && !isAdmin ? buildReadOnlyBar(user) : null;
|
|
// 고른 뿌리의 나무 — 고르기 모달과 같은 폴더 나무 · 「구조물 조합」 편집 칸 왼쪽
|
|
const tree = createTreePanel({
|
|
layer,
|
|
owner,
|
|
label: L("M02_KindTable"),
|
|
canEdit: canEdit(layer, isAdmin),
|
|
onPick: (item) => void main.openTable(layer, item.열),
|
|
settle: () => main.settle(),
|
|
onChanged: (colId) => main.openTable(layer, colId, true),
|
|
});
|
|
const go = async (next: Root, nextOwner: string | null): Promise<void> => {
|
|
// 같은 주소 안 이동은 머리 가드가 안 물음 — 저장 안 한 고침은 여기서
|
|
if (!(await main.confirmLeave())) return;
|
|
navigateTo(ROUTES.M02_MASTER_TEMPLATE, entryQuery({ ...entry, root: next, owner: nextOwner }));
|
|
};
|
|
const roots = createLibraryRoots({
|
|
user,
|
|
active: { root: entry.root, owner },
|
|
panel: tree.root,
|
|
go: (next, nextOwner) => void go(next, nextOwner),
|
|
});
|
|
const aside = el("aside", {
|
|
className: "m02-shell__tree",
|
|
children: [el("h3", { className: "m02-shell__title", text: L("M02_TreeTitle") }), roots],
|
|
});
|
|
const main = createMain(
|
|
isAdmin,
|
|
() => void Promise.all([side.refresh(), tree.refresh()]),
|
|
() => side.paint(),
|
|
{
|
|
top: readOnlyBar?.root,
|
|
aside,
|
|
onColumn: (column) => {
|
|
readOnlyBar?.setColumn(column);
|
|
tree.select(column);
|
|
},
|
|
},
|
|
);
|
|
// 첫 화면 · 닫은 뒤(양식 지움 · 자재 조합 상세 닫음) = 구조물집계표 — 빈 화면 없음
|
|
const home: Selection = {
|
|
layer,
|
|
projectId: null,
|
|
kind: "table",
|
|
name: STRUCTURE_TABLE,
|
|
};
|
|
const side = buildSide({
|
|
layer,
|
|
isAdmin,
|
|
onOpen: (sel) => void main.open(sel ?? home),
|
|
getOpen: main.current,
|
|
confirmLeave: main.confirmLeave,
|
|
showView: main.showView,
|
|
});
|
|
|
|
const layout = el("div", { className: "ui-workflow-layout m02-master" });
|
|
const overlays = createWorkflowOverlays({
|
|
title: L("M02_Title"),
|
|
optionsContent: el("div", { className: "m02-master__side", children: [side.root] }),
|
|
showProjectName: false,
|
|
onOptionsOpenChange: (isOpen) => layout.classList.toggle("is-options-open", isOpen),
|
|
});
|
|
layout.append(
|
|
el("div", {
|
|
className: "ui-workflow-layout__body",
|
|
children: [el("main", { className: "ui-workflow-layout__main", children: [main.root] })],
|
|
}),
|
|
overlays.root,
|
|
);
|
|
root.innerHTML = "";
|
|
root.append(layout);
|
|
void main.open(home);
|
|
// 진입 해시 `open` — 옵션 · 도면 컨테이너는 마스터에만(일위대가는 27-2 컨테이너가 생기면) ·
|
|
// 옵션 화면이 뒤에 열리면 편집 칸 차례(`seq`)가 늦게 온 집계표를 버림
|
|
if (entry.open === "옵션" || entry.open === "도면") side.reveal(entry.open);
|
|
void tree.refresh();
|
|
// 이 화면을 떠나면 층 길을 시스템 · 자기 층으로 되돌림(다른 화면의 항목 고르기가 남의 층을 안 부르게)
|
|
return { covers: () => false, show: () => {}, dispose: () => setActiveLayer("system") };
|
|
}
|