Files
Aislo/B01_Dashboard/B01_Dashboard_UI_Overview.ts
T
eomsangdonandClaude Sonnet 5.5 2a2c4d0f62 feat(B01): 대시보드 항목별 탭 — 해시 탭 줄 · 개요 · 프로젝트 · 회사 · 내 계정 · 시스템 (92-1·92-2)
- 해시 ?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
2026-10-08 13:58:34 +09:00

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;
}