/* ============================================================================= * 「개요」 탭(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 = {}, 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; }