新增儀表板快速操作功能

This commit is contained in:
2025-08-05 12:35:41 +08:00
parent 279720e276
commit d0c4adf243
3 changed files with 93 additions and 10 deletions

View File

@@ -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} />
} }
} }

View File

@@ -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>

View File

@@ -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">