"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardDescription, 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 { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Label } from "@/components/ui/label" import { Alert, AlertDescription } from "@/components/ui/alert" import { Search, MoreHorizontal, UserPlus, Edit, Trash2, Shield, Eye, Calendar, Activity, User, Mail, Building, Loader2, CheckCircle, AlertTriangle, Clock, RefreshCw, Copy, Link, Heart, ThumbsUp, Star, Plus, ExternalLink, Code, Users, ChevronLeft, ChevronRight, Filter, X, } from "lucide-react" export function UserManagement() { const [users, setUsers] = useState([]) const [searchTerm, setSearchTerm] = useState("") const [selectedDepartment, setSelectedDepartment] = useState("all") const [selectedRole, setSelectedRole] = useState("all") const [selectedStatus, setSelectedStatus] = useState("all") const [selectedUser, setSelectedUser] = useState(null) const [userActivities, setUserActivities] = useState([]) const [userStats, setUserStats] = useState({ totalApps: 0, totalReviews: 0, totalLikes: 0, loginDays: 0 }) const [activityPagination, setActivityPagination] = useState({ page: 1, limit: 10, total: 0, totalPages: 0 }) const [activityFilters, setActivityFilters] = useState({ search: '', startDate: '', endDate: '', activityType: 'all' }) const [showUserDetail, setShowUserDetail] = useState(false) const [showInviteUser, setShowInviteUser] = useState(false) const [showEditUser, setShowEditUser] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [isClient, setIsClient] = useState(false) const [isLoading, setIsLoading] = useState(false) const [stats, setStats] = useState({ totalUsers: 0, activeUsers: 0, adminCount: 0, developerCount: 0, inactiveUsers: 0, newThisMonth: 0 }) const [pagination, setPagination] = useState({ page: 1, limit: 10, total: 0, totalPages: 0 }) // Set client state after hydration useEffect(() => { setIsClient(true) }, []) // 載入用戶統計數據 const loadUserStats = async (userId: string) => { try { const response = await fetch(`/api/admin/users/${userId}/stats`) const data = await response.json() if (data.success) { setUserStats(data.data) } else { console.error('載入用戶統計數據失敗:', data.error) setUserStats({ totalApps: 0, totalReviews: 0, totalLikes: 0, loginDays: 0 }) } } catch (error) { console.error('載入用戶統計數據錯誤:', error) setUserStats({ totalApps: 0, totalReviews: 0, totalLikes: 0, loginDays: 0 }) } } // 載入用戶活動記錄 const loadUserActivities = async (userId: string, page: number = 1, filters: any = {}) => { try { const params = new URLSearchParams({ page: page.toString(), limit: activityPagination.limit.toString(), search: filters.search || '', startDate: filters.startDate || '', endDate: filters.endDate || '', activityType: filters.activityType || 'all' }) const response = await fetch(`/api/admin/users/${userId}/activities?${params}`) const data = await response.json() if (data.success) { setUserActivities(data.data.activities) setActivityPagination(data.data.pagination) } else { console.error('載入活動記錄失敗:', data.error) setUserActivities([]) setActivityPagination({ page: 1, limit: 10, total: 0, totalPages: 0 }) } } catch (error) { console.error('載入活動記錄錯誤:', error) setUserActivities([]) setActivityPagination({ page: 1, limit: 10, total: 0, totalPages: 0 }) } } // 載入用戶數據 const loadUsers = async () => { if (!isClient) return setIsLoading(true) try { const params = new URLSearchParams({ page: pagination.page.toString(), limit: pagination.limit.toString(), search: searchTerm, department: selectedDepartment, role: selectedRole, status: selectedStatus }) const response = await fetch(`/api/admin/users?${params}`) const data = await response.json() if (data.success) { setUsers(data.data.users) setStats(data.data.stats) setPagination(data.data.pagination) } else { console.error('載入用戶失敗:', data.error) } } catch (error) { console.error('載入用戶錯誤:', error) } finally { setIsLoading(false) } } // 當篩選條件改變時重新載入 useEffect(() => { loadUsers() }, [isClient, searchTerm, selectedDepartment, selectedRole, selectedStatus, pagination.page]) const [showInvitationLink, setShowInvitationLink] = useState(false) const [userToDelete, setUserToDelete] = useState(null) const [generatedInvitation, setGeneratedInvitation] = useState(null) const [success, setSuccess] = useState("") const [error, setError] = useState("") // 邀請用戶表單狀態 - 包含電子郵件和預設角色 const [inviteEmail, setInviteEmail] = useState("") const [inviteRole, setInviteRole] = useState("user") // 編輯用戶表單狀態 const [editUser, setEditUser] = useState({ id: "", name: "", email: "", department: "", role: "", status: "", }) // 篩選現在由 API 處理,不需要前端篩選 const handleViewUser = (user: any) => { setSelectedUser(user) setShowUserDetail(true) // 重置活動記錄狀態 setActivityPagination({ page: 1, limit: 10, total: 0, totalPages: 0 }) setActivityFilters({ search: '', startDate: '', endDate: '', activityType: 'all' }) // 重置統計數據狀態 setUserStats({ totalApps: 0, totalReviews: 0, totalLikes: 0, loginDays: 0 }) // 載入用戶統計數據和活動記錄 loadUserStats(user.id) loadUserActivities(user.id, 1, { search: '', startDate: '', endDate: '', activityType: 'all' }) } // 處理活動記錄篩選 const handleActivityFilter = (newFilters: any) => { const updatedFilters = { ...activityFilters, ...newFilters } setActivityFilters(updatedFilters) if (selectedUser) { loadUserActivities(selectedUser.id, 1, updatedFilters) } } // 處理活動記錄分頁 const handleActivityPageChange = (page: number) => { setActivityPagination(prev => ({ ...prev, page })) if (selectedUser) { loadUserActivities(selectedUser.id, page, activityFilters) } } const handleEditUser = (user: any) => { setEditUser({ id: user.id, name: user.name, email: user.email, department: user.department, role: user.role, status: user.status, }) setShowEditUser(true) } const handleDeleteUser = (user: any) => { setUserToDelete(user) setShowDeleteConfirm(true) } const handleToggleUserStatus = async (userId: string) => { setIsLoading(true) try { // 找到要切換狀態的用戶 const user = users.find(u => u.id === userId) if (!user) { setError("用戶不存在") setIsLoading(false) return } const newStatus = user.status === "active" ? "inactive" : "active" const response = await fetch(`/api/admin/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: user.name, department: user.department, role: user.role, status: newStatus }) }) const data = await response.json() if (data.success) { // 更新本地用戶列表 setUsers( users.map((u) => u.id === userId ? { ...u, status: newStatus } : u ) ) setSuccess("用戶狀態更新成功!") setTimeout(() => setSuccess(""), 3000) } else { setError(data.error || "更新用戶狀態失敗") } } catch (error) { console.error('更新用戶狀態錯誤:', error) setError("更新用戶狀態時發生錯誤") } setIsLoading(false) } const handleChangeUserRole = async (userId: string, newRole: string) => { setIsLoading(true) // 模擬 API 調用 await new Promise((resolve) => setTimeout(resolve, 1000)) setUsers(users.map((user) => (user.id === userId ? { ...user, role: newRole } : user))) setIsLoading(false) setSuccess(`用戶權限已更新為${getRoleText(newRole)}!`) setTimeout(() => setSuccess(""), 3000) } const handleGenerateInvitation = async () => { setError("") // 驗證表單 if (!inviteEmail) { setError("請輸入電子郵件") return } // 檢查電子郵件格式 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailRegex.test(inviteEmail)) { setError("請輸入有效的電子郵件格式") return } // 檢查電子郵件是否已存在 if (users.some((user) => user.email === inviteEmail)) { setError("此電子郵件已被使用或已發送邀請") return } setIsLoading(true) try { const response = await fetch('/api/admin/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: inviteEmail, role: inviteRole }) }) const data = await response.json() if (data.success) { const newInvitation = { id: data.data.user.id, name: data.data.user.name || "", email: data.data.user.email, department: data.data.user.department || "", role: data.data.user.role, status: "invited", joinDate: data.data.user.join_date ? new Date(data.data.user.join_date).toLocaleDateString('zh-TW') : "", lastLogin: "", totalApps: 0, totalReviews: 0, totalLikes: 0, invitationSentAt: new Date().toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }), invitationLink: data.data.invitationLink, invitedRole: inviteRole, // 記錄邀請時的預設角色 } setGeneratedInvitation(newInvitation) setInviteEmail("") setInviteRole("user") setShowInviteUser(false) setShowInvitationLink(true) setSuccess("邀請連結已生成") // 重新載入用戶列表 loadUsers() } else { setError(data.error || "生成邀請連結失敗") } } catch (error) { console.error('邀請用戶錯誤:', error) setError("生成邀請連結時發生錯誤") } finally { setIsLoading(false) } } const handleCopyInvitationLink = async (link: string) => { try { await navigator.clipboard.writeText(link) setSuccess("邀請連結已複製到剪貼簿!") setTimeout(() => setSuccess(""), 3000) } catch (err) { setError("複製失敗,請手動複製連結") setTimeout(() => setError(""), 3000) } } const handleRegenerateInvitation = async (userId: string, email: string) => { setIsLoading(true) // 模擬重新生成邀請連結 await new Promise((resolve) => setTimeout(resolve, 1500)) const newToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) const user = users.find((u) => u.id === userId) const role = (user as any)?.invitedRole || "user" const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (isClient ? window.location.origin : 'http://localhost:3000') const newInvitationLink = `${baseUrl}/register?token=${newToken}&email=${encodeURIComponent(email)}&role=${role}` setUsers( users.map((user) => user.id === userId ? { ...user, invitationLink: newInvitationLink, invitationSentAt: new Date().toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }), } : user, ), ) setIsLoading(false) setSuccess(`${email} 的邀請連結已重新生成!`) setTimeout(() => setSuccess(""), 3000) } const handleUpdateUser = async () => { setError("") if (!editUser.name || !editUser.email) { setError("請填寫所有必填欄位") return } // 檢查電子郵件是否被其他用戶使用 if (users.some((user) => user.email === editUser.email && user.id !== editUser.id)) { setError("此電子郵件已被其他用戶使用") return } setIsLoading(true) try { const response = await fetch(`/api/admin/users/${editUser.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: editUser.name, department: editUser.department, role: editUser.role, status: editUser.status }) }) const data = await response.json() if (data.success) { // 更新本地用戶列表 setUsers(users.map((user) => (user.id === editUser.id ? { ...user, ...editUser } : user))) setShowEditUser(false) setSuccess("用戶資料更新成功!") setTimeout(() => setSuccess(""), 3000) } else { setError(data.error || "更新用戶失敗") } } catch (error) { console.error('更新用戶錯誤:', error) setError("更新用戶時發生錯誤") } setIsLoading(false) } const confirmDeleteUser = async () => { if (!userToDelete) return setIsLoading(true) try { const response = await fetch(`/api/admin/users/${userToDelete.id}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { // 從本地用戶列表中移除 setUsers(users.filter((user) => user.id !== userToDelete.id)) setShowDeleteConfirm(false) setUserToDelete(null) setSuccess("用戶刪除成功!") setTimeout(() => setSuccess(""), 3000) } else { setError(data.error || "刪除用戶失敗") } } catch (error) { console.error('刪除用戶錯誤:', error) setError("刪除用戶時發生錯誤") } setIsLoading(false) } const getRoleColor = (role: string) => { switch (role) { case "admin": return "bg-purple-100 text-purple-800 border-purple-200" case "developer": return "bg-green-100 text-green-800 border-green-200" case "user": return "bg-blue-100 text-blue-800 border-blue-200" default: return "bg-gray-100 text-gray-800 border-gray-200" } } const getStatusColor = (status: string) => { switch (status) { case "active": return "bg-green-100 text-green-800 border-green-200" case "inactive": return "bg-gray-100 text-gray-800 border-gray-200" case "invited": return "bg-yellow-100 text-yellow-800 border-yellow-200" default: return "bg-gray-100 text-gray-800 border-gray-200" } } const getStatusText = (status: string) => { switch (status) { case "active": return "活躍" case "inactive": return "非活躍" case "invited": return "已邀請" default: return status } } const getRoleText = (role: string) => { switch (role) { case "admin": return "管理員" case "developer": return "開發者" case "user": return "一般用戶" default: return "待設定" } } // 獲取活動圖標 const getActivityIcon = (iconName: string) => { switch (iconName) { case 'Calendar': return case 'Heart': return case 'ThumbsUp': return case 'Eye': return case 'Star': return case 'Plus': return default: return } } // 獲取活動顏色 const getActivityColor = (color: string) => { switch (color) { case 'blue': return 'text-blue-600' case 'red': return 'text-red-600' case 'green': return 'text-green-600' case 'purple': return 'text-purple-600' case 'yellow': return 'text-yellow-600' default: return 'text-gray-600' } } const getRoleIcon = (role: string) => { switch (role) { case "admin": return case "developer": return case "user": return default: return } } return (
{/* Success/Error Messages */} {success && ( {success} )} {error && ( {error} )} {/* Header */}

