完成 Wave4-5 财务与管理页刷新(05-29-frontend-ux-wave4-5-finance-admin-refresh)
这个提交包含在:
@@ -7,19 +7,26 @@ import {
|
||||
type OrganizationTreeNode,
|
||||
} from "../api/employees";
|
||||
import { EmployeeAvatar } from "../components/common/EmployeeAvatar";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace";
|
||||
import { Button } from "../components/ui/button";
|
||||
|
||||
function countNodes(nodes: OrganizationTreeNode[]): number {
|
||||
return nodes.reduce((total, node) => total + 1 + countNodes(node.children), 0);
|
||||
}
|
||||
|
||||
function countExternalManagers(nodes: OrganizationTreeNode[]): number {
|
||||
return nodes.reduce(
|
||||
(total, node) => total + (node.externalManager ? 1 : 0) + countExternalManagers(node.children),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
function OrganizationNodeCard({ node, depth = 0 }: { node: OrganizationTreeNode; depth?: number }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div
|
||||
className="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm"
|
||||
style={{ marginLeft: `${depth * 24}px` }}
|
||||
className="finance-list-card"
|
||||
style={{ marginLeft: `${Math.min(depth, 4) * 18}px` }}
|
||||
>
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -78,20 +85,45 @@ export function OrganizationPage() {
|
||||
}, [organizationQuery.data?.items]);
|
||||
|
||||
if (organizationQuery.isLoading) {
|
||||
return <div className="card-std p-6 text-sm text-zinc-500">加载组织架构中...</div>;
|
||||
return (
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
eyebrow="管理"
|
||||
title="组织架构"
|
||||
subtitle="按部门查看当前 active 员工的汇报关系。"
|
||||
/>
|
||||
<div className="admin-command-panel text-sm text-zinc-500">加载组织架构中...</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
}
|
||||
|
||||
if (organizationQuery.isError) {
|
||||
return <div className="card-std p-6 text-sm text-red-600">加载组织架构失败</div>;
|
||||
return (
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
eyebrow="管理"
|
||||
title="组织架构"
|
||||
subtitle="按部门查看当前 active 员工的汇报关系。"
|
||||
/>
|
||||
<div className="admin-command-panel text-sm text-red-600">加载组织架构失败</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
}
|
||||
|
||||
const sections = organizationQuery.data?.items ?? [];
|
||||
const totalEmployees = sections.reduce((total, section) => total + countNodes(section.roots), 0);
|
||||
const externalManagerCount = sections.reduce((total, section) => total + countExternalManagers(section.roots), 0);
|
||||
const expandedCount = sections.filter((section) => {
|
||||
const key = section.department?.id ?? section.department?.name ?? "unknown";
|
||||
return expandedDepartments[key] ?? true;
|
||||
}).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
eyebrow="管理"
|
||||
title="组织架构"
|
||||
subtitle="按部门查看当前 active 员工的汇报关系,跨部门上级会在节点右侧单独标注。"
|
||||
subtitle="按部门查看 active 员工的汇报关系,跨部门上级单独标注,便于组织复核。"
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
@@ -124,6 +156,29 @@ export function OrganizationPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="finance-status-strip" data-testid="organization-summary">
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">部门分组</div>
|
||||
<div className="finance-metric-card__value">{sections.length}</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">展示员工</div>
|
||||
<div className="finance-metric-card__value">{totalEmployees}</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">已展开</div>
|
||||
<div className="finance-metric-card__value">{expandedCount}</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">跨部门汇报</div>
|
||||
<div className="finance-metric-card__value">{externalManagerCount}</div>
|
||||
</div>
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">视图模式</div>
|
||||
<div className="finance-metric-card__value text-base">只读</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sections.length ? (
|
||||
<div className="space-y-6">
|
||||
{sections.map((section: OrganizationDepartmentSection) => {
|
||||
@@ -132,11 +187,11 @@ export function OrganizationPage() {
|
||||
const totalNodes = countNodes(section.roots);
|
||||
|
||||
return (
|
||||
<section key={sectionKey} className="card-std p-6">
|
||||
<section key={sectionKey} className="admin-command-panel">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-zinc-900">{section.department?.name ?? "未分配部门"}</h3>
|
||||
<div className="mt-1 text-sm text-zinc-500">{totalNodes} 名员工</div>
|
||||
<h2 className="text-base font-semibold text-zinc-900">{section.department?.name ?? "未分配部门"}</h2>
|
||||
<div className="mt-1 text-sm text-zinc-500">{totalNodes} 名员工 · {expanded ? "已展开" : "已折叠"}</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -166,9 +221,9 @@ export function OrganizationPage() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="card-std p-6 text-sm text-zinc-500">当前没有可展示的 active 员工。</div>
|
||||
<div className="admin-command-panel text-sm text-zinc-500">当前没有可展示的 active 员工。</div>
|
||||
)}
|
||||
</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户