Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts
T
eomsangdonandClaude Opus 5.5 4a5b00d72d fix(M02): 일위대가 라이브러리에서 뿌리를 바꿔도 일위대가 화면 유지 · 좌측으로 열면 해시 open=일위대가(PLAN 27-2b 흠)
- 원인: 뿌리 줄이 두 벌(구조물 조합 쪽은 숨김) — 숨은 줄이 눌리면 go 기본값 open=구조물조합 · 좌측 열기는 해시를 안 바꿈
- 고침: 페이지가 지금 화면(view)을 쥐고 go 기본값을 그것으로 · 일위대가를 열면 history.replaceState 로 해시만 open=일위대가 · 일위대가에서 양식으로 가면 구조물조합
- 시험: test_m02_labor_tree 한 건 더함 · test_m02_structure_panel 콜백 모양 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
2026-09-30 15:17:56 +09:00

166 lines
7.2 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 OpenTarget,
type Root,
} from "./M02_MasterTemplete_Layer";
import { buildLaborLibrary, buildLaborSection } from "./M02_MasterTemplete_Labor";
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),
});
// 일위대가 화면이 떠 있나 — 뿌리를 바꿔도(어느 뿌리 줄을 누르든) 일위대가로 다시 엶 ·
// 해시 `open` 도 맞춤(새로 고침 · 카드 진입과 같게) · 옵션 · 도면은 옛 규칙(구조물 조합으로)
let view: OpenTarget = entry.open;
const mark = (open: OpenTarget): void => {
view = open;
const query = new URLSearchParams(entryQuery({ ...entry, open }));
history.replaceState(history.state, "", `#/${ROUTES.M02_MASTER_TEMPLATE}?${query}`);
};
const go = async (
next: Root,
nextOwner: string | null,
open: OpenTarget = view === "일위대가" ? "일위대가" : "구조물조합",
): Promise<void> => {
// 같은 주소 안 이동은 머리 가드가 안 물음 — 저장 안 한 고침은 여기서
if (!(await main.confirmLeave())) return;
navigateTo(
ROUTES.M02_MASTER_TEMPLATE,
entryQuery({ ...entry, open, root: next, owner: nextOwner }),
);
};
// 일위대가 라이브러리(PLAN 27-2b) — 같은 뿌리 셋 · 다른 뿌리를 누르면 그 뿌리의 일위대가로 다시 엶
const laborRoots = (panel: HTMLElement): HTMLElement =>
createLibraryRoots({
user,
active: { root: entry.root, owner },
panel,
go: (next, nextOwner) => void go(next, 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();
// 일위대가 화면에서 양식으로 옮김(일위대가 · 옵션 화면은 current 없음)
if (main.current() && view === "일위대가") mark("구조물조합");
},
{
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,
labor: ({ onOpen }) =>
buildLaborSection({
show: main.showView,
confirmLeave: main.confirmLeave,
onOpen: () => {
onOpen();
mark("일위대가");
},
make: () =>
buildLaborLibrary({ scope: { layer, owner }, canEdit: isAdmin, roots: laborRoots }),
}),
});
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` — 옵션 · 도면 컨테이너는 마스터에만 · 일위대가는 세 뿌리 ·
// 옵션 · 일위대가 화면이 뒤에 열리면 편집 칸 차례(`seq`)가 늦게 온 집계표를 버림
if (entry.open !== "구조물조합") side.reveal(entry.open);
void tree.refresh();
// 이 화면을 떠나면 층 길을 시스템 · 자기 층으로 되돌림(다른 화면의 항목 고르기가 남의 층을 안 부르게)
return { covers: () => false, show: () => {}, dispose: () => setActiveLayer("system") };
}