"use client" import { useState, useEffect } from "react" import { ProtectedRoute } from "@/components/protected-route" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { Brain, Lightbulb, BarChart3, ArrowLeft, Search, Download, Filter, ChevronLeft, ChevronRight, Loader2 } from "lucide-react" import Link from "next/link" import { useAuth } from "@/lib/hooks/use-auth" interface TestResult { id: string userId: string userName: string userDepartment: string userEmail: string type: "logic" | "creative" | "combined" score: number completedAt: string details?: any } interface AdminTestResultsStats { totalResults: number filteredResults: number averageScore: number totalUsers: number usersWithResults: number participationRate: number testTypeCounts: { logic: number creative: number combined: number } } interface PaginationInfo { currentPage: number totalPages: number totalResults: number limit: number hasNextPage: boolean hasPrevPage: boolean } export default function AdminResultsPage() { return ( ) } function AdminResultsContent() { const { user } = useAuth() const [results, setResults] = useState([]) const [stats, setStats] = useState({ totalResults: 0, filteredResults: 0, averageScore: 0, totalUsers: 0, usersWithResults: 0, participationRate: 0, testTypeCounts: { logic: 0, creative: 0, combined: 0 } }) const [pagination, setPagination] = useState({ currentPage: 1, totalPages: 1, totalResults: 0, limit: 10, hasNextPage: false, hasPrevPage: false }) const [departments, setDepartments] = useState([]) const [searchTerm, setSearchTerm] = useState("") const [departmentFilter, setDepartmentFilter] = useState("all") const [testTypeFilter, setTestTypeFilter] = useState("all") const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { loadData() }, []) useEffect(() => { loadData() }, [searchTerm, departmentFilter, testTypeFilter, pagination.currentPage]) const loadData = async () => { setIsLoading(true) setError(null) try { const params = new URLSearchParams({ search: searchTerm, department: departmentFilter, testType: testTypeFilter, page: pagination.currentPage.toString(), limit: pagination.limit.toString() }) const response = await fetch(`/api/admin/test-results?${params}`) const data = await response.json() if (data.success) { setResults(data.data.results) setStats(data.data.stats) setPagination(data.data.pagination) setDepartments(data.data.departments) } else { setError(data.message || "載入資料失敗") } } catch (error) { console.error("載入測驗結果失敗:", error) setError("載入資料時發生錯誤") } finally { setIsLoading(false) } } const handleSearch = (value: string) => { setSearchTerm(value) setPagination(prev => ({ ...prev, currentPage: 1 })) } const handleDepartmentChange = (value: string) => { setDepartmentFilter(value) setPagination(prev => ({ ...prev, currentPage: 1 })) } const handleTestTypeChange = (value: string) => { setTestTypeFilter(value) setPagination(prev => ({ ...prev, currentPage: 1 })) } const handlePageChange = (page: number) => { setPagination(prev => ({ ...prev, currentPage: page })) } const handlePreviousPage = () => { if (pagination.hasPrevPage) { handlePageChange(pagination.currentPage - 1) } } const handleNextPage = () => { if (pagination.hasNextPage) { handlePageChange(pagination.currentPage + 1) } } const getTestTypeInfo = (type: string) => { switch (type) { case "logic": return { name: "邏輯思維", icon: Brain, color: "bg-primary", textColor: "text-primary", } case "creative": return { name: "創意能力", icon: Lightbulb, color: "bg-accent", textColor: "text-accent", } case "combined": return { name: "綜合能力", icon: BarChart3, color: "bg-gradient-to-r from-primary to-accent", textColor: "text-primary", } default: return { name: "未知", icon: BarChart3, color: "bg-muted", textColor: "text-muted-foreground", } } } const getScoreLevel = (score: number) => { if (score >= 90) return { level: "優秀", color: "bg-green-500" } if (score >= 80) return { level: "良好", color: "bg-blue-500" } if (score >= 70) return { level: "中等", color: "bg-yellow-500" } if (score >= 60) return { level: "及格", color: "bg-orange-500" } return { level: "待加強", color: "bg-red-500" } } const formatDate = (dateString: string) => { return new Date(dateString).toLocaleString("zh-TW", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }) } const handleExport = async () => { try { const params = new URLSearchParams({ search: searchTerm, department: departmentFilter, testType: testTypeFilter }) const response = await fetch(`/api/admin/test-results/export?${params}`) const data = await response.json() if (data.success) { // 解碼 Base64 資料,保留 UTF-8 BOM const binaryString = atob(data.data) const bytes = new Uint8Array(binaryString.length) for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i) } // 創建 Blob,保留原始字節資料 const blob = new Blob([bytes], { type: 'text/csv;charset=utf-8' }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = data.filename document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(url) } else { console.error('匯出失敗:', data.message) alert('匯出失敗,請稍後再試') } } catch (error) { console.error('匯出錯誤:', error) alert('匯出時發生錯誤,請稍後再試') } } return (
{/* Header */}

所有測試結果

查看和分析所有員工的測試結果

{/* Summary Cards */}
{stats.totalResults}
總測試次數
{stats.averageScore}
平均分數
{stats.totalUsers}
總用戶數
{stats.participationRate}%
參與率
{/* Filter Section */} 篩選條件
handleSearch(e.target.value)} className="pl-10" />
{/* Test Results List */} 測試結果列表 顯示 {pagination.totalResults} 筆結果 (共 {stats.totalResults} 筆) {isLoading ? (
載入中...
) : error ? (
{error}
) : results.length === 0 ? (
沒有找到符合條件的測試結果
) : ( <> 用戶 部門 測試類型 分數 等級 完成時間 {results.map((result) => { const testTypeInfo = getTestTypeInfo(result.type) const scoreLevel = getScoreLevel(result.score) const IconComponent = testTypeInfo.icon return (
{result.userName}
{result.userEmail}
{result.userDepartment}
{testTypeInfo.name}
{result.score}
{scoreLevel.level}
{formatDate(result.completedAt)}
) })}
{/* Pagination */} {pagination.totalPages > 1 && (
顯示第 {(pagination.currentPage - 1) * pagination.limit + 1} - {Math.min(pagination.currentPage * pagination.limit, pagination.totalResults)} 筆,共 {pagination.totalResults} 筆
{/* Desktop Pagination */}
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((page) => ( ))}
{/* Mobile Pagination */}
{(() => { const maxVisiblePages = 3 const startPage = Math.max(1, pagination.currentPage - 1) const endPage = Math.min(pagination.totalPages, startPage + maxVisiblePages - 1) const pages = [] // 如果不在第一頁,顯示第一頁和省略號 if (startPage > 1) { pages.push( ) if (startPage > 2) { pages.push( ... ) } } // 顯示當前頁附近的頁碼 for (let i = startPage; i <= endPage; i++) { pages.push( ) } // 如果不在最後一頁,顯示省略號和最後一頁 if (endPage < pagination.totalPages) { if (endPage < pagination.totalPages - 1) { pages.push( ... ) } pages.push( ) } return pages })()}
)} )}
) }