- 주소 `?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
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Layer.ts
|
|
* M02 화면의 층 — 주소 인자(`#/m02-master-template?layer=personal|company` · 없으면 system) ·
|
|
* 층별 제목 · 고칠 권한(시스템 = SYSTEM_ADMIN · 회사 = role ADMIN 또는 is_master · 개인 = 본인) ·
|
|
* 회사 없음 안내 · 회사용 읽기만일 때 [개인용으로 복사] 막대.
|
|
* ========================================================================== */
|
|
|
|
import { ROUTES } from "@config/config_frontend";
|
|
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import { currentQuery, navigateTo } from "../A00_Common/router";
|
|
import type { SessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
|
import { copyItemToPersonal, fetchStructureCombos } from "./M02_MasterTemplete_Api_Fetch";
|
|
|
|
export type ManageLayer = "system" | "company" | "personal";
|
|
|
|
/** 주소 인자에서 층 — 모르는 값 · 없음 = 시스템 */
|
|
export function layerFromHash(): ManageLayer {
|
|
const value = currentQuery().get("layer");
|
|
return value === "company" || value === "personal" ? value : "system";
|
|
}
|
|
|
|
export const titleOf = (layer: ManageLayer): string =>
|
|
layer === "company"
|
|
? L("M02_TitleCompany")
|
|
: layer === "personal"
|
|
? L("M02_TitlePersonal")
|
|
: L("M02_Title");
|
|
|
|
export interface LayerAccess {
|
|
/** 이 층을 고칠 수 있나 — 서버도 같은 규칙으로 막음 */
|
|
canWrite: boolean;
|
|
/** 회사 · 개인 층인데 회사가 없음 — 「회사부터 등록」 안내 */
|
|
needCompany: boolean;
|
|
}
|
|
|
|
export function accessFor(layer: ManageLayer, user: SessionUser | null): LayerAccess {
|
|
if (layer === "system") return { canWrite: user?.role === "SYSTEM_ADMIN", needCompany: false };
|
|
if (!user?.company_id) return { canWrite: false, needCompany: true };
|
|
const companyAdmin = user.role === "ADMIN" || !!user.is_master;
|
|
return { canWrite: layer === "personal" || companyAdmin, needCompany: false };
|
|
}
|
|
|
|
/** 회사 없음 — 개인 · 회사 층은 회사 폴더 아래라 회사부터 */
|
|
export function buildNeedCompany(): HTMLElement {
|
|
const go = createButton({ label: L("M02_NeedCompanyGo"), variant: "filled" });
|
|
go.addEventListener("click", () => navigateTo(ROUTES.B01_ACCOUNT));
|
|
return el("div", {
|
|
className: "m02-layer-need",
|
|
children: [el("p", { text: L("M02_NeedCompany") }), go],
|
|
});
|
|
}
|
|
|
|
export interface ReadOnlyBar {
|
|
root: HTMLElement;
|
|
/** 집계표에서 고른 열 id — 없으면 null */
|
|
setColumn: (column: string | null) => void;
|
|
}
|
|
|
|
/** 회사용을 읽기만 볼 때 — 안내 + [개인용으로 복사](고른 열 하나를 내 개인 층으로) */
|
|
export function buildReadOnlyBar(user: SessionUser | null): ReadOnlyBar {
|
|
let column: string | null = null;
|
|
const copy = createButton({ label: L("M02_CopyToPersonal"), variant: "filled", size: "sm" });
|
|
copy.disabled = true;
|
|
copy.addEventListener("click", async () => {
|
|
if (!column) return showToast(L("M02_CopyPickFirst"), "error");
|
|
copy.disabled = true;
|
|
try {
|
|
const key = (await fetchStructureCombos()).줄.find((row) => row.열 === column)?.키;
|
|
if (!key) throw new Error(L("M02_CopyPickFirst"));
|
|
await copyItemToPersonal({
|
|
fromLayer: "company",
|
|
fromOwner: user?.company_id == null ? null : String(user.company_id),
|
|
key,
|
|
});
|
|
showToast(L("M02_CopyDone"), "success");
|
|
} catch (error) {
|
|
const why = error instanceof Error ? error.message : "";
|
|
showToast(L("M02_CopyFailed").replace("{value}", why), "error");
|
|
} finally {
|
|
copy.disabled = !column;
|
|
}
|
|
});
|
|
return {
|
|
root: el("div", {
|
|
className: "m02-layer-bar",
|
|
children: [el("span", { text: L("M02_ReadOnlyCompany") }), copy],
|
|
}),
|
|
setColumn: (id) => {
|
|
column = id;
|
|
copy.disabled = !id;
|
|
},
|
|
};
|
|
}
|