- 주소 `?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
248 lines
9.0 KiB
TypeScript
248 lines
9.0 KiB
TypeScript
import { ROUTES } from "@config/config_frontend";
|
|
import {
|
|
createButton,
|
|
createTag,
|
|
hideLoadingOverlay,
|
|
showLoadingOverlay,
|
|
showToast,
|
|
} from "@ui/ui_template_elements";
|
|
import { section } from "@ui/ui_template_general_blocks";
|
|
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
|
import { navigateTo } from "../A00_Common/router";
|
|
import {
|
|
fetchAllCompanies,
|
|
fetchAllJoinRequests,
|
|
fetchAllProjects,
|
|
fetchAllUsers,
|
|
fetchAuditLogs,
|
|
fetchCompanyJoinRequests,
|
|
fetchCompanyMembers,
|
|
fetchCompanyProjects,
|
|
fetchDashboardMe,
|
|
fetchSystemResources,
|
|
fetchUserCompany,
|
|
fetchUserProjects,
|
|
type AuditLog,
|
|
type CompanyInfo,
|
|
type DashboardUser,
|
|
type JoinRequest,
|
|
type Member,
|
|
type ProjectItem,
|
|
type ResourceData,
|
|
} from "./B01_Dashboard_Api_Fetch";
|
|
import { auditLogTable, userTable } from "./B01_Dashboard_UI_Admin";
|
|
import { L, roleLabel } from "./B01_Dashboard_UI_Common";
|
|
import {
|
|
buildCompanyPanel,
|
|
companyTable,
|
|
joinRequestTable,
|
|
memberTable,
|
|
} from "./B01_Dashboard_UI_Company";
|
|
import { openAddMemberModal, openCreateCompanyModal } from "./B01_Dashboard_UI_Modals";
|
|
import { buildProfileForm, buildSecurityForm } from "./B01_Dashboard_UI_Profile";
|
|
import { projectTable } from "./B01_Dashboard_UI_Projects";
|
|
import { buildResourcePanel } from "./B01_Dashboard_UI_Resources";
|
|
import { buildTempUploadSection } from "./B01_Dashboard_UI_TempUpload";
|
|
import "./B01_Dashboard_UI_Style.css";
|
|
|
|
export interface DashboardState {
|
|
user: DashboardUser;
|
|
userProjects: ProjectItem[];
|
|
companyProjects: ProjectItem[];
|
|
allProjects: ProjectItem[];
|
|
company: CompanyInfo | null;
|
|
members: Member[];
|
|
joinRequests: JoinRequest[];
|
|
allCompanies: CompanyInfo[];
|
|
allUsers: DashboardUser[];
|
|
allJoinRequests: JoinRequest[];
|
|
auditLogs: AuditLog[];
|
|
resources: ResourceData | null;
|
|
}
|
|
|
|
export async function renderB01Dashboard(root: HTMLElement): Promise<void> {
|
|
showLoadingOverlay();
|
|
try {
|
|
const user = await fetchDashboardMe();
|
|
const state: DashboardState = {
|
|
user,
|
|
userProjects: [],
|
|
companyProjects: [],
|
|
allProjects: [],
|
|
company: null,
|
|
members: [],
|
|
joinRequests: [],
|
|
allCompanies: [],
|
|
allUsers: [],
|
|
allJoinRequests: [],
|
|
auditLogs: [],
|
|
resources: null,
|
|
};
|
|
await loadRoleData(state);
|
|
root.innerHTML = "";
|
|
root.append(buildPage(state));
|
|
} catch (error) {
|
|
showToast(error instanceof Error ? error.message : L("B01_Dashboard_LoadFailed"), "error");
|
|
} finally {
|
|
hideLoadingOverlay();
|
|
}
|
|
}
|
|
|
|
async function loadRoleData(state: DashboardState): Promise<void> {
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
const results = await Promise.allSettled([
|
|
fetchAllCompanies(),
|
|
fetchAllUsers(),
|
|
fetchAllJoinRequests(),
|
|
fetchAuditLogs(),
|
|
fetchSystemResources(7),
|
|
fetchAllProjects(),
|
|
]);
|
|
state.allCompanies = results[0].status === "fulfilled" ? results[0].value : [];
|
|
state.allUsers = results[1].status === "fulfilled" ? results[1].value : [];
|
|
state.allJoinRequests = results[2].status === "fulfilled" ? results[2].value : [];
|
|
state.auditLogs = results[3].status === "fulfilled" ? results[3].value : [];
|
|
state.resources = results[4].status === "fulfilled" ? results[4].value : null;
|
|
state.allProjects = results[5].status === "fulfilled" ? results[5].value : [];
|
|
|
|
results.forEach((res, idx) => {
|
|
if (res.status === "rejected") {
|
|
console.error(`System Admin API failed at index ${idx}:`, res.reason);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
const [projects, company] = await Promise.all([
|
|
fetchUserProjects().catch(() => []),
|
|
fetchUserCompany().catch(() => null),
|
|
]);
|
|
state.userProjects = projects;
|
|
state.company = company;
|
|
if (state.user.role === "ADMIN") {
|
|
const [members, requests, companyProjects] = await Promise.all([
|
|
fetchCompanyMembers().catch(() => []),
|
|
fetchCompanyJoinRequests().catch(() => []),
|
|
fetchCompanyProjects().catch(() => []),
|
|
]);
|
|
state.members = members;
|
|
state.joinRequests = requests;
|
|
state.companyProjects = companyProjects;
|
|
}
|
|
}
|
|
|
|
function buildSystemSettingsPanel(): HTMLElement {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__actions";
|
|
wrap.append(
|
|
createButton({
|
|
label: L("B01_Dashboard_MasterElements"),
|
|
variant: "ghost",
|
|
onClick: () => navigateTo(ROUTES.M01_MASTER_DATA),
|
|
}),
|
|
createButton({
|
|
label: L("B01_Dashboard_MasterTemplate"),
|
|
variant: "ghost",
|
|
onClick: () => navigateTo(ROUTES.M02_MASTER_TEMPLATE),
|
|
}),
|
|
);
|
|
return wrap;
|
|
}
|
|
|
|
/** 구조물 관리 카드 — 개인용은 모두 · 회사용은 회사 관리자와 시스템 관리자(21-0) */
|
|
function buildStructurePanel(state: DashboardState): HTMLElement {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__actions";
|
|
const go = (layer: "company" | "personal", label: string): HTMLElement =>
|
|
createButton({
|
|
label,
|
|
variant: "ghost",
|
|
onClick: () => navigateTo(ROUTES.M02_MASTER_TEMPLATE, { layer }),
|
|
});
|
|
if (state.user.role === "ADMIN" || state.user.role === "SYSTEM_ADMIN") {
|
|
wrap.append(go("company", L("B01_Dashboard_StructureCompany")));
|
|
}
|
|
wrap.append(go("personal", L("B01_Dashboard_StructurePersonal")));
|
|
return wrap;
|
|
}
|
|
|
|
function buildPage(state: DashboardState): HTMLElement {
|
|
// 제목·여백은 공용 템플릿을 따른다 (2026-09-06 사용자 지시) — B02 등 다른 화면과 같은 모양.
|
|
const page = document.createElement("div");
|
|
|
|
const grid = document.createElement("div");
|
|
grid.className = "b01-dashboard__grid";
|
|
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
// 리소스 현황 오른쪽에 시스템 설정 — 앞으로 다른 설정 단추도 이 칸에 듦 (2026-09-15 브레인 Z01).
|
|
const topRow = document.createElement("div");
|
|
topRow.className = "b01-dashboard__top-row";
|
|
topRow.append(
|
|
section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources)),
|
|
section(L("B01_Dashboard_SystemSettings"), buildSystemSettingsPanel()),
|
|
);
|
|
grid.append(
|
|
topRow,
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.allProjects, state.user), true, [
|
|
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
|
]),
|
|
buildTempUploadSection(),
|
|
section(L("B01_Dashboard_StructureManage"), buildStructurePanel(state)),
|
|
section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [
|
|
createButton({ label: "+", onClick: () => openAddMemberModal() }),
|
|
]),
|
|
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.allJoinRequests, true), true),
|
|
section(L("B01_Dashboard_Companies"), companyTable(state.allCompanies, state.user), true, [
|
|
createButton({ label: "+", onClick: () => openCreateCompanyModal() }),
|
|
]),
|
|
);
|
|
} else if (state.user.role === "ADMIN") {
|
|
grid.append(
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.companyProjects, state.user), true, [
|
|
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
|
]),
|
|
buildTempUploadSection(),
|
|
section(L("B01_Dashboard_StructureManage"), buildStructurePanel(state)),
|
|
section(L("B01_Dashboard_Members"), memberTable(state.members, state.user), true, [
|
|
createButton({ label: "+", onClick: () => openAddMemberModal() }),
|
|
]),
|
|
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.joinRequests, false), true),
|
|
section(L("B01_Dashboard_Company"), buildCompanyPanel(state), true),
|
|
);
|
|
} else {
|
|
grid.append(
|
|
section(L("B01_Dashboard_Projects"), projectTable(state.userProjects, state.user), true, [
|
|
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
|
]),
|
|
// 임시 보관함 — 프로젝트를 만들기 전에 자료를 올려 두는 곳이라 프로젝트 목록
|
|
// 바로 아래에 둔다(2026-08-08 사용자 지시).
|
|
buildTempUploadSection(),
|
|
section(L("B01_Dashboard_StructureManage"), buildStructurePanel(state)),
|
|
section(L("B01_Dashboard_Company"), buildCompanyPanel(state), true),
|
|
);
|
|
}
|
|
|
|
grid.append(
|
|
section(L("B01_Dashboard_Profile"), buildProfileForm(state.user)),
|
|
section(L("B01_Account_Section_Security"), buildSecurityForm()),
|
|
);
|
|
// 시스템 로그는 맨 아래 (2026-09-06 사용자 지시).
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
grid.append(section(L("B01_Dashboard_AuditLogs"), auditLogTable(state.auditLogs), true));
|
|
}
|
|
page.append(grid);
|
|
const layout = createGeneralLayout({
|
|
pageClass: "b01-dashboard",
|
|
title: L("B01_Dashboard_Title"),
|
|
subtitle: L("B01_Dashboard_Subtitle"),
|
|
content: page,
|
|
});
|
|
// 역할 배지는 제목 줄 오른쪽에 둔다(자리는 CSS 격자가 잡는다).
|
|
const tag = createTag(
|
|
roleLabel(state.user.role),
|
|
state.user.role === "SYSTEM_ADMIN" ? "accent" : "neutral",
|
|
);
|
|
tag.classList.add("b01-dashboard__role");
|
|
layout.root.querySelector(".ui-general-layout__header")?.append(tag);
|
|
return layout.root;
|
|
}
|