/* ============================================================================= * B01_Dashboard_UI_View_SysUsers.ts * 시스템 관리 › 사용자(549-2) — 전체 사용자 표 · 거르기(회사 · 역할 · 찾기) · [+ 사용자] · 줄 끝 ⋯(수정 · 역할 변경 · 삭제). * 회사 거르기 = 서버가 그 회사 사람만 줌(전체는 최근 200 명) · 역할 · 찾기 = 받은 줄 안에서. * 주소 `&company=&q=<글>` 로 미리 거른 채 열 수 있음(회사 상세 › 사람). * ========================================================================== */ import { currentQuery } from "../A00_Common/router"; import { fetchAllCompanies, fetchAllUsers, type CompanyInfo, type DashboardUser, } from "./B01_Dashboard_Api_Fetch"; import { canChangeRole, canDeleteUser } from "./B01_Dashboard_UI_Helper"; import { openAddMemberModal, openChangeRoleModal, openDeleteUserModal, openEditUserModal, } from "./B01_Dashboard_UI_Modals"; import { badge, dataTable, emptyState, nameCell, pageFrame, primaryButton, rowMenu, type RowMenuItem, } from "./B01_Dashboard_UI_PageKit"; import { el, loadPage, searchBox, selectBox, toolbar } from "./B01_Dashboard_UI_View_Parts"; import type { ViewCtx } from "./B01_Dashboard_UI_Views"; import { roleText, statusBadgeOf } from "./logic/B01_Dashboard_Logic_Pages"; const SERVER_LIMIT = 200; /** 역할 · 찾기(이름 · 이메일 · 회사 · 부서 · 직급) 거르기 — 순서는 받은 그대로 */ export function filterUsers(users: DashboardUser[], role: string, query: string): DashboardUser[] { const q = query.trim().toLowerCase(); return users.filter( (u) => (!role || u.role === role) && (!q || [u.name, u.email, u.company_name, u.department, u.position] .filter(Boolean) .join(" ") .toLowerCase() .includes(q)), ); } const COLUMNS = [ { label: "이름" }, { label: "회사" }, { label: "직급" }, { label: "부서" }, { label: "전화" }, { label: "역할" }, { label: "상태" }, { label: "" }, ]; function menuOf(ctx: ViewCtx, u: DashboardUser): RowMenuItem[] { const items: RowMenuItem[] = [ { label: "정보 수정", onClick: () => openEditUserModal(ctx.me, u) }, ]; if (canChangeRole(ctx.me, u)) items.push({ label: "역할 변경", onClick: () => openChangeRoleModal(u) }); // 회사에서 빼기 · 계정 삭제 — 처리 방식 고르는 창이 확인을 겸함 · 자기 자신은 안 보임 if (canDeleteUser(ctx.me, u) && u.id !== ctx.me.id) items.push({ label: "삭제…", onClick: () => openDeleteUserModal(u) }); return items; } export function mountSysUsers(host: HTMLElement, ctx: ViewCtx): void { const start = currentQuery(); const state = { company: start.get("company") ?? "", role: "", q: (start.get("q") ?? "").toLowerCase(), }; loadPage(host, async (stage) => { const companies: CompanyInfo[] = await fetchAllCompanies().catch(() => []); let users: DashboardUser[] = []; const body = el("div"); const count = el("span", "b01-sys-count"); const draw = (): void => { const shown = filterUsers(users, state.role, state.q); const capped = users.length >= SERVER_LIMIT && !state.company; count.textContent = `${shown.length} / ${users.length} 명${capped ? " · 최근 200 명까지 — 회사로 거르면 더 보임" : ""}`; body.replaceChildren( shown.length ? dataTable( COLUMNS, shown.map((u) => { const st = statusBadgeOf(u.status); return [ nameCell(u.name, u.email), u.company_name ?? "-", u.position ?? "-", u.department ?? "-", u.phone ?? "-", badge(roleText(u.role), u.role === "USER" ? "neutral" : "accent"), badge(statusLabel(u.status, st.text), st.tone), rowMenu(menuOf(ctx, u)), ]; }), ) : emptyState({ icon: "user", text: users.length ? "조건에 맞는 사용자가 없습니다." : "사용자가 없습니다.", }), ); }; const load = async (): Promise => { users = await fetchAllUsers(state.company ? Number(state.company) : null); draw(); }; await load(); const company = selectBox( "회사", [["", "모든 회사"], ...companies.map((c): [string, string] => [String(c.id), c.name])], (v) => { state.company = v; void load(); }, ); company.value = state.company; const role = selectBox( "역할", [ ["", "모든 역할"], ["SYSTEM_ADMIN", "시스템 관리자"], ["ADMIN", "회사 관리자"], ["USER", "일반 사용자"], ], (v) => { state.role = v; draw(); }, ); const search = searchBox("이름 · 이메일 · 부서 찾기", (v) => { state.q = v; draw(); }); search.value = state.q; const page = pageFrame(stage, "사용자", "모든 회사의 사용자를 보고 정보 · 역할을 고칩니다."); page.setAction( primaryButton("사용자", () => openAddMemberModal(undefined, state.company ? Number(state.company) : ctx.companyId), ), ); page.body.append(toolbar(search, company, role, count), body); }); } /** 계정 상태 글 — 공용 배지 글에 없는 값(회사 없음)만 덧붙임 */ function statusLabel(status: string, fallback: string): string { return status === "NO_COMPANY" ? "회사 없음" : fallback; }