新增儀表板快速操作功能
This commit is contained in:
@@ -15,7 +15,7 @@ export function AdminPanel() {
|
|||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
switch (currentPage) {
|
switch (currentPage) {
|
||||||
case "dashboard":
|
case "dashboard":
|
||||||
return <AdminDashboard />
|
return <AdminDashboard onPageChange={setCurrentPage} />
|
||||||
case "users":
|
case "users":
|
||||||
return <UserManagement />
|
return <UserManagement />
|
||||||
case "apps":
|
case "apps":
|
||||||
@@ -27,7 +27,7 @@ export function AdminPanel() {
|
|||||||
case "settings":
|
case "settings":
|
||||||
return <SystemSettings />
|
return <SystemSettings />
|
||||||
default:
|
default:
|
||||||
return <AdminDashboard />
|
return <AdminDashboard onPageChange={setCurrentPage} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,9 +20,19 @@ const recentActivities: any[] = []
|
|||||||
|
|
||||||
const topApps: any[] = []
|
const topApps: any[] = []
|
||||||
|
|
||||||
export function AdminDashboard() {
|
interface AdminDashboardProps {
|
||||||
|
onPageChange?: (page: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminDashboard({ onPageChange }: AdminDashboardProps) {
|
||||||
const { competitions } = useCompetition()
|
const { competitions } = useCompetition()
|
||||||
|
|
||||||
|
const handleManageUsers = () => {
|
||||||
|
if (onPageChange) {
|
||||||
|
onPageChange("users")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Welcome Section */}
|
{/* Welcome Section */}
|
||||||
@@ -150,7 +160,7 @@ export function AdminDashboard() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<Button className="h-20 flex flex-col space-y-2">
|
<Button className="h-20 flex flex-col space-y-2" onClick={handleManageUsers}>
|
||||||
<Users className="w-6 h-6" />
|
<Users className="w-6 h-6" />
|
||||||
<span>管理用戶</span>
|
<span>管理用戶</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -13,6 +13,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
|
|||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||||
|
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination"
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
@@ -67,6 +68,12 @@ export function UserManagement() {
|
|||||||
totalApps: 0,
|
totalApps: 0,
|
||||||
totalReviews: 0
|
totalReviews: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
const [totalPages, setTotalPages] = useState(1)
|
||||||
|
const [totalUsers, setTotalUsers] = useState(0)
|
||||||
|
const [itemsPerPage] = useState(10) // Default to 10 items per page
|
||||||
|
|
||||||
// 載入用戶資料
|
// 載入用戶資料
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -74,8 +81,8 @@ export function UserManagement() {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
// 獲取用戶列表
|
// 獲取用戶列表 with pagination
|
||||||
const usersResponse = await fetch('/api/users', {
|
const usersResponse = await fetch(`/api/users?page=${currentPage}&limit=${itemsPerPage}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
}
|
}
|
||||||
@@ -84,6 +91,8 @@ export function UserManagement() {
|
|||||||
if (usersResponse.ok) {
|
if (usersResponse.ok) {
|
||||||
const usersData = await usersResponse.json()
|
const usersData = await usersResponse.json()
|
||||||
setUsers(usersData.users || [])
|
setUsers(usersData.users || [])
|
||||||
|
setTotalPages(usersData.pagination?.totalPages || 1)
|
||||||
|
setTotalUsers(usersData.pagination?.total || 0)
|
||||||
} else {
|
} else {
|
||||||
const errorData = await usersResponse.json().catch(() => ({}))
|
const errorData = await usersResponse.json().catch(() => ({}))
|
||||||
console.error('獲取用戶列表失敗:', errorData.error || usersResponse.statusText)
|
console.error('獲取用戶列表失敗:', errorData.error || usersResponse.statusText)
|
||||||
@@ -105,14 +114,15 @@ export function UserManagement() {
|
|||||||
console.error('獲取統計資料失敗:', errorData.error || statsResponse.statusText)
|
console.error('獲取統計資料失敗:', errorData.error || statsResponse.statusText)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('載入資料失敗:', error)
|
console.error('載入用戶資料失敗:', error)
|
||||||
|
setError('載入用戶資料失敗')
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchUsers()
|
fetchUsers()
|
||||||
}, [])
|
}, [currentPage, itemsPerPage]) // Re-fetch when page changes
|
||||||
|
|
||||||
// 重新獲取統計數據的函數
|
// 重新獲取統計數據的函數
|
||||||
const refreshStats = async () => {
|
const refreshStats = async () => {
|
||||||
@@ -734,8 +744,11 @@ export function UserManagement() {
|
|||||||
{/* Users Table */}
|
{/* Users Table */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>用戶列表 ({filteredUsers.length})</CardTitle>
|
<CardTitle>用戶列表 ({totalUsers} 總用戶)</CardTitle>
|
||||||
<CardDescription>管理所有平台用戶</CardDescription>
|
<CardDescription>
|
||||||
|
管理所有平台用戶 - 第 {currentPage} 頁,共 {totalPages} 頁
|
||||||
|
{totalPages > 1 && ` (每頁 ${itemsPerPage} 筆)`}
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -859,6 +872,66 @@ export function UserManagement() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex justify-center items-center py-4">
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (currentPage > 1) setCurrentPage(currentPage - 1)
|
||||||
|
}}
|
||||||
|
className={currentPage === 1 ? "pointer-events-none opacity-50" : ""}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
|
||||||
|
{/* Page numbers */}
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
let pageNum
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageNum = i + 1
|
||||||
|
} else if (currentPage <= 3) {
|
||||||
|
pageNum = i + 1
|
||||||
|
} else if (currentPage >= totalPages - 2) {
|
||||||
|
pageNum = totalPages - 4 + i
|
||||||
|
} else {
|
||||||
|
pageNum = currentPage - 2 + i
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaginationItem key={pageNum}>
|
||||||
|
<PaginationLink
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setCurrentPage(pageNum)
|
||||||
|
}}
|
||||||
|
isActive={currentPage === pageNum}
|
||||||
|
>
|
||||||
|
{pageNum}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (currentPage < totalPages) setCurrentPage(currentPage + 1)
|
||||||
|
}}
|
||||||
|
className={currentPage === totalPages ? "pointer-events-none opacity-50" : ""}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Invite User Dialog - 包含角色選擇 */}
|
{/* Invite User Dialog - 包含角色選擇 */}
|
||||||
<Dialog open={showInviteUser} onOpenChange={setShowInviteUser}>
|
<Dialog open={showInviteUser} onOpenChange={setShowInviteUser}>
|
||||||
<DialogContent className="max-w-md">
|
<DialogContent className="max-w-md">
|
||||||
|
Reference in New Issue
Block a user