用戶管理

管理平台用戶和權限

{/* Stats Cards */}

總用戶數

{stats.totalUsers}

活躍用戶

{stats.activeUsers}

管理員

{stats.adminCount}

開發者

{stats.developerCount}

待註冊

{stats.inactiveUsers}

本月新增

{stats.newThisMonth}

{/* Filters */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Users Table */} 用戶列表 ({pagination.total}) 管理所有平台用戶 用戶 部門 角色 狀態 加入日期 最後登入 操作 {users.map((user) => (
{user.name ? user.name.charAt(0) : user.email.charAt(0).toUpperCase()}

{user.name || "待註冊"}

{user.email}

{user.status === "invited" && user.invitationSentAt && (

邀請已生成:{user.invitationSentAt}

)}
{user.department || "待設定"}
{getRoleIcon(user.role || (user as any).invitedRole)} {getRoleText(user.role || (user as any).invitedRole)}
{getStatusText(user.status)} {user.joinDate || "-"} {user.lastLogin || "-"} handleViewUser(user)}> 查看詳情 {user.status !== "invited" && ( handleEditUser(user)}> 編輯用戶 )} {user.status === "invited" && user.invitationLink && ( <> handleCopyInvitationLink(user.invitationLink)}> 複製邀請連結 handleRegenerateInvitation(user.id, user.email)}> 重新生成連結 )} {user.status !== "invited" && user.role && ( handleToggleUserStatus(user.id)}> {user.status === "active" ? "停用用戶" : "啟用用戶"} )} handleDeleteUser(user)} className="text-red-600"> 刪除用戶
))}
{/* Invite User Dialog - 包含角色選擇 */} 邀請用戶 生成邀請連結,手動分享給新用戶
setInviteEmail(e.target.value)} placeholder="請輸入電子郵件" className="pl-10" />

