import { useEffect, useMemo, useState } from "react"; import { BarChart3, Building2, Check, TrendingUp, Users } from "lucide-react"; import { motion } from "motion/react"; import { useNavigate } from "react-router-dom"; import { completeDashboardTodo, getDashboardHome, type DashboardActivity, type DashboardHome, type DashboardStat, type DashboardTodo } from "@/lib/auth"; const DASHBOARD_PREVIEW_COUNT = 5; const DASHBOARD_HISTORY_PREVIEW_COUNT = 3; const baseStats = [ { name: "本月新增商机", metricKey: "monthlyOpportunities", icon: TrendingUp, color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-100 dark:bg-emerald-500/20" }, { name: "已推送OMS项目", metricKey: "pushedOmsProjects", icon: Users, color: "text-blue-600 dark:text-blue-400", bg: "bg-blue-100 dark:bg-blue-500/20" }, { name: "本月新增渠道", metricKey: "monthlyChannels", icon: Building2, color: "text-violet-600 dark:text-violet-400", bg: "bg-violet-100 dark:bg-violet-500/20" }, { name: "本月打卡次数", metricKey: "monthlyCheckins", icon: BarChart3, color: "text-amber-600 dark:text-amber-400", bg: "bg-amber-100 dark:bg-amber-500/20" }, ] as const; const statRoutes: Record<(typeof baseStats)[number]["metricKey"], { pathname: string; state?: { tab: "sales" | "channel" } }> = { monthlyOpportunities: { pathname: "/opportunities" }, pushedOmsProjects: { pathname: "/opportunities" }, monthlyChannels: { pathname: "/expansion", state: { tab: "channel" } }, monthlyCheckins: { pathname: "/work/checkin" }, }; export default function Dashboard() { const navigate = useNavigate(); const [home, setHome] = useState({}); const [loading, setLoading] = useState(true); const [showAllActivities, setShowAllActivities] = useState(false); const [showAllHistoryTodos, setShowAllHistoryTodos] = useState(false); const [historyExpanded, setHistoryExpanded] = useState(false); const [completingTodoId, setCompletingTodoId] = useState(null); useEffect(() => { let cancelled = false; async function loadDashboard() { try { const data = await getDashboardHome(); if (!cancelled) { setHome(data ?? {}); } } catch { if (!cancelled) { setHome({}); } } finally { if (!cancelled) { setLoading(false); } } } void loadDashboard(); return () => { cancelled = true; }; }, []); useEffect(() => { setShowAllActivities(false); setShowAllHistoryTodos(false); setHistoryExpanded(false); }, [home.activities, home.todos]); const statMap = new Map((home.stats ?? []).map((item: DashboardStat) => [item.metricKey, item.value])); const stats = baseStats.map((stat) => ({ ...stat, value: statMap.get(stat.metricKey), })); const pendingTodos = useMemo( () => (home.todos ?? []).filter((item) => item.status !== "done"), [home.todos], ); const historyTodos = useMemo( () => (home.todos ?? []).filter((item) => item.status === "done"), [home.todos], ); const visibleHistoryTodos = showAllHistoryTodos ? historyTodos : historyTodos.slice(0, DASHBOARD_HISTORY_PREVIEW_COUNT); const activities = (home.activities?.length ? home.activities : [{ id: 0, title: "无", content: "无", timeText: "无" }]) as DashboardActivity[]; const visibleActivities = showAllActivities ? activities : activities.slice(0, DASHBOARD_PREVIEW_COUNT); const hasMoreActivities = activities.length > DASHBOARD_PREVIEW_COUNT && activities[0]?.id !== 0; const hasMoreHistoryTodos = historyTodos.length > DASHBOARD_HISTORY_PREVIEW_COUNT; const handleCompleteTodo = async (todoId: string) => { if (!todoId || completingTodoId === todoId) { return; } setCompletingTodoId(todoId); try { await completeDashboardTodo(todoId); setHome((current) => ({ ...current, todos: (current.todos ?? []).map((item) => ( item.id === todoId ? { ...item, status: "done", updatedAt: new Date().toISOString() } : item )), })); } catch { // Keep current UI state when completion fails silently. } finally { setCompletingTodoId(null); } }; const handleStatCardClick = (metricKey: (typeof baseStats)[number]["metricKey"]) => { const target = statRoutes[metricKey]; if (!target) { return; } navigate(target.pathname, target.state ? { state: target.state } : undefined); }; return (

工作台

欢迎回来,{home.realName || "无"}。今天是你入职的第 {home.onboardingDays ?? 0} 天。

{loading ? ( ) : (
{stats.map((stat, i) => ( handleStatCardClick(stat.metricKey)} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }} className="crm-card min-h-[88px] rounded-xl p-3 text-left transition-all hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:hover:bg-slate-900 sm:min-h-[104px] sm:rounded-2xl sm:p-5" whileTap={{ scale: 0.98 }} aria-label={`查看${stat.name}`} >

{stat.name}

{stat.value ?? 0}

))}

待办事项

共 {pendingTodos.length + historyTodos.length} 条
待办 {pendingTodos.length} 条未完成
{pendingTodos.length ? (
    {pendingTodos.map((task: DashboardTodo) => (
  • {getTodoDisplayTitle(task)}

  • ))}
) : (
暂无未完成待办
)}
{historyExpanded ? ( historyTodos.length ? ( <>
    {visibleHistoryTodos.map((task: DashboardTodo) => (
  • {getTodoDisplayTitle(task)}

  • ))}
{hasMoreHistoryTodos ? ( ) : null} ) : (
暂无历史待办
) ) : null}

最新动态

{visibleActivities.map((news: DashboardActivity, i: number) => (

{news.title || "无"}

{news.content || "无"}

{news.timeText || "无"}

))}
{hasMoreActivities && !showAllActivities ? ( ) : null}
)}
); } function DashboardSkeleton() { return (
{[0, 1, 2, 3].map((item) => (
))}
{[0, 1].map((item) => (
))}
{[0, 1, 2].map((item) => (
))}
); } function getTodoDisplayTitle(task: DashboardTodo) { const title = task.title?.trim(); if (!title) { return "无"; } if (task.bizType === "report" && title.startsWith("明日工作计划:")) { return title.slice("明日工作计划:".length).trim() || "明日工作计划"; } return title; }