Files
Aislo/B01_Dashboard/B01_Dashboard_UI_View_SysCompanies.ts
T

229 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B01_Dashboard_UI_View_SysCompanies.ts
* 시스템 관리 › 회사(549-2) — 회사 목록(찾기 · 줄 끝 ⋯) → 회사 상세(정보 · 사람 · 프로젝트 탭) · [+ 회사].
* 상세 = 주소 `&company=<id>`(뒤로 가기 됨) · 수정 · 로고 · 새 회사 = 지금 창(Modals) 그대로.
* ========================================================================== */
import { createButton } from "@ui/ui_template_elements";
import { currentQuery } from "../A00_Common/router";
import {
fetchAllCompanies,
fetchAllUsers,
fetchCompanyProjects,
setCompanyLogo,
type CompanyInfo,
type ProjectItem,
} from "./B01_Dashboard_Api_Fetch";
import { openAssetPicker } from "./B01_Dashboard_UI_AssetPicker";
import { formatDate } from "./B01_Dashboard_UI_Common";
import { filterCompanies } from "./B01_Dashboard_UI_CompanyPick";
import { openCreateCompanyModal, openEditCompanyModal } from "./B01_Dashboard_UI_Modals";
import { viewHref } from "./B01_Dashboard_UI_Nav";
import {
badge,
dataTable,
emptyState,
nameCell,
pageFrame,
primaryButton,
rowMenu,
settingCard,
settingRow,
tabBar,
valueText,
} from "./B01_Dashboard_UI_PageKit";
import { el, loadPage, searchBox, toolbar } from "./B01_Dashboard_UI_View_Parts";
import type { ViewCtx } from "./B01_Dashboard_UI_Views";
import { roleText, statusBadgeOf } from "./logic/B01_Dashboard_Logic_Pages";
export const KIND_KO: Record<string, string> = {
ROAD: "임도설계",
PLAN: "예정노선",
STABILITY: "안정성평가",
EROSION: "사방설계",
};
const statusTag = (s?: string | null): HTMLElement =>
badge(s || "-", s === "활동중" ? "success" : "neutral");
const logoLabel = (c: CompanyInfo): string => (c.logo_asset_id ? "로고 변경…" : "로고 지정…");
function pickLogo(ctx: ViewCtx, company: CompanyInfo, after?: () => void): void {
void openAssetPicker("LOGO", company.id, ctx.me, company.logo_asset_id ?? null, async (id) => {
await setCompanyLogo(id, company.id);
company.logo_asset_id = id;
after?.();
});
}
/** 이름 칸 — 이름은 상세로 가는 링크(뒤로 가기 · 새 탭 됨) + 아래 작은 주소 */
function linkedName(c: CompanyInfo): HTMLElement {
const link = el("a", "b01-pg-name__main", c.name);
link.href = viewHref("sys-companies", { company: String(c.id) });
const cell = nameCell(c.name, c.business_address);
cell.firstElementChild!.replaceWith(link);
return cell;
}
const LIST_COLUMNS = [
{ label: "회사" },
{ label: "사업자등록번호" },
{ label: "대표자" },
{ label: "상태" },
{ label: "사용자", align: "end" as const },
{ label: "프로젝트", align: "end" as const },
{ label: "" },
];
function companyList(stage: HTMLElement, ctx: ViewCtx, companies: CompanyInfo[]): void {
const page = pageFrame(
stage,
"회사",
"등록된 회사를 보고 정보 · 로고를 고칩니다. 이름을 누르면 상세입니다.",
);
page.setAction(primaryButton("회사", () => openCreateCompanyModal()));
const body = el("div");
const draw = (query: string): void => {
const shown = filterCompanies(companies, query);
if (!shown.length) {
body.replaceChildren(
companies.length
? emptyState({ icon: "search", text: "찾는 회사가 없습니다." })
: emptyState({
icon: "building",
text: "등록된 회사가 없습니다.",
action: primaryButton("회사", () => openCreateCompanyModal()),
}),
);
return;
}
body.replaceChildren(
dataTable(
LIST_COLUMNS,
shown.map((c) => [
linkedName(c),
c.business_registration_number ?? "-",
c.business_owner ?? "-",
statusTag(c.business_status),
String(c.user_count ?? 0),
String(c.project_count ?? 0),
rowMenu([
{
label: "상세 보기",
onClick: () => ctx.go("sys-companies", { company: String(c.id) }),
},
{ label: "정보 수정", onClick: () => openEditCompanyModal(c) },
{ label: logoLabel(c), onClick: () => pickLogo(ctx, c) },
]),
]),
),
);
};
draw("");
page.body.append(toolbar(searchBox("회사 이름 · 사업자번호 찾기", draw)), body);
}
const projectTable = (projects: ProjectItem[]): HTMLElement =>
projects.length
? dataTable(
[
{ label: "프로젝트" },
{ label: "종류" },
{ label: "지역" },
{ label: "단계" },
{ label: "담당" },
{ label: "수정일" },
],
projects.map((p) => [
p.name,
KIND_KO[p.project_kind ?? "ROAD"] ?? "-",
p.region ?? "-",
`${p.workflow_stage} 단계`,
p.owner_name ?? "-",
formatDate(p.updated_at),
]),
)
: emptyState({ icon: "folder", text: "이 회사의 프로젝트가 없습니다." });
async function companyDetail(
stage: HTMLElement,
ctx: ViewCtx,
company: CompanyInfo,
): Promise<void> {
const [users, projects] = await Promise.all([
fetchAllUsers(company.id).catch(() => []),
fetchCompanyProjects(company.id).catch(() => []),
]);
const page = pageFrame(stage, company.name, "회사 상세");
page.setAction(
createButton({ label: "정보 수정", onClick: () => openEditCompanyModal(company) }),
);
const back = el("button", "b01-sys-back", "‹ 회사 목록");
back.type = "button";
back.addEventListener("click", () => ctx.go("sys-companies"));
const logoBtn = createButton({
label: logoLabel(company),
variant: "ghost",
onClick: () => pickLogo(ctx, company, () => (logoBtn.textContent = logoLabel(company))),
});
const info = settingCard([
settingRow("회사 이름", "", valueText(company.name)),
settingRow("사업자등록번호", "", valueText(company.business_registration_number)),
settingRow("대표자", "", valueText(company.business_owner)),
settingRow("주소", "", valueText(company.business_address)),
settingRow("상태", "", statusTag(company.business_status)),
settingRow("로고", "도면 표제란에 들어갑니다.", logoBtn),
]);
const people = users.length
? dataTable(
[
{ label: "이름" },
{ label: "직급" },
{ label: "부서" },
{ label: "역할" },
{ label: "상태" },
{ label: "" },
],
users.map((u) => {
const st = statusBadgeOf(u.status);
return [
nameCell(u.name, u.email),
u.position ?? "-",
u.department ?? "-",
roleText(u.role),
badge(st.text, st.tone),
rowMenu([
{
label: "사용자 페이지에서 보기",
onClick: () => ctx.go("sys-users", { company: String(company.id), q: u.email }),
},
]),
];
}),
)
: emptyState({ icon: "users", text: "이 회사에 소속된 사용자가 없습니다." });
const panes: Record<string, HTMLElement> = { info, people, projects: projectTable(projects) };
const pane = el("div");
pane.append(info);
const tabs = tabBar(
[
{ key: "info", label: "정보" },
{ key: "people", label: `사람 ${users.length}` },
{ key: "projects", label: `프로젝트 ${projects.length}` },
],
"info",
(key) => pane.replaceChildren(panes[key]),
);
page.body.append(back, tabs.root, pane);
}
export function mountSysCompanies(host: HTMLElement, ctx: ViewCtx): void {
const id = Number(currentQuery().get("company"));
loadPage(host, async (stage) => {
const companies = await fetchAllCompanies();
const company = id ? companies.find((c) => c.id === id) : undefined;
if (company) await companyDetail(stage, ctx, company);
else companyList(stage, ctx, companies);
});
}