Revert "feat(B01): 대시보드 좌측 패널 — 권한별 항목 목록 · 고른 항목 컨테이너만 우측에 · 고른 항목 해시 유지 (99-2)"
This reverts commit d69686a0a8.
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import { section } from "@ui/ui_template_general_blocks";
|
||||
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
||||
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import {
|
||||
fetchAllCompanies,
|
||||
@@ -43,12 +42,6 @@ import { buildProfileForm, buildSecurityForm } from "./B01_Dashboard_UI_Profile"
|
||||
import { openCompanyPicker } from "./B01_Dashboard_UI_CompanyPick";
|
||||
import { projectTable } from "./B01_Dashboard_UI_Projects";
|
||||
import { buildResourcePanel } from "./B01_Dashboard_UI_Resources";
|
||||
import {
|
||||
buildSidePanel,
|
||||
pickActiveKey,
|
||||
rememberActiveKey,
|
||||
type PanelItem,
|
||||
} from "./B01_Dashboard_UI_SidePanel";
|
||||
import { buildTempUploadSection } from "./B01_Dashboard_UI_TempUpload";
|
||||
import { buildKnowledgeChat } from "./B01_Dashboard_UI_Knowledge_Chat";
|
||||
import "./B01_Dashboard_UI_Style.css";
|
||||
@@ -236,133 +229,87 @@ function buildSystemSettingsSection(state: DashboardState): HTMLElement {
|
||||
return card;
|
||||
}
|
||||
|
||||
/** 좌측 패널 항목 — 권한별 · 항목 하나 = 지금 컨테이너 그대로(누를 때 만듦) */
|
||||
function panelItems(state: DashboardState): PanelItem[] {
|
||||
const one = (key: string, label: string, make: () => HTMLElement): PanelItem => ({
|
||||
key,
|
||||
label,
|
||||
build: () => [make()],
|
||||
});
|
||||
const projectsAdd = [
|
||||
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
||||
];
|
||||
const myInfo: PanelItem = {
|
||||
key: "profile",
|
||||
label: L("B01_Panel_MyInfo"),
|
||||
build: () => [
|
||||
section(L("B01_Dashboard_Profile"), buildProfileForm(state.user)),
|
||||
section(L("B01_Account_Section_Security"), buildSecurityForm()),
|
||||
],
|
||||
};
|
||||
const settings = one("settings", L("B01_Dashboard_SystemSettings"), () =>
|
||||
buildSystemSettingsSection(state),
|
||||
);
|
||||
const temp = one("temp", L("B01_Temp_Section"), () => buildTempUploadSection());
|
||||
if (state.user.role === "SYSTEM_ADMIN") {
|
||||
// 회사 알약이 붙은 컨테이너는 고른 회사 것
|
||||
const picked = state.allCompanies.filter((c) => c.id === state.adminCompanyId);
|
||||
return [
|
||||
one("resources", L("B01_Dashboard_Resources"), () =>
|
||||
section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true),
|
||||
),
|
||||
settings,
|
||||
one("projects", L("B01_Dashboard_Projects"), () =>
|
||||
withCompanyPill(
|
||||
section(
|
||||
L("B01_Dashboard_Projects"),
|
||||
projectTable(state.allProjects, state.user),
|
||||
true,
|
||||
projectsAdd,
|
||||
),
|
||||
state,
|
||||
),
|
||||
),
|
||||
temp,
|
||||
one("users", L("B01_Dashboard_Users"), () =>
|
||||
withCompanyPill(
|
||||
section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [
|
||||
createButton({
|
||||
label: "+",
|
||||
onClick: () => openAddMemberModal(undefined, state.adminCompanyId),
|
||||
}),
|
||||
]),
|
||||
state,
|
||||
),
|
||||
),
|
||||
one("requests", L("B01_Dashboard_JoinRequests"), () =>
|
||||
withCompanyPill(
|
||||
section(
|
||||
L("B01_Dashboard_JoinRequests"),
|
||||
joinRequestTable(state.allJoinRequests, true),
|
||||
true,
|
||||
),
|
||||
state,
|
||||
),
|
||||
),
|
||||
one("companies", L("B01_Dashboard_Companies"), () =>
|
||||
withCompanyPill(
|
||||
section(L("B01_Dashboard_Companies"), companyTable(picked, state.user), true, [
|
||||
createButton({ label: "+", onClick: () => openCreateCompanyModal() }),
|
||||
]),
|
||||
state,
|
||||
),
|
||||
),
|
||||
myInfo,
|
||||
one("logs", L("B01_Dashboard_AuditLogs"), () =>
|
||||
section(L("B01_Dashboard_AuditLogs"), auditLogTable(state.auditLogs), true),
|
||||
),
|
||||
];
|
||||
}
|
||||
const company = one("company", L("B01_Dashboard_Company"), () =>
|
||||
section(L("B01_Dashboard_Company"), buildCompanyPanel(state), true),
|
||||
);
|
||||
if (state.user.role === "ADMIN") {
|
||||
return [
|
||||
settings,
|
||||
one("projects", L("B01_Dashboard_Projects"), () =>
|
||||
section(
|
||||
L("B01_Dashboard_Projects"),
|
||||
projectTable(state.companyProjects, state.user),
|
||||
true,
|
||||
projectsAdd,
|
||||
),
|
||||
),
|
||||
temp,
|
||||
one("members", L("B01_Dashboard_Members"), () =>
|
||||
section(L("B01_Dashboard_Members"), memberTable(state.members, state.user), true, [
|
||||
createButton({ label: "+", onClick: () => openAddMemberModal() }),
|
||||
]),
|
||||
),
|
||||
one("requests", L("B01_Dashboard_JoinRequests"), () =>
|
||||
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.joinRequests, false), true),
|
||||
),
|
||||
company,
|
||||
myInfo,
|
||||
];
|
||||
}
|
||||
return [
|
||||
settings,
|
||||
one("projects", L("B01_Dashboard_Projects"), () =>
|
||||
section(
|
||||
L("B01_Dashboard_Projects"),
|
||||
projectTable(state.userProjects, state.user),
|
||||
true,
|
||||
projectsAdd,
|
||||
),
|
||||
),
|
||||
temp,
|
||||
company,
|
||||
myInfo,
|
||||
];
|
||||
}
|
||||
|
||||
function buildPage(state: DashboardState): HTMLElement {
|
||||
const items = panelItems(state);
|
||||
const detail = document.createElement("div");
|
||||
detail.className = "b01-dashboard__grid";
|
||||
// 제목·여백은 공용 템플릿을 따른다 (2026-09-06 사용자 지시) — 본문은 고른 항목 하나.
|
||||
// 제목·여백은 공용 템플릿을 따른다 (2026-09-06 사용자 지시) — B02 등 다른 화면과 같은 모양.
|
||||
const page = document.createElement("div");
|
||||
page.append(detail, buildKnowledgeChat());
|
||||
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "b01-dashboard__grid";
|
||||
|
||||
if (state.user.role === "SYSTEM_ADMIN") {
|
||||
// 시스템 설정은 리소스 현황 아래(29-1) · 회사 알약이 붙은 컨테이너는 고른 회사 것
|
||||
const picked = state.allCompanies.filter((c) => c.id === state.adminCompanyId);
|
||||
grid.append(
|
||||
section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true),
|
||||
buildSystemSettingsSection(state),
|
||||
withCompanyPill(
|
||||
section(L("B01_Dashboard_Projects"), projectTable(state.allProjects, state.user), true, [
|
||||
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
||||
]),
|
||||
state,
|
||||
),
|
||||
buildTempUploadSection(),
|
||||
withCompanyPill(
|
||||
section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [
|
||||
createButton({
|
||||
label: "+",
|
||||
onClick: () => openAddMemberModal(undefined, state.adminCompanyId),
|
||||
}),
|
||||
]),
|
||||
state,
|
||||
),
|
||||
withCompanyPill(
|
||||
section(
|
||||
L("B01_Dashboard_JoinRequests"),
|
||||
joinRequestTable(state.allJoinRequests, true),
|
||||
true,
|
||||
),
|
||||
state,
|
||||
),
|
||||
withCompanyPill(
|
||||
section(L("B01_Dashboard_Companies"), companyTable(picked, state.user), true, [
|
||||
createButton({ label: "+", onClick: () => openCreateCompanyModal() }),
|
||||
]),
|
||||
state,
|
||||
),
|
||||
);
|
||||
} else if (state.user.role === "ADMIN") {
|
||||
// 시스템 설정은 맨 위 한 줄(27-3a)
|
||||
grid.append(
|
||||
buildSystemSettingsSection(state),
|
||||
section(L("B01_Dashboard_Projects"), projectTable(state.companyProjects, state.user), true, [
|
||||
createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }),
|
||||
]),
|
||||
buildTempUploadSection(),
|
||||
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(
|
||||
buildSystemSettingsSection(state),
|
||||
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_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);
|
||||
page.append(buildKnowledgeChat());
|
||||
const layout = createGeneralLayout({
|
||||
pageClass: "b01-dashboard",
|
||||
title: L("B01_Dashboard_Title"),
|
||||
@@ -376,23 +323,5 @@ function buildPage(state: DashboardState): HTMLElement {
|
||||
);
|
||||
tag.classList.add("b01-dashboard__role");
|
||||
layout.root.querySelector(".ui-general-layout__header")?.append(tag);
|
||||
|
||||
// B 그룹 공용 좌측 패널(M01 · B03 과 같은 오버레이) — 열리면 본문을 밀어냄
|
||||
const panel = buildSidePanel(items, pickActiveKey(items), (key) => show(key, true));
|
||||
const overlays = createWorkflowOverlays({
|
||||
title: L("B01_Panel_Title"),
|
||||
optionsContent: panel.root,
|
||||
showProjectName: false,
|
||||
onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen),
|
||||
});
|
||||
layout.root.append(overlays.root);
|
||||
function show(key: string, picked: boolean): void {
|
||||
detail.replaceChildren(...(items.find((item) => item.key === key) ?? items[0]).build());
|
||||
panel.setActive(key);
|
||||
rememberActiveKey(key);
|
||||
// 좁은 화면(패널이 본문을 덮음)에서는 고르면 접음
|
||||
if (picked && window.matchMedia("(max-width: 860px)").matches) overlays.setTitleOpen(false);
|
||||
}
|
||||
show(pickActiveKey(items), false);
|
||||
return layout.root;
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { currentQuery } from "../A00_Common/router";
|
||||
|
||||
/** 좌측 패널 항목 하나 — 누르면 우측 상세에 `build()` 결과만 보임 */
|
||||
export interface PanelItem {
|
||||
key: string;
|
||||
label: string;
|
||||
build: () => HTMLElement[];
|
||||
}
|
||||
|
||||
/** 해시 `?tab=` 에 남은 항목 — 없거나 이 권한에 없는 항목이면 첫 항목 */
|
||||
export function pickActiveKey(items: readonly PanelItem[]): string {
|
||||
const saved = currentQuery().get("tab");
|
||||
return items.find((item) => item.key === saved)?.key ?? items[0].key;
|
||||
}
|
||||
|
||||
/** 고른 항목을 해시에 남김 — hashchange 를 일으키지 않음(다시 그리면 같은 항목으로 돌아옴) */
|
||||
export function rememberActiveKey(key: string): void {
|
||||
history.replaceState(null, "", `#/${ROUTES.B01_ACCOUNT}?tab=${encodeURIComponent(key)}`);
|
||||
}
|
||||
|
||||
/** B 그룹 공용 좌측 패널 목록 모양(`ui-sidebar-*`) — 줄 하나 = 항목 하나 */
|
||||
export function buildSidePanel(
|
||||
items: readonly PanelItem[],
|
||||
activeKey: string,
|
||||
onPick: (key: string) => void,
|
||||
): { root: HTMLElement; setActive: (key: string) => void } {
|
||||
const list = document.createElement("div");
|
||||
list.className = "ui-sidebar-list b01-dashboard__panel-list";
|
||||
const rows = new Map<string, HTMLButtonElement>();
|
||||
for (const item of items) {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = "ui-sidebar-row b01-dashboard__panel-row";
|
||||
row.textContent = item.label;
|
||||
row.addEventListener("click", () => onPick(item.key));
|
||||
rows.set(item.key, row);
|
||||
list.append(row);
|
||||
}
|
||||
const setActive = (key: string): void =>
|
||||
rows.forEach((row, rowKey) => row.classList.toggle("is-active", rowKey === key));
|
||||
setActive(activeKey);
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-sidebar-panel b01-dashboard__panel";
|
||||
root.append(list);
|
||||
return { root, setActive };
|
||||
}
|
||||
@@ -683,27 +683,3 @@
|
||||
outline: 2px solid var(--color-royal-amethyst, #6843a7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 좌측 패널(공용 오버레이) — 열리면 본문을 밀어내고 접히면 손잡이 폭만 남김(B03 과 같은 값) */
|
||||
.b01-dashboard .ui-general-layout__inner {
|
||||
transition: padding-left var(--transition-base);
|
||||
}
|
||||
|
||||
.b01-dashboard.is-options-open .ui-general-layout__inner {
|
||||
padding-left: min(var(--wf-left-panel-width), calc(100vw - var(--spacing-48)));
|
||||
}
|
||||
|
||||
.b01-dashboard:not(.is-options-open) .ui-general-layout__inner {
|
||||
padding-left: var(--spacing-24);
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.b01-dashboard.is-options-open .ui-general-layout__inner {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.b01-dashboard__panel-list {
|
||||
max-height: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -98,15 +98,14 @@ def test_회사_관리자_개인_카드_하나():
|
||||
|
||||
def test_모든_역할_한_줄_통째로_접힘():
|
||||
src = _text(PAGE)
|
||||
build = _fn(src, "panelItems")
|
||||
build = _fn(src, "buildPage")
|
||||
# 세 갈래 모두 한 번씩 · 시스템 관리자는 리소스 현황 아래(29-1) · 프로젝트 위
|
||||
assert "buildSystemSettingsSection(state)" in build
|
||||
assert len(re.findall(r"^\s+settings,$", build, re.M)) == 3
|
||||
sys_part = build.split('if (state.user.role === "ADMIN")')[0]
|
||||
assert build.count("buildSystemSettingsSection(state)") == 3
|
||||
sys_part = build.split('} else if (state.user.role === "ADMIN")')[0]
|
||||
assert (
|
||||
sys_part.index('one("resources"')
|
||||
< sys_part.index(" settings,")
|
||||
< sys_part.index('one("projects"')
|
||||
sys_part.index('L("B01_Dashboard_Resources")')
|
||||
< sys_part.index("buildSystemSettingsSection(state)")
|
||||
< sys_part.index('L("B01_Dashboard_Projects")')
|
||||
)
|
||||
assert "StructureManage" not in src and "buildStructurePanel" not in src
|
||||
assert "top-row" not in src
|
||||
@@ -133,11 +132,11 @@ def test_회사_알약_시스템_관리자만():
|
||||
for call in ("fetchAllUsers(companyId)", "fetchCompanyJoinRequests(companyId)",
|
||||
"fetchCompanyProjects(companyId)"): # fmt: skip
|
||||
assert call in load, call
|
||||
build = _fn(src, "panelItems")
|
||||
build = _fn(src, "buildPage")
|
||||
# 회사 따라 바뀌는 컨테이너 넷(프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리)마다 알약
|
||||
sys_part = build.split('if (state.user.role === "ADMIN")')[0]
|
||||
sys_part = build.split('} else if (state.user.role === "ADMIN")')[0]
|
||||
assert sys_part.count("withCompanyPill(") == 4
|
||||
assert "withCompanyPill" not in build.split('if (state.user.role === "ADMIN")')[1]
|
||||
assert "withCompanyPill" not in build.split('} else if (state.user.role === "ADMIN")')[1]
|
||||
assert "companyTable(picked, state.user)" in build
|
||||
assert "openAddMemberModal(undefined, state.adminCompanyId)" in build
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"""B01 대시보드 좌측 패널 시험(99-2) — 권한별 항목 · 해시 유지 · 공용 오버레이. 화면 조작은 ORCA."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B01 = ROOT / "B01_Dashboard"
|
||||
|
||||
|
||||
def _text(name: str) -> str:
|
||||
return (B01 / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _items(role: str) -> list[str]:
|
||||
"""panelItems 안 권한별 return 목록의 항목 이름(key) 차례."""
|
||||
body = re.search(r"^function panelItems\([\s\S]*?^}", _text("B01_Dashboard_UI_Page.ts"), re.M)
|
||||
assert body
|
||||
lists = re.findall(r"^( *)return \[\n([\s\S]*?)^\1\];", body.group(0), re.M)
|
||||
part = lists[["SYSTEM_ADMIN", "ADMIN", "USER"].index(role)][1]
|
||||
found = re.findall(r'one\(\s*"(\w+)"|^ {4,6}(settings|temp|company|myInfo),', part, re.M)
|
||||
names = [a or b for a, b in found]
|
||||
return ["profile" if n == "myInfo" else n for n in names]
|
||||
|
||||
|
||||
def test_권한별_항목_차례():
|
||||
assert _items("SYSTEM_ADMIN") == [
|
||||
"resources", "settings", "projects", "temp", "users", "requests",
|
||||
"companies", "profile", "logs",
|
||||
] # fmt: skip
|
||||
assert _items("ADMIN") == [
|
||||
"settings", "projects", "temp", "members", "requests", "company", "profile",
|
||||
] # fmt: skip
|
||||
assert _items("USER") == ["settings", "projects", "temp", "company", "profile"]
|
||||
|
||||
|
||||
def test_고른_항목_해시에_남김():
|
||||
panel = _text("B01_Dashboard_UI_SidePanel.ts")
|
||||
assert 'currentQuery().get("tab")' in panel
|
||||
assert "history.replaceState" in panel # hashchange 를 안 일으킴
|
||||
page = _text("B01_Dashboard_UI_Page.ts")
|
||||
assert "show(pickActiveKey(items), false)" in page # 다시 그려도 같은 항목
|
||||
assert 'const ADMIN_COMPANY_KEY = "aislo.adminCompany";' in page
|
||||
|
||||
|
||||
def test_공용_좌측_패널_부품():
|
||||
page = _text("B01_Dashboard_UI_Page.ts")
|
||||
assert "createWorkflowOverlays" in page and "showProjectName: false" in page
|
||||
assert "is-options-open" in page
|
||||
panel = _text("B01_Dashboard_UI_SidePanel.ts")
|
||||
assert "ui-sidebar-list" in panel and "ui-sidebar-row" in panel
|
||||
# 옛 탭 줄 · 해시 탭 부품 없음
|
||||
assert not (B01 / "B01_Dashboard_UI_Tabs.ts").exists()
|
||||
# 좁은 화면(≤860) — 고르면 접음
|
||||
assert "max-width: 860px" in page
|
||||
@@ -128,8 +128,6 @@ export const ui_locales_b1 = {
|
||||
B01_Temp_Delete_Failed: ["삭제에 실패했습니다.", "Failed to delete."],
|
||||
|
||||
B01_Dashboard_Profile: ["기본정보", "Profile"],
|
||||
B01_Panel_Title: ["대시보드", "Dashboard"],
|
||||
B01_Panel_MyInfo: ["내 정보", "My info"],
|
||||
B01_Dashboard_SaveProfile: ["기본정보 저장", "Save profile"],
|
||||
B01_Dashboard_Table_Project: ["프로젝트명", "Project"],
|
||||
B01_Dashboard_Table_Region: ["지역", "Region"],
|
||||
|
||||
Reference in New Issue
Block a user