- 해시 ?tab= 탭 줄(B08 단추 꼴) · 고른 탭 자료만 받음 · 설정 카드는 탭 줄 위 · 회사 고르기 알약은 탭 줄 오른쪽 하나 - 회사 없음 진입 두 곳(router · M02 Layer)은 ?tab=company · 회사 관리자 프로젝트 목록 한 번만 받음 - 개요 숫자 카드(단계별 · 실패 · 재확인 · 가입 대기 · 보관함 · 리소스) · 프로젝트 표 칸 보강 · 찾기 · 거르기 · 쪽 넘김 · 진행 현황 - 회사 정보 칸(사업자번호 · 대표 · 상태) · 팀원 읽기 · 자산 표 · 내 계정 마지막 로그인 · 시스템 리소스 7·30·90일 · 활성 추이 · 전체 회사 · 사용자 · 로그 쪽 · 고객 문의 - 시험 test_b01_dashboard_tabs 신규 · test_b01_settings_cards 고침 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
104 lines
4.3 KiB
TypeScript
104 lines
4.3 KiB
TypeScript
/* =============================================================================
|
|
* 「개요」 탭(92장) — 로그인 직후 첫 화면. 이미 받는 자료의 숫자만 · 누르면 해당 탭.
|
|
* ========================================================================== */
|
|
|
|
import { section } from "@ui/ui_template_general_blocks";
|
|
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
|
import type { ProjectItem, ResourceData } from "./B01_Dashboard_Api_Fetch";
|
|
import { L } from "./B01_Dashboard_UI_Common";
|
|
import type { DashboardState } from "./B01_Dashboard_UI_Page";
|
|
import { summarizeProjects } from "./B01_Dashboard_UI_ProjectStats";
|
|
import { goTab, type DashboardTab } from "./B01_Dashboard_UI_Tabs";
|
|
|
|
/** 숫자 칸 하나 — 누르면 `tab`(덧인자 `extra`)으로 */
|
|
function tile(
|
|
label: string,
|
|
value: string | number,
|
|
tab: DashboardTab,
|
|
extra: Record<string, string> = {},
|
|
alert = false,
|
|
): HTMLElement {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "b01-dashboard__metric b01-dashboard__metric--link";
|
|
button.classList.toggle("b01-dashboard__metric--alert", alert);
|
|
button.dataset.b01Tile = label;
|
|
const labelEl = document.createElement("span");
|
|
labelEl.className = "b01-dashboard__metric-label";
|
|
labelEl.textContent = label;
|
|
const valueEl = document.createElement("span");
|
|
valueEl.className = "b01-dashboard__metric-value";
|
|
valueEl.textContent = String(value);
|
|
button.append(labelEl, valueEl);
|
|
button.addEventListener("click", () => goTab(tab, extra));
|
|
return button;
|
|
}
|
|
|
|
function grid(tiles: HTMLElement[]): HTMLElement {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__overview-grid";
|
|
wrap.append(...tiles);
|
|
return wrap;
|
|
}
|
|
|
|
/** 소제목 + 숫자 칸 묶음 */
|
|
function block(title: string, tiles: HTMLElement[]): HTMLElement {
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "b01-dashboard__overview-block";
|
|
const heading = document.createElement("p");
|
|
heading.className = "b01-dashboard__overview-title";
|
|
heading.textContent = title;
|
|
wrap.append(heading, grid(tiles));
|
|
return wrap;
|
|
}
|
|
|
|
function percent(value?: number | null): string {
|
|
return value == null ? "-" : `${Math.round(value)}%`;
|
|
}
|
|
|
|
function resourceTiles(resources: ResourceData | null): HTMLElement[] {
|
|
const now = resources?.current;
|
|
return [
|
|
tile(L("B01_Dashboard_Metric_Cpu"), percent(now?.cpu_usage_percent), "system"),
|
|
tile(L("B01_Dashboard_Metric_Memory"), percent(now?.memory_usage_percent), "system"),
|
|
tile(L("B01_Dashboard_Metric_Disk"), percent(now?.disk_usage_percent), "system"),
|
|
tile(L("B01_Dashboard_Metric_ActiveUsers"), now?.active_user_count ?? 0, "system"),
|
|
tile(L("B01_Dashboard_Metric_ActiveProjects"), now?.active_project_count ?? 0, "system"),
|
|
];
|
|
}
|
|
|
|
/** 가입 대기 수 — 시스템 관리자는 고른 회사 것 */
|
|
function joinPending(state: DashboardState): number {
|
|
return (state.user.role === "SYSTEM_ADMIN" ? state.allJoinRequests : state.joinRequests).length;
|
|
}
|
|
|
|
/** 개요 컨테이너들 — `projects` 는 역할별 프로젝트 목록(프로젝트 탭과 같은 목록) */
|
|
export function buildOverview(state: DashboardState, projects: ProjectItem[]): HTMLElement[] {
|
|
const sum = summarizeProjects(projects);
|
|
const steps = workflowSteps();
|
|
const status = [
|
|
tile(L("B01_Ov_Total"), sum.total, "projects"),
|
|
tile(L("B01_Ov_Failed"), sum.failed, "projects", { filter: "failed" }, sum.failed > 0),
|
|
tile(L("B01_Ov_Stale"), sum.stale, "projects", { filter: "stale" }, sum.stale > 0),
|
|
];
|
|
const stages = sum.byStep.map((count, index) =>
|
|
tile(steps[index] ?? String(index + 1), count, "projects", { filter: `stage-${index}` }),
|
|
);
|
|
const todo: HTMLElement[] = [];
|
|
if (state.user.role !== "USER")
|
|
todo.push(tile(L("B01_Ov_JoinPending"), joinPending(state), "company"));
|
|
todo.push(tile(L("B01_Ov_TempBox"), state.tempCount, "projects"));
|
|
|
|
const body = document.createElement("div");
|
|
body.className = "b01-dashboard__overview";
|
|
body.append(grid(status), block(L("B01_Ov_ByStage"), stages));
|
|
const sections = [
|
|
section(L("B01_Ov_ProjectStatus"), body, true),
|
|
section(L("B01_Ov_Todo"), grid(todo), true),
|
|
];
|
|
if (state.user.role === "SYSTEM_ADMIN") {
|
|
sections.push(section(L("B01_Ov_Resources"), grid(resourceTiles(state.resources)), true));
|
|
}
|
|
return sections;
|
|
}
|