Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts
T
eomsangdonandClaude Sonnet 5.5 154d693599 feat(M02): 구조물 관리 화면 — 층 인자(개인 · 회사) · 층별 권한 · 대시보드 카드
- 주소 `?layer=personal|company`(없으면 시스템) — 라우터가 인자를 떼고 `currentQuery` 로 읽음 · 층 고정 풂
- 층 길 접두어 `/layers/{층}` — 구조물집계표 · 조합 · 복사 호출이 화면 층을 따름(시스템은 옛 길 그대로)
- 제목 셋(마스터 템플릿 · 구조물 관리(회사) · 구조물 관리(개인)) · 돌아가기 층 무관 · 도면 양식은 시스템 층만
- 편집 권한 — 시스템 SYSTEM_ADMIN · 회사 ADMIN 또는 is_master · 개인 본인 · 읽기만이면 단추 숨김 + [개인용으로 복사]
- 회사 없음 → 「회사부터 등록」 안내 · 대시보드 카드 「구조물 관리」(USER 개인용 · ADMIN · SYSTEM_ADMIN 회사용 + 개인용)
- 시험 test_m02_layer_screen.py 5 · test_m02_structure_panel.py 갱신

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Kyw9qnkJ94pC1A4PfpDtA
2026-09-29 12:09:31 +09:00

78 lines
3.0 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_UI_Page.ts
* 마스터 템플릿 · 구조물 관리 화면 — 좌측 도킹 패널(양식 목록) / 우측 편집 칸(표·도면 부품)
*
* 층 = 주소 인자 `?layer=personal|company`(없으면 시스템) — 같은 화면 한 벌.
* 화면은 로그인만 확인 — 고치기는 층별 권한일 때만 켬(서버도 막음 · `M02_MasterTemplete_Layer.ts`).
* ========================================================================== */
import "@ui/ui_template_workflow_layout.css";
import { el } from "@ui/ui_template_elements";
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
import { setActiveLayer } from "./M02_MasterTemplete_Api_Fetch";
import {
accessFor,
buildNeedCompany,
buildReadOnlyBar,
layerFromHash,
titleOf,
} from "./M02_MasterTemplete_Layer";
import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure";
import { 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<void> {
const user = await fetchSessionUser().catch(() => null);
const layer = layerFromHash();
setActiveLayer(layer);
const access = accessFor(layer, user);
if (access.needCompany) {
root.replaceChildren(buildNeedCompany());
return;
}
const isAdmin = access.canWrite; // 이 층을 고칠 수 있나(하위 부품의 「관리자」 = 같은 뜻)
// 회사용을 읽기만 볼 때 — 안내 + [개인용으로 복사]
const readOnlyBar = layer === "company" && !isAdmin ? buildReadOnlyBar(user) : null;
const main = createMain(
isAdmin,
() => void side.refresh(),
() => side.paint(),
{ top: readOnlyBar?.root, onColumn: (column) => readOnlyBar?.setColumn(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: titleOf(layer),
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);
}