Files
Mutual_Fund/frontend/components/admin/unified-workspace.tsx

218 lines
8.4 KiB
TypeScript
Raw Permalink 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.
"use client";
import { ShieldAlert } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { AdminShell, type ShellNotification } from "@/components/admin/shell";
import { AdminSection } from "@/components/admin/sections/admin";
import { CustomersSection } from "@/components/admin/sections/customers";
import { DraftsSection } from "@/components/admin/sections/drafts";
import { FundsSection } from "@/components/admin/sections/funds";
import { Nl2SqlSection } from "@/components/admin/sections/nl2sql";
import { OverviewSection } from "@/components/admin/sections/overview";
import { RiskSection } from "@/components/admin/sections/risk";
import { TodosSection } from "@/components/admin/sections/todos";
import { VisitsSection } from "@/components/admin/sections/visits";
import { WorkOrdersSection } from "@/components/admin/sections/workorders";
import { LoadingBlock, NoPermission, ToastProvider, formatTime } from "@/components/admin/ui";
import { adminApi } from "@/lib/admin-api";
import { canUseSection, isAdvisor, isRiskOfficer, isStaff, isWorkspaceSection, type WorkspaceSection } from "@/lib/roles";
import type { UserPayload } from "@/types/api";
const SECTION_META: Record<WorkspaceSection, { title: string; subtitle: string }> = {
overview: { title: "工作台概览", subtitle: "按当前角色展示核心经营指标" },
todos: { title: "我的待办", subtitle: "投顾待办事项的处理与跟踪" },
customers: { title: "客户管理", subtitle: "名下客户、持仓、诊断与签约状态" },
drafts: { title: "投顾草稿", subtitle: "建议草稿的编辑、发送与废弃" },
visits: { title: "客户回访", subtitle: "回访留痕与合规话术参考" },
risk: { title: "风险预警", subtitle: "待处置预警与人工处置通道" },
workorders: { title: "工单处理", subtitle: "认领、提交审核与复核闭环" },
funds: { title: "基金数据", subtitle: "白名单产品与净值数据" },
nl2sql: { title: "智能问数", subtitle: "自然语言查数,自动生成 SQL 并返回结果" },
admin: { title: "系统管理", subtitle: "NL2SQL 配置、知识库与数据管理" },
};
const REQUIRED_ROLE: Record<WorkspaceSection, string> = {
overview: "员工账号",
todos: "投顾权限",
customers: "投顾权限",
drafts: "投顾权限",
visits: "投顾权限",
risk: "风控权限",
workorders: "风控权限",
funds: "员工账号",
nl2sql: "员工账号",
admin: "管理员权限",
};
export function UnifiedWorkspace({ defaultSection = "overview" }: { defaultSection?: WorkspaceSection }) {
const [user, setUser] = useState<UserPayload | null>(null);
const [access, setAccess] = useState<"checking" | "allowed" | "denied">("checking");
const [section, setSection] = useState<WorkspaceSection>(defaultSection);
const [notifications, setNotifications] = useState<ShellNotification[]>([]);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const initial = params.get("section");
if (isWorkspaceSection(initial)) setSection(initial);
}, []);
const changeSection = useCallback((next: WorkspaceSection) => {
setSection(next);
const url = new URL(window.location.href);
url.searchParams.set("section", next);
window.history.replaceState(null, "", url.toString());
}, []);
useEffect(() => {
void (async () => {
try {
const current = await adminApi.me();
setUser(current);
setAccess(isStaff(current) ? "allowed" : "denied");
} catch {
setAccess("denied");
}
})();
}, []);
useEffect(() => {
if (access !== "allowed" || !user) return;
void (async () => {
const result: ShellNotification[] = [];
if (isAdvisor(user)) {
try {
const dashboard = await adminApi.dashboard();
for (const todo of (dashboard.todos?.items ?? []).slice(0, 5)) {
result.push({
id: `todo-${todo.id}`,
title: todo.todo_type,
body: `${todo.customer_id ? `客户 #${todo.customer_id} · ` : ""}截止 ${formatTime(todo.due_at)}`,
time: todo.priority ?? "普通",
tone: todo.priority === "高" ? "alert" : "info",
section: "todos",
});
}
} catch {
/* 无投顾权限时静默跳过 */
}
}
if (isRiskOfficer(user)) {
try {
const alerts = await adminApi.alerts({ status: "未处理", page: 1, page_size: 5 });
for (const alert of alerts.items ?? []) {
result.push({
id: `alert-${alert.id}`,
title: `${alert.alert_type}(${alert.alert_level})`,
body: `客户 #${alert.customer_id} · 待处置`,
time: formatTime(alert.create_time),
tone: alert.alert_level === "高" ? "alert" : "info",
section: "risk",
});
}
} catch {
/* 无风控权限时静默跳过 */
}
try {
const orders = await adminApi.workOrders({ status: "待审核", page: 1, page_size: 5 });
for (const order of orders.items ?? []) {
result.push({
id: `order-${order.id}`,
title: `${order.order_type} 待复核`,
body: `${order.work_order_no} · 优先级 ${order.priority}`,
time: formatTime(order.create_time),
tone: "check",
section: "workorders",
});
}
} catch {
/* 无风控权限时静默跳过 */
}
}
setNotifications(result);
})();
}, [access, user]);
if (access === "checking") {
return (
<main className="flex min-h-screen items-center justify-center bg-slate-50">
<LoadingBlock text="正在验证员工身份..." />
</main>
);
}
if (access === "denied" || !user) {
return (
<main className="flex min-h-screen items-center justify-center bg-slate-50 px-6">
<section className="w-full max-w-md rounded-xl border border-slate-200 bg-white p-8 text-center">
<ShieldAlert className="mx-auto h-10 w-10 text-rose-500" />
<h1 className="mt-5 text-xl font-semibold text-slate-900">无权限访问员工工作台</h1>
<p className="mt-2 text-sm leading-6 text-slate-500">
当前账号不是员工账号,无法进入统一员工工作台。请使用员工账号登录,或返回官网浏览基金产品。
</p>
<div className="mt-6 flex justify-center gap-3">
<Link
href="/"
className="rounded-lg border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-50"
>
返回官网
</Link>
<Link
href="/login"
className="rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
>
重新登录
</Link>
</div>
</section>
</main>
);
}
const meta = SECTION_META[section];
const allowed = canUseSection(user, section);
return (
<ToastProvider>
<AdminShell
user={user}
section={section}
onSectionChange={changeSection}
title={meta.title}
subtitle={meta.subtitle}
notifications={notifications}
>
{!allowed ? (
<NoPermission module={meta.title} hint={`该模块需要${REQUIRED_ROLE[section]},当前账号不具备操作权限。`} />
) : section === "overview" ? (
<OverviewSection
user={user}
onNavigate={(target) => changeSection(target)}
/>
) : section === "todos" ? (
<TodosSection onOpenCustomer={() => changeSection("customers")} />
) : section === "customers" ? (
<CustomersSection />
) : section === "drafts" ? (
<DraftsSection />
) : section === "visits" ? (
<VisitsSection />
) : section === "risk" ? (
<RiskSection />
) : section === "workorders" ? (
<WorkOrdersSection />
) : section === "funds" ? (
<FundsSection user={user} />
) : section === "nl2sql" ? (
<Nl2SqlSection />
) : (
<AdminSection username={user.real_name ?? user.username} />
)}
</AdminShell>
</ToastProvider>
);
}