"use client"; import { useState, useMemo } from "react"; import Image from "next/image"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { LoadingState, ErrorState, EmptyState } from "@/components/ui/api-states"; import { useApi } from "@/lib/hooks/use-api"; import { fetchWithAuth } from "@/lib/api/client"; import type { OrganizationInfo, OrganizationMember, MemberRole, MemberStatus, InviteMemberPayload, } from "@/lib/api/organization"; import { Users, Mail, Search, Plus, MoreVertical, Shield, UserCheck, Eye, Trash2 } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; const roleConfig: Record = { admin: { label: "管理员", icon: , color: "bg-red-50 text-red-700 border-red-200", }, member: { label: "成员", icon: , color: "bg-blue-50 text-blue-700 border-blue-200", }, viewer: { label: "查看者", icon: , color: "bg-gray-50 text-gray-700 border-gray-200", }, }; const statusConfig: Record = { active: { label: "活跃", color: "bg-green-100 text-green-700", }, pending: { label: "待加入", color: "bg-yellow-100 text-yellow-700", }, inactive: { label: "已停用", color: "bg-gray-100 text-gray-600", }, }; function getInitials(name: string): string { return name.slice(0, 2).toUpperCase(); } function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", }); } export default function ClientsPage() { const [searchQuery, setSearchQuery] = useState(""); const [roleFilter, setRoleFilter] = useState("all"); const [inviteDialogOpen, setInviteDialogOpen] = useState(false); const [inviteEmail, setInviteEmail] = useState(""); const [inviteRole, setInviteRole] = useState("member"); const [selectedMember, setSelectedMember] = useState(null); const [roleChangeDialogOpen, setRoleChangeDialogOpen] = useState(false); const [newRole, setNewRole] = useState("member"); const [removeDialogOpen, setRemoveDialogOpen] = useState(false); const [isMutating, setIsMutating] = useState(false); const { data: orgInfo, isLoading: orgLoading, error: orgError, } = useApi("/api/v1/organization/info"); const { data: members, isLoading: membersLoading, error: membersError, refresh: refreshMembers, } = useApi("/api/v1/organization/members"); const filteredMembers = useMemo(() => { const memberList = members || []; return memberList.filter((member) => { const matchesSearch = !searchQuery || member.name.toLowerCase().includes(searchQuery.toLowerCase()) || member.email.toLowerCase().includes(searchQuery.toLowerCase()); const matchesRole = roleFilter === "all" || member.role === roleFilter; return matchesSearch && matchesRole; }); }, [members, searchQuery, roleFilter]); const safeOrgInfo = orgInfo ?? null; const loading = orgLoading || membersLoading; const handleInvite = async () => { if (!inviteEmail) return; try { setIsMutating(true); await fetchWithAuth("/api/v1/organization/members/invite", { method: "POST", body: JSON.stringify({ email: inviteEmail, role: inviteRole }), }); setInviteDialogOpen(false); setInviteEmail(""); setInviteRole("member"); refreshMembers(); } finally { setIsMutating(false); } }; const handleRoleChange = async () => { if (!selectedMember) return; try { setIsMutating(true); await fetchWithAuth(`/api/v1/organization/members/${selectedMember.id}/role`, { method: "PUT", body: JSON.stringify({ role: newRole }), }); setRoleChangeDialogOpen(false); setSelectedMember(null); refreshMembers(); } finally { setIsMutating(false); } }; const handleRemoveMember = async () => { if (!selectedMember) return; try { setIsMutating(true); await fetchWithAuth(`/api/v1/organization/members/${selectedMember.id}`, { method: "DELETE", }); setRemoveDialogOpen(false); setSelectedMember(null); refreshMembers(); } finally { setIsMutating(false); } }; if (loading) { return (
); } if (orgError || membersError) { return (

组织管理

window.location.reload()} />
); } return (

组织管理

管理组织成员和权限设置

组织信息 {safeOrgInfo ? (

组织名称

{safeOrgInfo.name}

成员数量

{safeOrgInfo.member_count} 人

创建时间

{formatDate(safeOrgInfo.created_at)}

) : (

组织信息加载中...

)}
成员列表
setSearchQuery(e.target.value)} className="pl-10" />
{filteredMembers.length === 0 ? ( } message="没有找到成员" description="尝试调整搜索条件或筛选器" /> ) : ( 成员 角色 状态 加入时间 操作 {filteredMembers.map((member) => (
{member.avatar_url ? ( ) : ( getInitials(member.name) )}

{member.name}

{member.email}

{roleConfig[member.role].icon} {roleConfig[member.role].label} {statusConfig[member.status].label} {formatDate(member.joined_at)} { setSelectedMember(member); setNewRole(member.role); setRoleChangeDialogOpen(true); }} > 修改角色 { setSelectedMember(member); setRemoveDialogOpen(true); }} > 移除成员
))}
)}
邀请新成员 通过邮箱邀请新成员加入组织,并为其分配角色权限。
setInviteEmail(e.target.value)} />
修改成员角色 更改 {selectedMember?.name} 的角色权限
移除成员 确定要将 {selectedMember?.name} 从组织中移除吗?此操作不可撤销。
); }