用戶註冊時將自動設定為此角色

邀請流程說明:

系統將生成專屬邀請連結,您可以複製連結並手動分享給用戶。用戶點擊連結後可自行設定姓名、部門、密碼並完成註冊。

角色權限說明:

  • 一般用戶:只能瀏覽和收藏應用
  • 開發者:可以提交應用申請
  • 管理員:可以訪問管理後台
{/* Invitation Link Dialog */} 邀請連結已生成 請複製以下連結並分享給用戶 {generatedInvitation && (
{generatedInvitation.email.charAt(0).toUpperCase()}

{generatedInvitation.email}

{getRoleIcon(generatedInvitation.invitedRole)} {getRoleText(generatedInvitation.invitedRole)}

生成時間:{generatedInvitation.invitationSentAt}

注意事項:

  • 請將此連結安全地分享給指定用戶
  • 連結包含用戶的電子郵件和角色資訊
  • 用戶將自動設定為{getRoleText(generatedInvitation.invitedRole)}角色
  • 如需重新生成連結,可在用戶列表中操作
)}
{/* Edit User Dialog */} 編輯用戶 修改用戶資料和權限
setEditUser({ ...editUser, name: e.target.value })} className="pl-10" />
setEditUser({ ...editUser, email: e.target.value })} className="pl-10" />
{/* Delete Confirmation Dialog */} 確認刪除用戶 此操作無法復原。確定要刪除用戶「{userToDelete?.name || userToDelete?.email}」嗎?
{/* User Detail Dialog */} 用戶詳情 查看用戶的詳細資訊和活動記錄 {selectedUser && ( 基本資訊 活動記錄 統計數據
{selectedUser.name ? selectedUser.name.charAt(0) : selectedUser.email.charAt(0).toUpperCase()}

{selectedUser.name || "待註冊用戶"}

{selectedUser.email}

{getRoleIcon(selectedUser.role || (selectedUser as any).invitedRole)} {getRoleText(selectedUser.role || (selectedUser as any).invitedRole)}
{getStatusText(selectedUser.status)}

部門

{selectedUser.department || "待設定"}

加入日期

{selectedUser.joinDate || "尚未註冊"}

最後登入

{selectedUser.lastLogin || "尚未登入"}

用戶ID

{selectedUser.id}

{selectedUser.status === "invited" && selectedUser.invitationSentAt && ( <>

邀請生成時間

{selectedUser.invitationSentAt}

邀請狀態

等待用戶註冊

)}
{selectedUser.status === "invited" && selectedUser.invitationLink && (
)}
{selectedUser.status === "invited" ? (

用戶尚未註冊,暫無活動記錄

) : ( <> {/* 篩選控件 */}
handleActivityFilter({ search: e.target.value })} className="mt-1 h-8 text-sm" />
handleActivityFilter({ startDate: e.target.value })} className="mt-1 h-8 text-sm" />
handleActivityFilter({ endDate: e.target.value })} className="mt-1 h-8 text-sm" />
{/* 活動記錄列表 */} {userActivities.length === 0 ? (

暫無活動記錄

) : ( <>
{userActivities.map((activity, index) => (
{getActivityIcon(activity.icon)}

{activity.action_text}

{new Date(activity.created_at).toLocaleString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}

))}
{/* 分頁控件 */} {activityPagination.totalPages > 1 && (
顯示第 {((activityPagination.page - 1) * activityPagination.limit) + 1} - {Math.min(activityPagination.page * activityPagination.limit, activityPagination.total)} 項,共 {activityPagination.total} 項
第 {activityPagination.page} 頁,共 {activityPagination.totalPages} 頁
)} )} )}

{userStats.totalApps}

創建應用

{userStats.totalReviews}

撰寫評價

{userStats.totalLikes}

獲得讚數

{userStats.loginDays}

登入天數

)}
) }