From 5a8cc7ec18a79b4208ecc6dc996ef908b7037203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Mon, 29 Sep 2025 21:06:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=A6=E4=BD=9C=E9=83=A8=E9=96=80=E5=88=86?= =?UTF-8?q?=E6=9E=90=E6=95=B8=E6=93=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/analytics/page.tsx | 158 ++-------------- app/api/admin/analytics/departments/route.ts | 180 +++++++++++++++++++ 2 files changed, 195 insertions(+), 143 deletions(-) create mode 100644 app/api/admin/analytics/departments/route.ts diff --git a/app/admin/analytics/page.tsx b/app/admin/analytics/page.tsx index e70c0bd..2a2e1f0 100644 --- a/app/admin/analytics/page.tsx +++ b/app/admin/analytics/page.tsx @@ -57,153 +57,25 @@ function AnalyticsContent() { totalTests: 0, }) - const departments = ["人力資源部", "資訊技術部", "財務部", "行銷部", "業務部", "研發部", "客服部", "其他"] useEffect(() => { loadAnalyticsData() }, []) - const loadAnalyticsData = () => { - // Load users - const users: User[] = JSON.parse(localStorage.getItem("hr_users") || "[]") + const loadAnalyticsData = async () => { + try { + const response = await fetch('/api/admin/analytics/departments') + const data = await response.json() - // Load all test results - const allResults: TestResult[] = [] - - users.forEach((user: User) => { - // Check for logic test results - const logicKey = `logicTestResults_${user.id}` - const logicResults = localStorage.getItem(logicKey) - if (logicResults) { - const data = JSON.parse(logicResults) - allResults.push({ - userId: user.id, - userName: user.name, - userDepartment: user.department, - type: "logic", - score: data.score, - completedAt: data.completedAt, - }) + if (data.success) { + setDepartmentStats(data.data.departmentStats) + setOverallStats(data.data.overallStats) + } else { + console.error('獲取部門分析數據失敗:', data.message) } - - // Check for creative test results - const creativeKey = `creativeTestResults_${user.id}` - const creativeResults = localStorage.getItem(creativeKey) - if (creativeResults) { - const data = JSON.parse(creativeResults) - allResults.push({ - userId: user.id, - userName: user.name, - userDepartment: user.department, - type: "creative", - score: data.score, - completedAt: data.completedAt, - }) - } - - // Check for combined test results - const combinedKey = `combinedTestResults_${user.id}` - const combinedResults = localStorage.getItem(combinedKey) - if (combinedResults) { - const data = JSON.parse(combinedResults) - allResults.push({ - userId: user.id, - userName: user.name, - userDepartment: user.department, - type: "combined", - score: data.overallScore, - completedAt: data.completedAt, - }) - } - }) - - // Calculate department statistics - const deptStats: DepartmentStats[] = departments - .map((dept) => { - const deptUsers = users.filter((u) => u.department === dept) - const deptResults = allResults.filter((r) => r.userDepartment === dept) - const participatedUsers = new Set(deptResults.map((r) => r.userId)).size - - // Calculate average scores by test type - const logicResults = deptResults.filter((r) => r.type === "logic") - const creativeResults = deptResults.filter((r) => r.type === "creative") - const combinedResults = deptResults.filter((r) => r.type === "combined") - - const averageLogicScore = - logicResults.length > 0 - ? Math.round(logicResults.reduce((sum, r) => sum + r.score, 0) / logicResults.length) - : 0 - - const averageCreativeScore = - creativeResults.length > 0 - ? Math.round(creativeResults.reduce((sum, r) => sum + r.score, 0) / creativeResults.length) - : 0 - - const averageCombinedScore = - combinedResults.length > 0 - ? Math.round(combinedResults.reduce((sum, r) => sum + r.score, 0) / combinedResults.length) - : 0 - - // Calculate overall average - const allScores = [averageLogicScore, averageCreativeScore, averageCombinedScore].filter((s) => s > 0) - const overallAverage = - allScores.length > 0 ? Math.round(allScores.reduce((sum, s) => sum + s, 0) / allScores.length) : 0 - - // Find top performer - const userScores = new Map() - deptResults.forEach((result) => { - if (!userScores.has(result.userId)) { - userScores.set(result.userId, []) - } - userScores.get(result.userId)!.push(result.score) - }) - - let topPerformer: string | null = null - let topScore = 0 - userScores.forEach((scores, userId) => { - const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length - if (avgScore > topScore) { - topScore = avgScore - const user = users.find((u) => u.id === userId) - topPerformer = user ? user.name : null - } - }) - - return { - department: dept, - totalUsers: deptUsers.length, - participatedUsers, - participationRate: deptUsers.length > 0 ? Math.round((participatedUsers / deptUsers.length) * 100) : 0, - averageLogicScore, - averageCreativeScore, - averageCombinedScore, - overallAverage, - topPerformer, - testCounts: { - logic: logicResults.length, - creative: creativeResults.length, - combined: combinedResults.length, - }, - } - }) - .filter((stat) => stat.totalUsers > 0) // Only show departments with users - - setDepartmentStats(deptStats) - - // Calculate overall statistics - const totalUsers = users.length - const totalParticipants = new Set(allResults.map((r) => r.userId)).size - const overallParticipationRate = totalUsers > 0 ? Math.round((totalParticipants / totalUsers) * 100) : 0 - const averageScore = - allResults.length > 0 ? Math.round(allResults.reduce((sum, r) => sum + r.score, 0) / allResults.length) : 0 - - setOverallStats({ - totalUsers, - totalParticipants, - overallParticipationRate, - averageScore, - totalTests: allResults.length, - }) + } catch (error) { + console.error('獲取部門分析數據錯誤:', error) + } } const getScoreColor = (score: number) => { @@ -314,9 +186,9 @@ function AnalyticsContent() { 所有部門 - {departments.map((dept) => ( - - {dept} + {departmentStats.map((stat) => ( + + {stat.department} ))} diff --git a/app/api/admin/analytics/departments/route.ts b/app/api/admin/analytics/departments/route.ts new file mode 100644 index 0000000..dc4db61 --- /dev/null +++ b/app/api/admin/analytics/departments/route.ts @@ -0,0 +1,180 @@ +import { NextRequest, NextResponse } from "next/server" +import { executeQuery } from "@/lib/database/connection" + +export async function GET(request: NextRequest) { + try { + // 獲取所有用戶 + const users = await executeQuery(` + SELECT id, name, department, role + FROM users + ORDER BY department, name + `) + + // 獲取所有測試結果 + const testResults = await executeQuery(` + SELECT + tr.user_id, + tr.test_type, + tr.score, + tr.completed_at, + u.name as user_name, + u.department + FROM test_results tr + LEFT JOIN users u ON tr.user_id = u.id + ORDER BY u.department, tr.completed_at DESC + `) + + // 獲取綜合測試結果 + const combinedResults = await executeQuery(` + SELECT + ctr.user_id, + ctr.logic_score, + ctr.creativity_score, + ctr.overall_score, + ctr.completed_at, + u.name as user_name, + u.department + FROM combined_test_results ctr + LEFT JOIN users u ON ctr.user_id = u.id + ORDER BY u.department, ctr.completed_at DESC + `) + + // 按部門分組統計 + const departmentMap = new Map() + + // 初始化所有部門 + const allDepartments = Array.from(new Set(users.map((u: any) => u.department))) + + allDepartments.forEach(dept => { + departmentMap.set(dept, { + department: dept, + totalUsers: 0, + participatedUsers: new Set(), + logicScores: [], + creativeScores: [], + combinedScores: [], + testCounts: { + logic: 0, + creative: 0, + combined: 0 + } + }) + }) + + // 統計用戶數據 + users.forEach((user: any) => { + const dept = user.department + if (departmentMap.has(dept)) { + departmentMap.get(dept).totalUsers++ + } + }) + + // 統計測試結果 + testResults.forEach((result: any) => { + const dept = result.department + if (departmentMap.has(dept)) { + const deptData = departmentMap.get(dept) + deptData.participatedUsers.add(result.user_id) + deptData.testCounts[result.test_type]++ + + if (result.test_type === 'logic') { + deptData.logicScores.push(result.score) + } else if (result.test_type === 'creative') { + deptData.creativeScores.push(result.score) + } + } + }) + + // 統計綜合測試結果 + combinedResults.forEach((result: any) => { + const dept = result.department + if (departmentMap.has(dept)) { + const deptData = departmentMap.get(dept) + deptData.participatedUsers.add(result.user_id) + deptData.testCounts.combined++ + deptData.combinedScores.push(result.overall_score) + } + }) + + // 計算部門統計數據 + const departmentStats = Array.from(departmentMap.values()).map(deptData => { + const participatedCount = deptData.participatedUsers.size + const participationRate = deptData.totalUsers > 0 + ? Math.round((participatedCount / deptData.totalUsers) * 100) + : 0 + + // 計算平均分數 + const averageLogicScore = deptData.logicScores.length > 0 + ? Math.round(deptData.logicScores.reduce((sum: number, score: number) => sum + score, 0) / deptData.logicScores.length) + : 0 + + const averageCreativeScore = deptData.creativeScores.length > 0 + ? Math.round(deptData.creativeScores.reduce((sum: number, score: number) => sum + score, 0) / deptData.creativeScores.length) + : 0 + + const averageCombinedScore = deptData.combinedScores.length > 0 + ? Math.round(deptData.combinedScores.reduce((sum: number, score: number) => sum + score, 0) / deptData.combinedScores.length) + : 0 + + // 計算整體平均分數 + const allScores = [averageLogicScore, averageCreativeScore, averageCombinedScore].filter(s => s > 0) + const overallAverage = allScores.length > 0 + ? Math.round(allScores.reduce((sum, score) => sum + score, 0) / allScores.length) + : 0 + + return { + department: deptData.department, + totalUsers: deptData.totalUsers, + participatedUsers: participatedCount, + participationRate, + averageLogicScore, + averageCreativeScore, + averageCombinedScore, + overallAverage, + testCounts: deptData.testCounts + } + }) + + // 計算整體統計 + const totalUsers = users.length + const totalParticipants = new Set([ + ...testResults.map((r: any) => r.user_id), + ...combinedResults.map((r: any) => r.user_id) + ]).size + const overallParticipationRate = totalUsers > 0 + ? Math.round((totalParticipants / totalUsers) * 100) + : 0 + + // 計算整體平均分數 + const allScores = [ + ...testResults.map((r: any) => r.score), + ...combinedResults.map((r: any) => r.overall_score) + ] + const averageScore = allScores.length > 0 + ? Math.round(allScores.reduce((sum, score) => sum + score, 0) / allScores.length) + : 0 + + const totalTests = testResults.length + combinedResults.length + + return NextResponse.json({ + success: true, + data: { + departmentStats, + overallStats: { + totalUsers, + totalParticipants, + overallParticipationRate, + averageScore, + totalTests + } + } + }) + + } catch (error) { + console.error("獲取部門分析數據失敗:", error) + return NextResponse.json( + { success: false, message: "獲取部門分析數據失敗", error: error instanceof Error ? error.message : String(error) }, + { status: 500 } + ) + } +}