"use client"; import { Activity, AlertTriangle, BarChart3, CheckCircle2, FileText, Users, Wallet } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { adminApi, errMessage, type DashboardData, type PersonalReport, type RiskAlert, type WorkOrder } from "@/lib/admin-api"; import { isAdvisor, isRiskOfficer } from "@/lib/roles"; import type { UserPayload } from "@/types/api"; import { Badge, Button, Card, EmptyState, ErrorState, LoadingBlock, StatCard, formatMoney, formatPercent, formatTime, isOverdue, } from "@/components/admin/ui"; const LEVEL_KEYS = ["C1", "C2", "C3", "C4", "C5", "unknown"]; const LEVEL_LABEL: Record = { C1: "C1 保守型", C2: "C2 稳健型", C3: "C3 平衡型", C4: "C4 成长型", C5: "C5 进取型", unknown: "未测评", }; export function OverviewSection({ user, onNavigate, }: { user: UserPayload; onNavigate: (section: "todos" | "customers" | "risk" | "workorders") => void; }) { const advisor = isAdvisor(user); const risk = isRiskOfficer(user); const [dashboard, setDashboard] = useState(null); const [personal, setPersonal] = useState(null); const [alerts, setAlerts] = useState([]); const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const load = useCallback(async () => { setLoading(true); setError(""); try { const tasks: Promise[] = []; if (advisor) { tasks.push( adminApi.dashboard().then(setDashboard).catch(() => setDashboard(null)), adminApi.personalReport().then(setPersonal).catch(() => setPersonal(null)) ); } if (risk) { tasks.push( adminApi .alerts({ page: 1, page_size: 5 }) .then((result) => setAlerts(result.items ?? [])) .catch(() => setAlerts([])), adminApi .workOrders({ page: 1, page_size: 5 }) .then((result) => setOrders(result.items ?? [])) .catch(() => setOrders([])) ); } await Promise.all(tasks); } catch (e) { setError(errMessage(e, "工作台数据加载失败")); } finally { setLoading(false); } }, [advisor, risk]); useEffect(() => { void load(); }, [load]); if (loading) return ; if (error) return void load()} />; const overview = dashboard?.overview; const pendingTodos = dashboard?.todos?.items ?? []; const levels = dashboard?.customer_levels ?? {}; const maxLevel = Math.max(1, ...LEVEL_KEYS.map((key) => levels[key] ?? 0)); return (
{advisor ? ( <>
} label="名下客户" value={overview?.total_customers ?? "--"} hint={`已签约 ${overview?.signed_customers ?? 0} · 已结束 ${overview?.closed_customers ?? 0}`} /> } label="管理资产" value={overview?.total_aum ? formatMoney(overview.total_aum) : "--"} hint="名下客户持有中市值合计" /> } label="客户流失率" value={overview?.churn_rate !== undefined ? formatPercent(overview.churn_rate) : "--"} hint="已结束 / 总客户" /> } label="待办事项" value={dashboard?.todos?.pending ?? "--"} hint={ pendingTodos.some((item) => isOverdue(item.due_at)) ? `${pendingTodos.filter((item) => isOverdue(item.due_at)).length} 项已逾期` : "暂无逾期" } hintTone={pendingTodos.some((item) => isOverdue(item.due_at)) ? "danger" : "muted"} />
{Object.keys(levels).length === 0 ? ( ) : (
{LEVEL_KEYS.filter((key) => (levels[key] ?? 0) > 0 || key === "unknown").map((key) => (
{LEVEL_LABEL[key] ?? key} {levels[key] ?? 0}
))}
)} onNavigate("todos")}> 全部待办 } > {pendingTodos.length === 0 ? ( ) : (
    {pendingTodos.slice(0, 6).map((todo) => (
  • {todo.todo_type}

    {todo.customer_id ? `客户 #${todo.customer_id}` : "无关联客户"} · 截止{" "} {formatTime(todo.due_at)}

    {isOverdue(todo.due_at) && 已逾期} {todo.priority ?? "普通"}
  • ))}
)}
) : ( )} {risk ? (
onNavigate("risk")}> 进入风控 } > {alerts.length === 0 ? ( ) : (
    {alerts.map((alert) => (
  • {alert.alert_type} 客户 #{alert.customer_id}
    {alert.alert_level}
  • ))}
)}
onNavigate("workorders")}> 进入工单 } > {orders.length === 0 ? ( ) : (
    {orders.map((order) => (
  • {order.order_type} {order.work_order_no}
    {order.priority}
  • ))}
)}
) : ( )}

基金产品

查看在售基金与历史净值

NL2SQL 查询

自然语言查询业务数据

知识库管理

维护 RAG 文档与检索数据

{!isAdvisor(user) && (

部分入口需要对应角色权限,无权限时会在目标模块给出提示

)}
); }