"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 { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Badge } from "@/components/ui/badge" import { Brain, Lightbulb, BarChart3, ArrowLeft, Search, Download, Filter, ChevronLeft, ChevronRight, Loader2, Eye } 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) const [selectedResult, setSelectedResult] = useState(null) const [showDetailModal, setShowDetailModal] = useState(false) const [detailData, setDetailData] = useState(null) const [isLoadingDetail, setIsLoadingDetail] = useState(false) 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('匯出時發生錯誤,請稍後再試') } } const handleViewDetail = async (result: TestResult) => { setSelectedResult(result) setShowDetailModal(true) setIsLoadingDetail(true) try { const response = await fetch(`/api/admin/test-results/detail?testResultId=${result.id}&testType=${result.type}`) const data = await response.json() if (data.success) { console.log('前端收到的詳細資料:', data.data) console.log('題目數量:', data.data.questions?.length || 0) setDetailData(data.data) } else { console.error('獲取詳細結果失敗:', data.message) alert('獲取詳細結果失敗,請稍後再試') } } catch (error) { console.error('獲取詳細結果錯誤:', error) alert('獲取詳細結果時發生錯誤,請稍後再試') } finally { setIsLoadingDetail(false) } } 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 })()}
)} )}
{/* 詳細結果模態框 */} 測驗詳細結果 {selectedResult && `${selectedResult.userName} - ${getTestTypeInfo(selectedResult.type).name}`} {isLoadingDetail ? (
載入詳細結果中...
) : detailData ? (
{/* 基本資訊 */} 基本資訊

{detailData.user.name}

{detailData.user.email}

{detailData.user.department}

{formatDate(detailData.result.completedAt)}

{detailData.result.score}

{getScoreLevel(detailData.result.score).level}
{/* 題目詳情 */} {detailData.questions && detailData.questions.length > 0 && ( 答題詳情
{/* 邏輯思維題目 */} {detailData.questions.filter((q: any) => q.type === 'logic').length > 0 && (

邏輯思維題目

{detailData.questions .filter((q: any) => q.type === 'logic') .map((question: any, index: number) => (

第 {index + 1} 題

{question.isCorrect ? "正確" : "錯誤"}

{question.question}

{question.option_a &&

A. {question.option_a}

} {question.option_b &&

B. {question.option_b}

} {question.option_c &&

C. {question.option_c}

} {question.option_d &&

D. {question.option_d}

} {question.option_e &&

E. {question.option_e}

}

用戶答案: {question.userAnswer}

正確答案: {question.correctAnswer}

{question.explanation && (

{question.explanation}

)}
))}
)} {/* 創意能力題目 */} {detailData.questions.filter((q: any) => q.type === 'creative').length > 0 && (

創意能力題目

{detailData.questions .filter((q: any) => q.type === 'creative') .map((question: any, index: number) => (

第 {index + 1} 題

{question.score} 分

{question.statement}

{question.userAnswer}

{question.score} 分

))}
)}
)} {/* 綜合測試詳細分析 */} {detailData.result.type === 'combined' && detailData.result.details && ( 綜合能力分析

邏輯思維

{detailData.result.details.logicScore}

創意能力

{detailData.result.details.creativeScore}

能力平衡

{detailData.result.details.abilityBalance}

)}
) : (
無法載入詳細結果
)}
) }