Files
Aislo/B01_Dashboard/B01_Dashboard_UI_System.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

135 lines
5.2 KiB
TypeScript

/* =============================================================================
* 「시스템」 탭(92장 · 시스템 관리자만) — 리소스(7 · 30 · 90일) · 활성 추이 · 전체 회사 ·
* 전체 사용자 · 시스템 로그(쪽 넘김) · 고객 문의. 모두 이미 있는 API.
* ========================================================================== */
import { createButton, createTag, showToast, type TagVariant } from "@ui/ui_template_elements";
import { section, text } from "@ui/ui_template_general_blocks";
import {
fetchAuditLogPage,
fetchSystemResources,
type AuditLog,
type ResourceData,
type SupportRequest,
} from "./B01_Dashboard_Api_Fetch";
import { allUserTable, auditLogTable } from "./B01_Dashboard_UI_Admin";
import { companyTable } from "./B01_Dashboard_UI_Company";
import { formatDate, formatTime, L, stackedCell } from "./B01_Dashboard_UI_Common";
import type { DashboardState } from "./B01_Dashboard_UI_Page";
import { buildPager, createPagedTable } from "./B01_Dashboard_UI_Paging";
import { buildActivityChart, buildResourcePanel } from "./B01_Dashboard_UI_Resources";
/** 리소스 기간(일) — 백엔드 최대 90 */
export const RESOURCE_DAYS = [7, 30, 90] as const;
/** 시스템 로그 한 쪽 줄 수 */
export const AUDIT_PAGE_SIZE = 20;
const INQUIRY_TAG: Record<SupportRequest["status"], TagVariant> = {
NEW: "warning",
REVIEWED: "accent",
RESOLVED: "success",
DELETED: "neutral",
};
/** 기간 고르기 + 리소스 현황 + 활성 추이 — 기간을 바꾸면 이 두 컨테이너만 새로 받음 */
function buildResourceSections(initial: ResourceData | null): HTMLElement[] {
const bar = document.createElement("div");
bar.className = "b01-dashboard__period";
const panelHost = document.createElement("div");
const trendHost = document.createElement("div");
let days: number = RESOURCE_DAYS[0];
let latest = 0;
const draw = (data: ResourceData | null): void => {
panelHost.replaceChildren(buildResourcePanel(data, days));
trendHost.replaceChildren(buildActivityChart(data, days));
};
const drawBar = (): void => {
bar.replaceChildren(
...RESOURCE_DAYS.map((value) => {
const button = createButton({
label: L("B01_Sy_Days").replace("{days}", String(value)),
variant: value === days ? "filled" : "ghost",
onClick: () => void pick(value),
});
button.setAttribute("aria-pressed", String(value === days));
button.dataset.b01Days = String(value);
return button;
}),
);
};
const pick = async (value: number): Promise<void> => {
if (value === days) return;
days = value;
drawBar();
const mine = ++latest;
try {
const data = await fetchSystemResources(value);
if (mine === latest) draw(data);
} catch (error) {
if (mine === latest) showToast(error instanceof Error ? error.message : "", "error");
}
};
drawBar();
draw(initial);
const resource = section(L("B01_Dashboard_Resources"), panelHost, true);
resource.querySelector(".ui-card__body")?.prepend(bar);
return [resource, section(L("B01_Sy_ActiveTrend"), trendHost, true)];
}
/** 시스템 로그 — 서버가 쪽으로 나눠 줌(전체 건수 포함) */
function buildAuditSection(first: { total: number; items: AuditLog[] }): HTMLElement {
const host = document.createElement("div");
const draw = (page: number, data: { total: number; items: AuditLog[] }): void => {
const pages = Math.max(1, Math.ceil(data.total / AUDIT_PAGE_SIZE));
host.replaceChildren(
auditLogTable(data.items),
buildPager(page, pages, data.total, (next) => {
fetchAuditLogPage(next, AUDIT_PAGE_SIZE)
.then((result) => draw(next, result))
.catch((error) => showToast(error instanceof Error ? error.message : "", "error"));
}),
);
};
draw(1, first);
return section(L("B01_Dashboard_AuditLogs"), host, true);
}
function inquiryTable(requests: SupportRequest[]): HTMLElement {
return createPagedTable(
[
L("B01_Sy_Received"),
L("B01_Sy_Sender"),
L("B01_Dashboard_Table_Company"),
L("B01_Sy_Subject"),
L("B01_Pj_Message"),
L("B01_Dashboard_Table_Status"),
],
(request: SupportRequest) => {
const message = document.createElement("span");
message.className = "b01-dashboard__ellipsis";
message.textContent = request.message;
message.title = request.message;
return [
stackedCell(formatDate(request.created_at), formatTime(request.created_at)),
text([request.user_name, request.user_email].filter(Boolean).join(" · ")),
text(request.company_name),
text(request.subject),
message,
createTag(L(`B01_Sy_Inquiry_${request.status}`), INQUIRY_TAG[request.status]),
];
},
requests,
).root;
}
export function buildSystemTab(state: DashboardState): HTMLElement[] {
return [
...buildResourceSections(state.resources),
section(L("B01_Sy_AllCompanies"), companyTable(state.allCompanies, state.user), true),
section(L("B01_Sy_AllUsers"), allUserTable(state.systemUsers), true),
buildAuditSection({ total: state.auditTotal, items: state.auditLogs }),
section(L("B01_Sy_Inquiries"), inquiryTable(state.supportRequests), true),
];
}