"use client" import React, { useEffect, useState } from "react" import { useSession } from "next-auth/react" import { useRouter } from "next/navigation" import { format } from "date-fns" import { zhCN } from "date-fns/locale" import { Users, TreePine, UserPlus, ArrowLeft, ChevronDown, ChevronRight, Shield, Ban, Check, LogIn } from "lucide-react" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { useDialog } from "@/components/ui/alert-dialog-custom" interface Collaborator { id: string name: string | null email: string role: string } interface TreeOwner { id: string name: string | null email: string } interface TreeInfo { id: string name: string createdAt: string memberCount: number collaborators: Collaborator[] } interface CollaboratingTree { id: string name: string role: string owner: TreeOwner } interface UserInfo { id: string email: string name: string | null avatar: string | null isAdmin: boolean isActive: boolean createdAt: string treeCount: number totalMembers: number trees: TreeInfo[] collaboratingTrees: CollaboratingTree[] } interface Stats { totalUsers: number totalTrees: number totalMembers: number } interface LoginLog { id: string userId: string email: string userName: string | null ip: string | null userAgent: string | null success: boolean message: string | null createdAt: string } export default function AdminPage() { const { data: session, status } = useSession() const router = useRouter() const { showAlert } = useDialog() const [users, setUsers] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [expandedUsers, setExpandedUsers] = useState>(new Set()) const [updating, setUpdating] = useState(null) const [loginLogs, setLoginLogs] = useState([]) const [logsLoading, setLogsLoading] = useState(false) useEffect(() => { if (status === "unauthenticated") { router.push("/auth/signin") return } if (status === "authenticated") { fetchUsers() fetchLoginLogs() } }, [status, router]) const fetchUsers = async () => { try { setLoading(true) const response = await fetch("/api/admin/users") if (!response.ok) { throw new Error("获取数据失败") } const data = await response.json() setUsers(data.users) setStats(data.stats) } catch (err) { setError(err instanceof Error ? err.message : "未知错误") } finally { setLoading(false) } } const fetchLoginLogs = async () => { try { setLogsLoading(true) const response = await fetch("/api/admin/login-logs?limit=100") if (!response.ok) { // 如果获取失败,设置为空数组,不报错 setLoginLogs([]) return } const data = await response.json() setLoginLogs(data.logs || []) } catch (err) { console.error("获取登录日志失败:", err) setLoginLogs([]) } finally { setLogsLoading(false) } } const toggleUserExpand = (userId: string) => { setExpandedUsers((prev) => { const newSet = new Set(prev) if (newSet.has(userId)) { newSet.delete(userId) } else { newSet.add(userId) } return newSet }) } const formatDate = (dateString: string) => { return format(new Date(dateString), "yyyy年MM月dd日 HH:mm", { locale: zhCN }) } const toggleUserStatus = async (userId: string, currentStatus: boolean) => { try { setUpdating(userId) const response = await fetch(`/api/admin/users/${userId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isActive: !currentStatus }), }) if (!response.ok) { const data = await response.json() throw new Error(data.error || "操作失败") } // 更新本地状态 setUsers((prev) => prev.map((user) => user.id === userId ? { ...user, isActive: !currentStatus } : user ) ) } catch (err) { await showAlert(err instanceof Error ? err.message : "操作失败", "错误") } finally { setUpdating(null) } } if (status === "loading" || loading) { return (
) } if (error) { return (
错误 {error}
) } return (
{/* 头部 */}

用户管理

{/* 统计卡片 */} {stats && (
总用户数
{stats.totalUsers}
总家族数
{stats.totalTrees}
总成员数
{stats.totalMembers}
)} {/* Tabs */} 用户列表 登录日志 {/* 用户列表 Tab */} 用户列表 所有注册用户及其创建的家族信息 用户 邮箱 状态 家族数 成员数 注册时间 操作 {users.map((user) => ( toggleUserExpand(user.id)} > {(user.trees.length > 0 || user.collaboratingTrees.length > 0) && ( expandedUsers.has(user.id) ? ( ) : ( ) )}
{user.name || "未设置"} {user.isAdmin && ( )}
{user.email} {user.isActive ? ( 启用 ) : ( 停用 )} {user.treeCount} {user.totalMembers} {formatDate(user.createdAt)} {!user.isAdmin && ( )}
{/* 展开的家族列表和协作信息 */} {expandedUsers.has(user.id) && (user.trees.length > 0 || user.collaboratingTrees.length > 0) && (
{/* 创建的家族 */} {user.trees.length > 0 && (

创建的家族

家族名称 成员数 协作者 创建时间 {user.trees.map((tree) => ( {tree.name} {tree.memberCount} {tree.collaborators.length > 0 ? (
{tree.collaborators.map((c) => ( {c.name || c.email} ({c.role === 'EDITOR' ? '编辑' : '查看'}) ))}
) : ( )}
{formatDate(tree.createdAt)}
))}
)} {/* 协作的家族 */} {user.collaboratingTrees.length > 0 && (

协作的家族

家族名称 角色 所有者 {user.collaboratingTrees.map((tree) => ( {tree.name} {tree.role === 'EDITOR' ? '编辑者' : '查看者'} {tree.owner.name || tree.owner.email} ))}
)}
)} ))} {users.length === 0 && ( 暂无用户数据 )} {/* 登录日志 Tab */} 登录日志 用户登录记录,按时间倒序排列 {logsLoading ? (
) : ( 用户 邮箱 状态 登录时间 {loginLogs.map((log) => ( {log.userName || "未设置"} {log.email} {log.success ? ( 成功 ) : ( 失败 )} {formatDate(log.createdAt)} ))} {loginLogs.length === 0 && ( 暂无登录记录 )}
)}
) }