"use client" import React, { useState, useEffect } from "react" import { useCompetition } from "@/contexts/competition-context" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { Alert, AlertDescription } from "@/components/ui/alert" import { Separator } from "@/components/ui/separator" import { Progress } from "@/components/ui/progress" import { Checkbox } from "@/components/ui/checkbox" import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination" import { Trophy, Plus, Award, MoreHorizontal, Edit, Play, CheckCircle, AlertTriangle, Crown, UserPlus, Loader2, Star, StarOff, Users, Settings, ClipboardList, Link, UserCheck, UserX, Upload, Filter, User, Mail, Trash2, Eye, Search, X, } from "lucide-react" import type { CompetitionRule, CompetitionAwardType } from "@/types/competition" import { ScoringManagement } from "./scoring-management" // Competition data - empty for production const mockIndividualApps: any[] = [] // Teams data - empty for production const initialTeams: any[] = [] export function CompetitionManagement() { const { competitions, currentCompetition, setCurrentCompetition, addCompetition, updateCompetition, deleteCompetition, judges, addJudge, updateJudge, deleteJudge, awards, addAward, getAppDetailedScores, judgeScores, getAppJudgeScores, submitJudgeScore, } = useCompetition() // Teams state - managed locally for now const [teams, setTeams] = useState(initialTeams) // 資料庫整合狀態 const [dbCompetitions, setDbCompetitions] = useState([]) const [isLoadingDb, setIsLoadingDb] = useState(false) const [dbStats, setDbStats] = useState(null) // 評審資料庫整合狀態 const [dbJudges, setDbJudges] = useState([]) const [isLoadingJudges, setIsLoadingJudges] = useState(false) const [judgeStats, setJudgeStats] = useState(null) // 團隊資料庫整合狀態 const [dbTeams, setDbTeams] = useState([]) const [isLoadingTeams, setIsLoadingTeams] = useState(false) const [teamStats, setTeamStats] = useState(null) // 可用應用狀態 const [availableApps, setAvailableApps] = useState([]) const [isLoadingApps, setIsLoadingApps] = useState(false) // 可用用戶狀態 const [availableUsers, setAvailableUsers] = useState([]) const [isLoadingUsers, setIsLoadingUsers] = useState(false) const [showCreateCompetition, setShowCreateCompetition] = useState(false) const [showAddJudge, setShowAddJudge] = useState(false) const [showCreateAward, setShowCreateAward] = useState(false) const [showScoringManagement, setShowScoringManagement] = useState(false) const [showJudgeLinks, setShowJudgeLinks] = useState(false) const [showManualScoring, setShowManualScoring] = useState(false) const [showCreateTeam, setShowCreateTeam] = useState(false) const [showTeamDetail, setShowTeamDetail] = useState(false) const [showDeleteTeamConfirm, setShowDeleteTeamConfirm] = useState(false) const [selectedCompetition, setSelectedCompetition] = useState(null) const [selectedTeam, setSelectedTeam] = useState(null) const [teamToDelete, setTeamToDelete] = useState(null) const [isLoading, setIsLoading] = useState(false) const [success, setSuccess] = useState("") const [error, setError] = useState("") const [showCompetitionDetail, setShowCompetitionDetail] = useState(false) const [showDeleteCompetitionConfirm, setShowDeleteCompetitionConfirm] = useState(false) const [showChangeStatusDialog, setShowChangeStatusDialog] = useState(false) const [selectedCompetitionForAction, setSelectedCompetitionForAction] = useState(null) const [newStatus, setNewStatus] = useState("") // 奖项搜索和筛选状态 const [awardSearchQuery, setAwardSearchQuery] = useState("") const [awardYearFilter, setAwardYearFilter] = useState("all") // 資料庫 API 調用函數 const fetchCompetitions = async () => { setIsLoadingDb(true) try { const response = await fetch('/api/admin/competitions') const data = await response.json() if (data.success) { setDbCompetitions(data.data) } else { setError('獲取競賽列表失敗: ' + data.message) } } catch (error) { console.error('獲取競賽列表失敗:', error) setError('獲取競賽列表失敗') } finally { setIsLoadingDb(false) } } // 獲取當前競賽 const fetchCurrentCompetition = async () => { try { const response = await fetch('/api/admin/competitions/current') const data = await response.json() if (data.success) { setCurrentCompetition(data.data) } } catch (error) { console.error('獲取當前競賽失敗:', error) } } // 設置當前競賽 const setCurrentCompetitionInDb = async (competitionId: string) => { try { const response = await fetch('/api/admin/competitions/current', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ competitionId }), }) const data = await response.json() if (data.success) { setCurrentCompetition(data.data) setSuccess('當前競賽設置成功!') setTimeout(() => setSuccess(''), 3000) } else { setError('設置當前競賽失敗: ' + data.message) setTimeout(() => setError(''), 3000) } } catch (error) { console.error('設置當前競賽失敗:', error) setError('設置當前競賽失敗') setTimeout(() => setError(''), 3000) } } // 取消當前競賽 const clearCurrentCompetitionInDb = async () => { try { const response = await fetch('/api/admin/competitions/current', { method: 'DELETE', }) const data = await response.json() if (data.success) { setCurrentCompetition(null) setSuccess('當前競賽已取消!') setTimeout(() => setSuccess(''), 3000) } else { setError('取消當前競賽失敗: ' + data.message) setTimeout(() => setError(''), 3000) } } catch (error) { console.error('取消當前競賽失敗:', error) setError('取消當前競賽失敗') setTimeout(() => setError(''), 3000) } } const fetchCompetitionStats = async () => { try { const response = await fetch('/api/admin/competitions/stats') const data = await response.json() if (data.success) { setDbStats(data.data) } } catch (error) { console.error('獲取競賽統計失敗:', error) } } const createCompetitionInDb = async (competitionData: any) => { try { const response = await fetch('/api/admin/competitions', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(competitionData) }) const data = await response.json() if (data.success) { setSuccess('競賽創建成功!') await fetchCompetitions() // 重新獲取列表 await fetchCompetitionStats() // 重新獲取統計 return data.data } else { setError('創建競賽失敗: ' + data.message) return null } } catch (error) { console.error('創建競賽失敗:', error) setError('創建競賽失敗') return null } } const updateCompetitionInDb = async (id: string, updates: any) => { try { const response = await fetch(`/api/admin/competitions/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(updates) }) const data = await response.json() if (data.success) { setSuccess('競賽更新成功!') await fetchCompetitions() // 重新獲取列表 await fetchCompetitionStats() // 重新獲取統計 return data.data } else { setError('更新競賽失敗: ' + data.message) return null } } catch (error) { console.error('更新競賽失敗:', error) setError('更新競賽失敗') return null } } const deleteCompetitionInDb = async (id: string) => { try { const response = await fetch(`/api/admin/competitions/${id}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { setSuccess('競賽刪除成功!') await fetchCompetitions() // 重新獲取列表 await fetchCompetitionStats() // 重新獲取統計 return true } else { setError('刪除競賽失敗: ' + data.message) return false } } catch (error) { console.error('刪除競賽失敗:', error) setError('刪除競賽失敗') return false } } // 評審資料庫 API 調用函數 const fetchJudges = async () => { setIsLoadingJudges(true) try { // 添加時間戳避免緩存 const response = await fetch(`/api/admin/judges?t=${Date.now()}`, { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }) const data = await response.json() console.log('🔍 fetchJudges 響應:', data) if (data.success) { console.log('📊 獲取到的評審數據:', data.data) setDbJudges(data.data) } else { setError('獲取評審列表失敗: ' + data.message) } } catch (error) { console.error('獲取評審列表失敗:', error) setError('獲取評審列表失敗') } finally { setIsLoadingJudges(false) } } const fetchJudgeStats = async () => { try { const response = await fetch('/api/admin/judges/stats') const data = await response.json() console.log('評審統計 API 響應:', data) if (data.success) { setJudgeStats(data.data) console.log('評審統計設置成功:', data.data) } else { console.error('評審統計 API 失敗:', data.message) } } catch (error) { console.error('獲取評審統計失敗:', error) } } // 團隊資料庫 API 調用函數 const fetchTeams = async () => { setIsLoadingTeams(true) try { // 添加時間戳避免緩存 const response = await fetch(`/api/admin/teams?t=${Date.now()}`, { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }) const data = await response.json() console.log('🔍 fetchTeams 響應:', data) if (data.success) { console.log('📊 獲取到的團隊數據:', data.data) setDbTeams(data.data) } else { setError('獲取團隊列表失敗: ' + data.message) } } catch (error) { console.error('獲取團隊列表失敗:', error) setError('獲取團隊列表失敗') } finally { setIsLoadingTeams(false) } } const fetchTeamStats = async () => { try { const response = await fetch('/api/admin/teams/stats') const data = await response.json() console.log('團隊統計 API 響應:', data) if (data.success) { setTeamStats(data.data) console.log('團隊統計設置成功:', data.data) } } catch (error) { console.error('獲取團隊統計失敗:', error) } } const createTeamInDb = async (teamData: any) => { try { const response = await fetch('/api/admin/teams', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(teamData) }) const data = await response.json() if (data.success) { setSuccess('團隊創建成功!') await fetchTeams() // 重新獲取列表 await fetchTeamStats() // 重新獲取統計 return data.data } else { setError('創建團隊失敗: ' + data.message) return null } } catch (error) { console.error('創建團隊失敗:', error) setError('創建團隊失敗') return null } } const updateTeamInDb = async (teamId: string, updates: any) => { try { const response = await fetch(`/api/admin/teams/${teamId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(updates) }) const data = await response.json() if (data.success) { setSuccess('團隊更新成功!') await fetchTeams() // 重新獲取列表 await fetchTeamStats() // 重新獲取統計 return true // 返回 true 表示成功 } else { setError('更新團隊失敗: ' + data.message) return false } } catch (error) { console.error('更新團隊失敗:', error) setError('更新團隊失敗') return false } } const deleteTeamInDb = async (teamId: string, hard: boolean = false, skipRefresh: boolean = false) => { try { const response = await fetch(`/api/admin/teams/${teamId}?hard=${hard}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { setSuccess(data.message || '團隊刪除成功!') if (!skipRefresh) { await fetchTeams() // 重新獲取列表 await fetchTeamStats() // 重新獲取統計 } return true } else { setError('刪除團隊失敗: ' + data.message) return false } } catch (error) { console.error('刪除團隊失敗:', error) setError('刪除團隊失敗') return false } } // 獲取可用應用列表 const fetchAvailableApps = async (teamId?: string) => { console.log('🔍 開始獲取可用應用列表, teamId:', teamId) setIsLoadingApps(true) try { const url = teamId ? `/api/admin/apps/available?teamId=${teamId}` : '/api/admin/apps/available' console.log('📡 請求 URL:', url) const response = await fetch(url) const data = await response.json() console.log('📊 應用 API 響應:', data) if (data.success) { console.log('✅ 應用數據設置成功:', data.data.length, '個應用') setAvailableApps(data.data) return data.data // 返回數據 } else { console.error('❌ 獲取應用列表失敗:', data.message) setError('獲取可用應用列表失敗: ' + data.message) return [] } } catch (error) { console.error('❌ 獲取可用應用列表失敗:', error) setError('獲取可用應用列表失敗') return [] } finally { setIsLoadingApps(false) } } // 獲取可用用戶列表 const fetchAvailableUsers = async () => { console.log('🔍 開始獲取可用用戶列表') setIsLoadingUsers(true) try { const response = await fetch('/api/admin/users/available') const data = await response.json() console.log('📊 用戶 API 響應:', data) if (data.success) { console.log('✅ 用戶數據設置成功:', data.data.length, '個用戶') setAvailableUsers(data.data) return data.data // 返回數據 } else { console.error('❌ 獲取用戶列表失敗:', data.message) setError('獲取可用用戶列表失敗: ' + data.message) return [] } } catch (error) { console.error('❌ 獲取可用用戶列表失敗:', error) setError('獲取可用用戶列表失敗') return [] } finally { setIsLoadingUsers(false) } } // 獲取完整團隊信息 const fetchTeamDetails = async (teamId: string) => { try { const response = await fetch(`/api/admin/teams/${teamId}`) const data = await response.json() if (data.success) { return data.data } else { setError('獲取團隊詳情失敗: ' + data.message) return null } } catch (error) { console.error('獲取團隊詳情失敗:', error) setError('獲取團隊詳情失敗') return null } } // 編輯團隊 const handleEditTeam = async (team: any) => { try { console.log('🔍 開始編輯團隊:', team.id) // 先加載用戶和應用數據 const users = await fetchAvailableUsers() const apps = await fetchAvailableApps(team.id) console.log('📊 實際載入的用戶數據:', users.length) console.log('📊 實際載入的應用數據:', apps.length) // 獲取完整的團隊信息 const teamDetails = await fetchTeamDetails(team.id) console.log('📋 團隊詳情:', teamDetails) if (teamDetails) { // 填充表單數據 const teamData = { name: teamDetails.name, leader: teamDetails.leader_name || '', leader_id: teamDetails.leader_id || '', department: teamDetails.department, contactEmail: teamDetails.contact_email || '', leaderPhone: teamDetails.leader_phone || '', description: teamDetails.description || '', members: (teamDetails.members || []).map((member: any) => ({ id: member.user_id || member.id, user_id: member.user_id || member.id, name: member.name, department: member.department, role: member.role || '成員' })), apps: teamDetails.apps ? teamDetails.apps.map((app: any) => app.id || app) : [], submittedAppCount: teamDetails.apps?.length || 0, } console.log('📝 填充的表單數據:', teamData) setNewTeam(teamData) setSelectedTeam(teamDetails) setShowCreateTeam(true) } } catch (error) { console.error('編輯團隊失敗:', error) setError('編輯團隊失敗') } } // 刪除團隊 const handleDeleteTeam = async (team: any) => { setTeamToDelete(team) setShowDeleteTeamConfirm(true) } // 確認刪除團隊 const confirmDeleteTeam = async () => { if (!teamToDelete) return setIsLoading(true) try { const success = await deleteTeamInDb(teamToDelete.id) if (success) { setSuccess("團隊刪除成功!") setShowDeleteTeamConfirm(false) setTeamToDelete(null) // 重新獲取團隊列表 await fetchTeams() await fetchTeamStats() } } catch (error) { console.error('刪除團隊失敗:', error) setError('刪除團隊失敗') } finally { setIsLoading(false) } } const createJudgeInDb = async (judgeData: any) => { try { const response = await fetch('/api/admin/judges', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(judgeData) }) const data = await response.json() if (data.success) { setSuccess('評審創建成功!') await fetchJudges() // 重新獲取列表 await fetchJudgeStats() // 重新獲取統計 return data.data } else { setError('創建評審失敗: ' + data.message) return null } } catch (error) { console.error('創建評審失敗:', error) setError('創建評審失敗') return null } } const updateJudgeInDb = async (id: string, updates: any) => { try { const response = await fetch(`/api/admin/judges/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(updates) }) const data = await response.json() if (data.success) { setSuccess('評審更新成功!') await fetchJudges() // 重新獲取列表 await fetchJudgeStats() // 重新獲取統計 return true // 返回 true 表示成功 } else { setError('更新評審失敗: ' + data.message) return false } } catch (error) { console.error('更新評審失敗:', error) setError('更新評審失敗') return false } } const deleteJudgeInDb = async (id: string, hardDelete: boolean = false, skipRefresh: boolean = false) => { try { const url = hardDelete ? `/api/admin/judges/${id}?hard=true` : `/api/admin/judges/${id}` const response = await fetch(url, { method: 'DELETE' }) const data = await response.json() if (data.success) { setSuccess(data.message || '評審刪除成功!') if (!skipRefresh) { await fetchJudges() // 重新獲取列表 await fetchJudgeStats() // 重新獲取統計 } return true } else { setError('刪除評審失敗: ' + data.message) return false } } catch (error) { console.error('刪除評審失敗:', error) setError('刪除評審失敗') return false } } const [awardMonthFilter, setAwardMonthFilter] = useState("all") const [awardTypeFilter, setAwardTypeFilter] = useState("all") const [awardCompetitionTypeFilter, setAwardCompetitionTypeFilter] = useState("all") // 組件載入時獲取資料 useEffect(() => { fetchCompetitions() fetchCurrentCompetition() fetchCompetitionStats() fetchJudges() fetchJudgeStats() fetchTeams() fetchTeamStats() fetchAvailableApps() }, []) // 當競賽列表載入完成後,載入每個競賽的評分進度 useEffect(() => { if (competitions.length > 0) { competitions.forEach(competition => { loadScoringProgress(competition.id); }); } }, [competitions]) // 当筛选条件改变时重置分页 const resetAwardPagination = () => { setAwardCurrentPage(1) } // Manual scoring states const [manualScoring, setManualScoring] = useState({ judgeId: "", participantId: "", participantType: "individual" as "individual" | "team", scores: {} as Record, comments: "", }) // 混合賽的參賽者類型選擇 const [selectedParticipantType, setSelectedParticipantType] = useState<"individual" | "team">("individual") // Form states - Updated for mixed competition support const [newCompetition, setNewCompetition] = useState({ name: "", type: "individual" as "individual" | "team" | "mixed", year: new Date().getFullYear(), month: new Date().getMonth() + 1, startDate: "", endDate: "", description: "", status: "upcoming" as const, // For individual and team competitions judges: [] as string[], participatingApps: [] as string[], participatingTeams: [] as string[], evaluationFocus: "", rules: [] as CompetitionRule[], awardTypes: [] as CompetitionAwardType[], // For mixed competitions - separate configurations individualConfig: { judges: [] as string[], rules: [] as CompetitionRule[], awardTypes: [] as CompetitionAwardType[], evaluationFocus: "", }, teamConfig: { judges: [] as string[], rules: [] as CompetitionRule[], awardTypes: [] as CompetitionAwardType[], evaluationFocus: "", }, }) const [newJudge, setNewJudge] = useState({ name: "", title: "", department: "", expertise: "", }) const [newAward, setNewAward] = useState({ competitionId: "", participantId: "", participantType: "individual" as "individual" | "team", awardType: "custom" as const, awardName: "", customAwardTypeId: "", description: "", score: 0, category: "innovation" as const, rank: 0, applicationLinks: { production: "", demo: "", github: "", }, documents: [] as { id: string; name: string; type: string; size: string; uploadDate: string; url: string }[], judgeComments: "", photos: [] as { id: string; name: string; url: string; caption: string; uploadDate: string; size: string }[], }) // 動態獎項類型狀態 const [competitionAwardTypes, setCompetitionAwardTypes] = useState([]) const [loadingAwardTypes, setLoadingAwardTypes] = useState(false) // 動態參賽者狀態 const [competitionApps, setCompetitionApps] = useState([]) const [competitionTeams, setCompetitionTeams] = useState([]) const [competitionJudges, setCompetitionJudges] = useState([]) const [loadingParticipants, setLoadingParticipants] = useState(false) // 文件上傳狀態 const [uploadingFiles, setUploadingFiles] = useState(false) const [uploadProgress, setUploadProgress] = useState<{[key: string]: number}>({}) // 照片上傳狀態 const [uploadingPhotos, setUploadingPhotos] = useState(false) const [photoUploadProgress, setPhotoUploadProgress] = useState<{[key: string]: number}>({}) // 日期格式化輔助函式 const formatDate = (dateString: string | Date): string => { if (!dateString) return '' const date = new Date(dateString) if (isNaN(date.getTime())) return '' const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}/${month}/${day}` } // 文件上傳處理函數 const handleFileUpload = async (files: FileList) => { const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'] const maxSize = 10 * 1024 * 1024 // 10MB setUploadingFiles(true) const newDocuments = [...newAward.documents] for (let i = 0; i < files.length; i++) { const file = files[i] // 驗證文件類型 if (!allowedTypes.includes(file.type)) { alert(`文件 ${file.name} 格式不支援,請上傳 PDF、DOC、DOCX、PPTX 格式的文件`) continue } // 驗證文件大小 if (file.size > maxSize) { alert(`文件 ${file.name} 超過 10MB 限制`) continue } const fileId = `doc_${Date.now()}_${i}` setUploadProgress(prev => ({ ...prev, [fileId]: 0 })) try { // 創建 FormData const formData = new FormData() formData.append('file', file) formData.append('awardId', newAward.id || 'temp') // 上傳文件 const response = await fetch('/api/upload/document', { method: 'POST', body: formData, }) if (!response.ok) { throw new Error('文件上傳失敗') } const result = await response.json() // 添加文件到列表 const getFileType = (mimeType: string) => { const typeMap: {[key: string]: string} = { 'application/pdf': 'PDF', 'application/msword': 'DOC', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX', 'application/vnd.ms-powerpoint': 'PPT', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PPTX' } return typeMap[mimeType] || file.type.split('/')[1].toUpperCase() } const newDoc = { id: fileId, name: file.name, type: getFileType(file.type), size: formatFileSize(file.size), uploadDate: new Date().toLocaleDateString('zh-TW'), url: result.url, originalName: file.name } newDocuments.push(newDoc) setUploadProgress(prev => ({ ...prev, [fileId]: 100 })) } catch (error) { console.error('文件上傳錯誤:', error) alert(`文件 ${file.name} 上傳失敗`) setUploadProgress(prev => ({ ...prev, [fileId]: 0 })) } } setNewAward({ ...newAward, documents: newDocuments }) setUploadingFiles(false) setUploadProgress({}) } // 格式化文件大小 const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes' const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] } // 刪除文件 const handleRemoveDocument = (docId: string) => { setNewAward({ ...newAward, documents: newAward.documents.filter(doc => doc.id !== docId) }) } // 照片上傳處理函數 const handlePhotoUpload = async (files: FileList) => { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'] const maxSize = 5 * 1024 * 1024 // 5MB setUploadingPhotos(true) const newPhotos = [...newAward.photos] for (let i = 0; i < files.length; i++) { const file = files[i] // 驗證文件類型 if (!allowedTypes.includes(file.type)) { alert(`照片 ${file.name} 格式不支援,請上傳 JPG、PNG、GIF、WEBP 格式的照片`) continue } // 驗證文件大小 if (file.size > maxSize) { alert(`照片 ${file.name} 超過 5MB 限制`) continue } const photoId = `photo_${Date.now()}_${i}` setPhotoUploadProgress(prev => ({ ...prev, [photoId]: 0 })) try { // 創建 FormData const formData = new FormData() formData.append('file', file) formData.append('awardId', newAward.id || 'temp') // 上傳照片 const response = await fetch('/api/upload/photo', { method: 'POST', body: formData, }) if (!response.ok) { throw new Error('照片上傳失敗') } const result = await response.json() // 添加照片到列表 const newPhoto = { id: photoId, name: file.name, url: result.url, size: formatFileSize(file.size), uploadDate: new Date().toLocaleDateString('zh-TW'), caption: '', type: file.type.split('/')[1].toUpperCase(), originalName: file.name } newPhotos.push(newPhoto) setPhotoUploadProgress(prev => ({ ...prev, [photoId]: 100 })) } catch (error) { console.error('照片上傳錯誤:', error) alert(`照片 ${file.name} 上傳失敗`) setPhotoUploadProgress(prev => ({ ...prev, [photoId]: 0 })) } } setNewAward({ ...newAward, photos: newPhotos }) setUploadingPhotos(false) setPhotoUploadProgress({}) } // 刪除照片 const handleRemovePhoto = (photoId: string) => { setNewAward({ ...newAward, photos: newAward.photos.filter(photo => photo.id !== photoId) }) } // 載入競賽獎項類型 const loadCompetitionAwardTypes = async (competitionId: string) => { if (!competitionId) { setCompetitionAwardTypes([]) return } setLoadingAwardTypes(true) try { const response = await fetch(`/api/admin/competitions/${competitionId}/award-types`) const data = await response.json() if (data.success) { setCompetitionAwardTypes(data.data) // 如果有獎項類型,預設選擇第一個 if (data.data.length > 0) { setNewAward(prev => ({ ...prev, awardType: 'custom', customAwardTypeId: data.data[0].id })) } } else { console.error('載入獎項類型失敗:', data.message) setCompetitionAwardTypes([]) } } catch (error) { console.error('載入獎項類型失敗:', error) setCompetitionAwardTypes([]) } finally { setLoadingAwardTypes(false) } } // 載入競賽參賽者和評審 const loadCompetitionParticipants = async (competitionId: string) => { if (!competitionId) { setCompetitionApps([]) setCompetitionTeams([]) setCompetitionJudges([]) return } setLoadingParticipants(true) try { // 並行載入參賽應用、參賽團隊和評審 const [appsResponse, teamsResponse, judgesResponse] = await Promise.all([ fetch(`/api/admin/competitions/${competitionId}/apps`), fetch(`/api/admin/competitions/${competitionId}/teams`), fetch(`/api/admin/competitions/${competitionId}/judges`) ]) const [appsData, teamsData, judgesData] = await Promise.all([ appsResponse.json(), teamsResponse.json(), judgesResponse.json() ]) if (appsData.success) { setCompetitionApps(appsData.data) } else { console.error('載入參賽應用失敗:', appsData.message) setCompetitionApps([]) } if (teamsData.success) { console.log('✅ 載入參賽團隊成功:', teamsData.data) setCompetitionTeams(teamsData.data) } else { console.error('❌ 載入參賽團隊失敗:', teamsData.message) setCompetitionTeams([]) } if (judgesData.success) { setCompetitionJudges(judgesData.data) } else { console.error('載入評審失敗:', judgesData.message) setCompetitionJudges([]) } } catch (error) { console.error('載入參賽者失敗:', error) setCompetitionApps([]) setCompetitionTeams([]) setCompetitionJudges([]) } finally { setLoadingParticipants(false) } } // Team form states const [newTeam, setNewTeam] = useState({ name: "", leader: "", leader_id: "", // 添加隊長 ID department: "HQBU", contactEmail: "", leaderPhone: "", description: "", members: [] as Array<{ id: string; name: string; department: string; role: string }>, apps: [] as string[], // 改為存儲應用 ID submittedAppCount: 0, }) const [newMember, setNewMember] = useState({ name: "", user_id: "", // 添加用戶 ID department: "HQBU", role: "成員", }) const [newApp, setNewApp] = useState({ name: "", link: "", }) const [createError, setCreateError] = useState("") // Participant selection states - separate for mixed competitions const [participantSearchTerm, setParticipantSearchTerm] = useState("") const [departmentFilter, setDepartmentFilter] = useState("all") const [individualParticipantSearchTerm, setIndividualParticipantSearchTerm] = useState("") const [teamParticipantSearchTerm, setTeamParticipantSearchTerm] = useState("") const [individualDepartmentFilter, setIndividualDepartmentFilter] = useState("all") const [teamDepartmentFilter, setTeamDepartmentFilter] = useState("all") // Get participants based on competition type const [selectedJudge, setSelectedJudge] = useState(null) const [showJudgeDetail, setShowJudgeDetail] = useState(false) const [showDeleteJudgeConfirm, setShowDeleteJudgeConfirm] = useState(false) const [showDisableJudgeConfirm, setShowDisableJudgeConfirm] = useState(false) // 獎項相關狀態 const [showAwardDetail, setShowAwardDetail] = useState(false) const [selectedAward, setSelectedAward] = useState(null) const [showDeleteAwardConfirm, setShowDeleteAwardConfirm] = useState(false) const [awardToDelete, setAwardToDelete] = useState(null) // 評審分頁和篩選狀態 const [judgeCurrentPage, setJudgeCurrentPage] = useState(1) const [judgeSearchTerm, setJudgeSearchTerm] = useState("") const [judgeDepartmentFilter, setJudgeDepartmentFilter] = useState("all") const [judgeExpertiseFilter, setJudgeExpertiseFilter] = useState("all") const [judgeStatusFilter, setJudgeStatusFilter] = useState("all") const judgesPerPage = 6 // 團隊分頁和篩選狀態 const [teamCurrentPage, setTeamCurrentPage] = useState(1) const [teamSearchTerm, setTeamSearchTerm] = useState("") const teamsPerPage = 6 // 獎項分頁狀態 const [awardCurrentPage, setAwardCurrentPage] = useState(1) const awardsPerPage = 6 // Get participants based on competition type const getParticipants = (competitionType: string) => { switch (competitionType) { case "individual": return availableApps case "team": // 總是使用 dbTeams,如果為空則返回空數組 return dbTeams default: return [] } } // Filter participants - updated for mixed competitions const getFilteredParticipants = (competitionType: string) => { const participants = getParticipants(competitionType) let searchTerm = participantSearchTerm let departmentFilterValue = departmentFilter // Use separate search terms for mixed competitions if (newCompetition.type === "mixed") { searchTerm = competitionType === "individual" ? individualParticipantSearchTerm : teamParticipantSearchTerm departmentFilterValue = competitionType === "individual" ? individualDepartmentFilter : teamDepartmentFilter } const filtered = participants.filter((participant) => { const searchField = competitionType === "team" ? participant.name : participant.name const creatorField = competitionType === "team" ? (participant.leader_name || participant.leader) : (participant.creator_name || participant.creator_id || participant.creator) const matchesSearch = searchField.toLowerCase().includes(searchTerm.toLowerCase()) || creatorField.toLowerCase().includes(searchTerm.toLowerCase()) const matchesDepartment = departmentFilterValue === "all" || participant.department === departmentFilterValue return matchesSearch && matchesDepartment }) console.log('🔍 getFilteredParticipants 結果:', { competitionType, filteredLength: filtered.length, filtered: filtered.slice(0, 2).map(f => ({ id: f.id, name: f.name, leader_name: f.leader_name, leader: f.leader, member_count: f.member_count, submissionDate: f.submissionDate })) }) return filtered } const resetForm = () => { setNewCompetition({ name: "", type: "individual", year: new Date().getFullYear(), month: new Date().getMonth() + 1, startDate: "", endDate: "", description: "", status: "upcoming", judges: [], participatingApps: [], participatingTeams: [], evaluationFocus: "", rules: [], awardTypes: [], individualConfig: { judges: [], rules: [], awardTypes: [], evaluationFocus: "", }, teamConfig: { judges: [], rules: [], awardTypes: [], evaluationFocus: "", }, }) // Reset search terms setParticipantSearchTerm("") setDepartmentFilter("all") setIndividualParticipantSearchTerm("") setTeamParticipantSearchTerm("") setIndividualDepartmentFilter("all") setTeamDepartmentFilter("all") } const resetTeamForm = () => { setNewTeam({ name: "", leader: "", department: "HQBU", contactEmail: "", leaderPhone: "", description: "", members: [], apps: [], submittedAppCount: 0, }) setNewMember({ name: "", department: "HQBU", role: "成員", }) setNewApp({ name: "", link: "", }) } const handleCreateCompetition = async () => { setCreateError("") if (!newCompetition.name || !newCompetition.startDate || !newCompetition.endDate) { setCreateError("請填寫所有必填欄位") return } // Validation for mixed competitions if (newCompetition.type === "mixed") { if (newCompetition.individualConfig.judges.length === 0 && newCompetition.teamConfig.judges.length === 0) { setCreateError("混合賽至少需要為個人賽或團體賽選擇評審") return } // Check if at least one competition type has participants const hasParticipants = newCompetition.participatingApps.length > 0 || newCompetition.participatingTeams.length > 0 if (!hasParticipants) { setCreateError("請至少選擇一個個人賽應用或團隊賽團隊") return } // Validate individual rules if there are individual participants and judges if (newCompetition.participatingApps.length > 0 && newCompetition.individualConfig.judges.length > 0) { if (newCompetition.individualConfig.rules.length > 0) { const individualTotalWeight = calculateTotalWeight(newCompetition.individualConfig.rules) if (Math.abs(individualTotalWeight - 100) > 0.01) { setCreateError("個人賽評比標準權重總和必須為 100%") return } } } // Validate team rules if there are team participants and judges if (newCompetition.participatingTeams.length > 0 && newCompetition.teamConfig.judges.length > 0) { if (newCompetition.teamConfig.rules.length > 0) { const teamTotalWeight = calculateTotalWeight(newCompetition.teamConfig.rules) if (Math.abs(teamTotalWeight - 100) > 0.01) { setCreateError("團體賽評比標準權重總和必須為 100%") return } } } } else { // Validation for single type competitions if (newCompetition.judges.length === 0) { setCreateError("請至少選擇一位評審") return } const hasParticipants = (newCompetition.type === "individual" && newCompetition.participatingApps.length > 0) || (newCompetition.type === "team" && newCompetition.participatingTeams.length > 0) if (!hasParticipants) { setCreateError("請至少選擇一個參賽項目") return } if (newCompetition.rules.length > 0) { const totalWeight = calculateTotalWeight(newCompetition.rules) if (Math.abs(totalWeight - 100) > 0.01) { setCreateError("評比標準權重總和必須為 100%") return } const hasEmptyRule = newCompetition.rules.some((rule) => !rule.name.trim() || !rule.description.trim()) if (hasEmptyRule) { setCreateError("請填寫所有評比標準的名稱和描述") return } } if (newCompetition.awardTypes.length > 0) { const hasEmptyAwardType = newCompetition.awardTypes.some( (awardType) => !awardType.name.trim() || !awardType.description.trim(), ) if (hasEmptyAwardType) { setCreateError("請填寫所有獎項類型的名稱和描述") return } } } setIsLoading(true) try { if (selectedCompetitionForAction) { // 編輯模式 - 更新現有競賽 const updates = { name: newCompetition.name, year: newCompetition.year, month: newCompetition.month, startDate: newCompetition.startDate, endDate: newCompetition.endDate, status: newCompetition.status, description: newCompetition.description, type: newCompetition.type, evaluationFocus: newCompetition.evaluationFocus, maxTeamSize: newCompetition.maxTeamSize || null, // 關聯數據 judges: newCompetition.judges || [], teams: newCompetition.participatingTeams || [], awardTypes: newCompetition.awardTypes || [], rules: newCompetition.rules || [] } const updatedCompetition = await updateCompetitionInDb(selectedCompetitionForAction.id, updates) if (updatedCompetition) { // 同時更新 context updateCompetition(selectedCompetitionForAction.id, newCompetition) } } else { // 創建模式 - 新增競賽到資料庫 const competitionData = { name: newCompetition.name, year: newCompetition.year, month: newCompetition.month, startDate: newCompetition.startDate, endDate: newCompetition.endDate, status: newCompetition.status, description: newCompetition.description, type: newCompetition.type, evaluationFocus: newCompetition.evaluationFocus, maxTeamSize: newCompetition.maxTeamSize || null, isActive: true, // 關聯數據 judges: newCompetition.judges || [], teams: newCompetition.participatingTeams || [], awardTypes: newCompetition.awardTypes || [], rules: newCompetition.rules || [] } const createdCompetition = await createCompetitionInDb(competitionData) if (createdCompetition) { // 同時添加到 context(保持向後兼容) const competitionWithId = { ...newCompetition, id: createdCompetition.id, createdAt: createdCompetition.created_at, } addCompetition(competitionWithId) } } } catch (error) { console.error("處理競賽失敗:", error) setCreateError("處理競賽時發生錯誤") } setShowCreateCompetition(false) setSelectedCompetitionForAction(null) setCreateError("") resetForm() setIsLoading(false) setTimeout(() => setSuccess(""), 3000) } const handleCreateTeam = async () => { setCreateError("") if (!newTeam.name || !newTeam.leader || !newTeam.contactEmail) { setCreateError("請填寫團隊名稱、隊長和聯絡信箱") return } if (newTeam.members.length === 0) { setCreateError("請至少添加一名團隊成員") return } // Check if leader is in members list const leaderInMembers = newTeam.members.some((member) => member.name === newTeam.leader) if (!leaderInMembers) { setCreateError("隊長必須在團隊成員列表中") return } setIsLoading(true) try { if (selectedTeam) { // 編輯模式 - 更新現有團隊 // 使用選擇的隊長 ID,如果沒有選擇則保持原有的隊長 ID const leaderId = newTeam.leader_id || (selectedTeam as any).leader_id || '0b844fb6-1a63-4e0c-a15a-416e9b0ec8c7' // 根據當前標籤頁決定發送哪些字段 const teamData: any = { name: newTeam.name, leader_id: leaderId, department: newTeam.department, contact_email: newTeam.contactEmail, description: newTeam.description } // 只有在成員標籤頁或應用標籤頁時才發送對應的數據 // 這裡我們總是發送成員和應用數據,因為用戶可能在任何標籤頁點擊更新 teamData.members = newTeam.members.map(member => ({ user_id: member.user_id || member.id, // 確保使用正確的 user_id role: member.role || 'member' })) teamData.apps = newTeam.apps // 添加應用 ID 列表 const success = await updateTeamInDb(selectedTeam.id, teamData) if (success) { setSuccess("團隊更新成功!") } } else { // 創建模式 - 新增團隊 // 使用選擇的隊長 ID,如果沒有選擇則使用預設的用戶 ID const leaderId = newTeam.leader_id || '0b844fb6-1a63-4e0c-a15a-416e9b0ec8c7' const teamData = { name: newTeam.name, leader_id: leaderId, department: newTeam.department, contact_email: newTeam.contactEmail, description: newTeam.description, members: newTeam.members.map(member => ({ user_id: member.user_id || member.id, // 確保使用正確的 user_id role: member.role || 'member' })), apps: newTeam.apps // 添加應用 ID 列表 } const createdTeam = await createTeamInDb(teamData) if (createdTeam) { setSuccess("團隊創建成功!") } } } catch (error) { console.error("處理團隊失敗:", error) setCreateError("處理團隊時發生錯誤") } setShowCreateTeam(false) setSelectedTeam(null) resetTeamForm() setIsLoading(false) setTimeout(() => setSuccess(""), 3000) } const handleConfirmDeleteTeam = async () => { if (!teamToDelete) return setIsLoading(true) try { const success = await deleteTeamInDb(teamToDelete.id, true) // 硬刪除 if (success) { setShowDeleteTeamConfirm(false) setTeamToDelete(null) setSuccess("團隊刪除成功!") } else { setError("刪除團隊失敗") } } catch (error) { console.error('刪除團隊失敗:', error) setError('刪除團隊失敗') } finally { setIsLoading(false) } } const handleAddMember = () => { if (!newMember.user_id || !newMember.name.trim()) { setCreateError("請選擇成員") return } // 檢查是否已經添加過這個成員 if (newTeam.members.some(member => member.id === newMember.user_id)) { setCreateError("該成員已經在團隊中") return } const member = { id: newMember.user_id, // 使用用戶 ID user_id: newMember.user_id, // 確保 user_id 字段存在 name: newMember.name, department: newMember.department, role: newMember.role, } setNewTeam({ ...newTeam, members: [...newTeam.members, member], }) setNewMember({ name: "", user_id: "", department: "HQBU", role: "成員", }) setCreateError("") } const handleRemoveMember = (memberId: string) => { setNewTeam({ ...newTeam, members: newTeam.members.filter((m) => m.id !== memberId), }) } const handleAddApp = (appId: string) => { if (newTeam.apps.includes(appId)) { setCreateError("此應用已經加入團隊") return } setNewTeam({ ...newTeam, apps: [...newTeam.apps, appId], }) setCreateError("") } const handleRemoveApp = (appId: string) => { setNewTeam({ ...newTeam, apps: newTeam.apps.filter(id => id !== appId), }) } const handleAddJudge = async () => { setError("") if (!newJudge.name || !newJudge.title || !newJudge.department) { setError("請填寫所有必填欄位") return } setIsLoading(true) try { const expertiseArray = newJudge.expertise .split(",") .map((s) => s.trim()) .filter(Boolean) if (selectedJudge) { // 編輯模式 - 更新現有評審 const updates = { name: newJudge.name, title: newJudge.title, department: newJudge.department, expertise: expertiseArray, avatar: newJudge.avatar || null, isActive: true } const updatedJudge = await updateJudgeInDb(selectedJudge.id, updates) if (updatedJudge) { // 同時更新 context updateJudge(selectedJudge.id, { ...newJudge, expertise: expertiseArray, }) } } else { // 新增模式 - 新增評審到資料庫 const judgeData = { name: newJudge.name, title: newJudge.title, department: newJudge.department, expertise: expertiseArray, avatar: newJudge.avatar || null, isActive: true } const createdJudge = await createJudgeInDb(judgeData) if (createdJudge) { // 同時添加到 context(保持向後兼容) addJudge({ ...newJudge, expertise: expertiseArray, }) // 重新獲取評審列表和統計 await fetchJudges() await fetchJudgeStats() } } } catch (error) { console.error("處理評審失敗:", error) setError("處理評審時發生錯誤") } setShowAddJudge(false) setSelectedJudge(null) setNewJudge({ name: "", title: "", department: "", expertise: "", }) setIsLoading(false) setTimeout(() => setSuccess(""), 3000) } const handleEditJudge = (judge: any) => { setSelectedJudge(judge) setNewJudge({ name: judge.name, title: judge.title, department: judge.department, expertise: judge.expertise.join(", "), }) setShowAddJudge(true) // 使用新增評審對話框 } const handleDisableJudge = (judge: any) => { setSelectedJudge(judge) setShowDisableJudgeConfirm(true) } const handleEnableJudge = async (judge: any) => { setIsLoading(true) try { const success = await updateJudgeInDb(judge.id, { is_active: true }) if (success) { setSuccess('評審已啟用!') // 不需要再次調用 fetchJudges 和 fetchJudgeStats,因為 updateJudgeInDb 已經調用了 } else { setError('啟用評審失敗') } } catch (error) { console.error('啟用評審失敗:', error) setError('啟用評審失敗') } finally { setIsLoading(false) } } const handleDeleteJudge = (judge: any) => { setSelectedJudge(judge) setShowDeleteJudgeConfirm(true) } const confirmDisableJudge = async () => { if (!selectedJudge) return setIsLoading(true) try { const success = await updateJudgeInDb(selectedJudge.id, { is_active: false }) if (success) { setShowDisableJudgeConfirm(false) setSelectedJudge(null) setSuccess('評審已停用!') // 不需要再次調用 fetchJudges 和 fetchJudgeStats,因為 updateJudgeInDb 已經調用了 } else { setError('停用評審失敗') } } catch (error) { console.error('停用評審失敗:', error) setError('停用評審失敗') } finally { setIsLoading(false) } } const confirmDeleteJudge = async () => { if (!selectedJudge) return const judgeId = selectedJudge.id // 保存 ID,避免後續變為 null const judgeName = selectedJudge.name // 保存名稱,避免後續變為 null setIsLoading(true) try { const success = await deleteJudgeInDb(judgeId, true, true) // 直接硬刪除,跳過內部刷新 if (success) { setShowDeleteJudgeConfirm(false) setSelectedJudge(null) // 立即從 context 中移除(如果存在) console.log('🗑️ 調用 deleteJudge 移除評審:', judgeId) console.log('🗑️ 評審名稱:', judgeName) console.log('🗑️ 當前 context 中的評審:', judges.map(j => ({ id: j.id, name: j.name }))) // 根據名稱匹配 context 中的評審,因為 ID 可能不匹配 const contextJudge = judges.find(j => j.name === judgeName) if (contextJudge) { console.log('🗑️ 找到匹配的 context 評審:', contextJudge) deleteJudge(contextJudge.id) } else { console.log('🗑️ 沒有找到匹配的 context 評審,嘗試使用原始 ID') deleteJudge(judgeId) } // 強制從 dbJudges 中移除,確保立即更新 setDbJudges(prev => { console.log('🗑️ 從 dbJudges 中移除評審:', judgeId) console.log('🗑️ 移除前的評審列表:', prev.map(j => ({ id: j.id, name: j.name, is_active: j.is_active }))) const filtered = prev.filter(judge => judge.id !== judgeId) console.log('🗑️ 移除後的評審列表:', filtered.map(j => ({ id: j.id, name: j.name, is_active: j.is_active }))) return filtered }) // 等待一個微任務,確保 context 狀態更新完成 await new Promise(resolve => setTimeout(resolve, 0)) // 確保列表和統計數據立即更新 console.log('🔄 調用 fetchJudges 重新獲取數據') await fetchJudges() await fetchJudgeStats() // 再次檢查 context 狀態 console.log('🔍 刪除後檢查 context 狀態') } } catch (error) { console.error('刪除評審失敗:', error) setError('刪除評審失敗') } finally { setIsLoading(false) } } const handleCreateAward = async () => { setError("") if (!newAward.competitionId || !newAward.participantId || !newAward.awardName) { setError("請填寫所有必填欄位") return } setIsLoading(true) try { // 獲取競賽信息 const competition = competitions.find((c) => c.id === newAward.competitionId) if (!competition) { setError("找不到選定的競賽") return } // 根據競賽類型自動設置 participantType let actualParticipantType = newAward.participantType if (competition.type === "team") { actualParticipantType = "team" } else if (competition.type === "individual") { actualParticipantType = "individual" } // 如果是 mixed 類型,保持用戶選擇的 participantType // 獲取參賽者信息 let participant: any = null let participantName = "" let creatorName = "" console.log('🔍 查找參賽者調試信息:') console.log('competition.type:', competition.type) console.log('original participantType:', newAward.participantType) console.log('actual participantType:', actualParticipantType) console.log('participantId:', newAward.participantId) console.log('competitionApps:', competitionApps) console.log('competitionTeams:', competitionTeams) if (actualParticipantType === "individual") { participant = competitionApps.find((app) => app.id === newAward.participantId) participantName = participant?.name || "" creatorName = participant?.creator_name || participant?.creator || "" console.log('找到個人參賽者:', participant) } else if (actualParticipantType === "team") { participant = competitionTeams.find((team) => team.id === newAward.participantId) participantName = participant?.name || "" creatorName = participant?.leader_name || participant?.leader || "" console.log('找到團隊參賽者:', participant) // 團體賽需要關聯到對應的應用 if (participant && participant.app_id) { console.log('團隊關聯的應用 ID:', participant.app_id) console.log('團隊關聯的應用名稱:', participant.app_name) } } if (!participant) { console.error('❌ 找不到參賽者') console.log('competitionApps 長度:', competitionApps.length) console.log('competitionTeams 長度:', competitionTeams.length) setError(`找不到選定的參賽者 (${newAward.participantType}: ${newAward.participantId})`) return } // 根據獎項類型設定圖標 let icon = "🏆" switch (newAward.awardType) { case "gold": icon = "🥇"; break; case "silver": icon = "🥈"; break; case "bronze": icon = "🥉"; break; case "popular": icon = "👥"; break; case "innovation": icon = "💡"; break; case "technical": icon = "⚙️"; break; default: icon = "🏆"; break; } // 準備獎項數據 const awardData = { competition_id: newAward.competitionId, app_id: actualParticipantType === "individual" ? newAward.participantId : (participant?.app_id || null), team_id: actualParticipantType === "team" ? newAward.participantId : null, app_name: actualParticipantType === "individual" ? participantName : (participant?.app_name || null), team_name: actualParticipantType === "team" ? participantName : null, creator: creatorName, award_type: newAward.awardType, award_name: newAward.awardName, custom_award_type_id: newAward.customAwardTypeId || null, score: newAward.score, rank: newAward.rank, category: newAward.category, description: newAward.description, judge_comments: newAward.judgeComments, application_links: newAward.applicationLinks, documents: newAward.documents, photos: newAward.photos, year: new Date().getFullYear(), month: new Date().getMonth() + 1, icon: icon, competition_type: competition.type, participant_type: actualParticipantType, } console.log('📝 準備獎項數據:', awardData) // 調用 API 創建獎項 const response = await fetch('/api/admin/awards', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(awardData), }) const result = await response.json() if (!response.ok) { throw new Error(result.message || '創建獎項失敗') } // 創建成功,添加到本地列表 const newAwardItem = { id: result.data.id, competitionId: newAward.competitionId, competitionName: competition.name, participantId: newAward.participantId, participantType: newAward.participantType, participantName: participantName, creatorName: creatorName, awardType: newAward.awardType, awardName: newAward.awardName, customAwardTypeId: newAward.customAwardTypeId, score: newAward.score, rank: newAward.rank, category: newAward.category, description: newAward.description, judgeComments: newAward.judgeComments, applicationLinks: newAward.applicationLinks, documents: newAward.documents, photos: newAward.photos, year: new Date().getFullYear(), month: new Date().getMonth() + 1, icon: icon, createdAt: new Date().toISOString(), } addAward(newAwardItem) // 重置表單 setNewAward({ competitionId: "", participantId: "", participantType: "individual", awardType: "custom", awardName: "", customAwardTypeId: "", description: "", score: 0, category: "innovation", rank: 0, applicationLinks: { production: "", demo: "", github: "", }, documents: [], judgeComments: "", photos: [], }) setShowCreateAward(false) setSuccess("獎項創建成功!") setTimeout(() => setSuccess(""), 3000) } catch (error) { console.error('創建獎項失敗:', error) setError(error instanceof Error ? error.message : '創建獎項失敗') } finally { setIsLoading(false) } } const handleViewAward = async (award: any) => { setSelectedAward(award) setShowAwardDetail(true) // 載入該競賽的評審團信息 if (award.competitionId) { try { console.log('🔍 載入競賽評審團:', award.competitionId); const response = await fetch(`/api/admin/competitions/${award.competitionId}/judges`); const data = await response.json(); if (data.success) { console.log('✅ 獲取到評審團:', data.data.length, '位'); setCompetitionJudges(data.data); } else { console.error('❌ 獲取評審團失敗:', data.message); setCompetitionJudges([]); } } catch (error) { console.error('❌ 載入評審團失敗:', error); setCompetitionJudges([]); } } } const handleEditAward = (award: any) => { setSelectedAward(award) setNewAward({ competitionId: award.competitionId, participantId: award.appId || award.teamId || "", participantType: award.competitionType, awardType: award.awardType, awardName: award.awardName, customAwardTypeId: award.customAwardTypeId || "", description: (award as any).description || "", score: award.score, category: award.category, rank: award.rank, applicationLinks: (award as any).applicationLinks || { production: "", demo: "", github: "", }, documents: (award as any).documents || [], judgeComments: (award as any).judgeComments || "", photos: (award as any).photos || [], }) setShowCreateAward(true) } const handleDeleteAward = (award: any) => { setAwardToDelete(award) setShowDeleteAwardConfirm(true) } const confirmDeleteAward = async () => { if (!awardToDelete) return setIsLoading(true) await new Promise((resolve) => setTimeout(resolve, 500)) // 這裡應該調用 context 中的刪除函數 // deleteAward(awardToDelete.id) setShowDeleteAwardConfirm(false) setAwardToDelete(null) setSuccess("獎項刪除成功!") setIsLoading(false) setTimeout(() => setSuccess(""), 3000) } const handleManualScoring = async (competition: any) => { setSelectedCompetition(competition) // 設定初始參賽者類型 let participantType = "individual"; if (competition.type === "mixed") { setSelectedParticipantType("individual") // 混合賽預設從個人賽開始 participantType = "individual"; } else { setSelectedParticipantType(competition.type) participantType = competition.type; } // 初始化評分項目 const initialScores = getInitialScores(competition, participantType) setManualScoring({ judgeId: "", participantId: "", participantType: participantType, scores: initialScores, comments: "", }) // 載入競賽相關數據 await loadCompetitionDataForScoring(competition) setShowManualScoring(true) } // 載入競賽相關數據用於評分 const loadCompetitionDataForScoring = async (competition: any) => { try { console.log('🔍 開始載入競賽評分數據,競賽ID:', competition.id) // 載入競賽評審 const judgesResponse = await fetch(`/api/competitions/${competition.id}/judges`) const judgesData = await judgesResponse.json() console.log('🔍 競賽評審API回應:', judgesData) console.log('🔍 檢查條件:') console.log(' - judgesData.success:', judgesData.success) console.log(' - judgesData.data:', !!judgesData.data) console.log(' - judgesData.data.judges:', !!judgesData.data?.judges) console.log(' - judgesData.data.judges.length:', judgesData.data?.judges?.length) if (judgesData.success && judgesData.data && judgesData.data.judges) { console.log('✅ 競賽評審載入成功:', judgesData.data.judges.length, '個評審') // 更新評審數據到dbJudges(如果需要的話) setDbJudges(prev => { const existingIds = prev.map(j => j.id) const newJudges = judgesData.data.judges.filter((j: any) => !existingIds.includes(j.id)) return [...prev, ...newJudges] }) } else { console.error('❌ 競賽評審載入失敗:', judgesData.message) console.error('❌ 詳細錯誤信息:', judgesData.error) } // 載入競賽參賽者(應用和團隊) const [appsResponse, teamsResponse] = await Promise.all([ fetch(`/api/competitions/${competition.id}/apps`), fetch(`/api/competitions/${competition.id}/teams`) ]) const appsData = await appsResponse.json() const teamsData = await teamsResponse.json() console.log('應用API回應:', appsData) console.log('團隊API回應:', teamsData) // 更新selectedCompetition以包含載入的數據 const updatedCompetition = { ...competition } // 添加評審數據到selectedCompetition if (judgesData.success && judgesData.data && judgesData.data.judges) { console.log('✅ 競賽評審載入成功,添加到selectedCompetition:', judgesData.data.judges.length, '個評審') updatedCompetition.judges = judgesData.data.judges } else { console.error('❌ 競賽評審載入失敗,設置空數組') updatedCompetition.judges = [] } if (appsData.success && appsData.data && appsData.data.apps) { console.log('✅ 競賽應用載入成功:', appsData.data.apps.length, '個應用') updatedCompetition.apps = appsData.data.apps } else { console.error('❌ 競賽應用載入失敗:', appsData.message) updatedCompetition.apps = [] } if (teamsData.success && teamsData.data && teamsData.data.teams) { console.log('✅ 競賽團隊載入成功:', teamsData.data.teams.length, '個團隊') // 確保團隊數據的唯一性,避免重複的ID const uniqueTeams = teamsData.data.teams.filter((team: any, index: number, self: any[]) => self.findIndex(t => t.id === team.id) === index ) updatedCompetition.teams = uniqueTeams console.log('🔍 去重後的團隊數量:', uniqueTeams.length) // 同時更新dbTeams,確保不重複添加 setDbTeams(prev => { const existingIds = prev.map(t => t.id) const newTeams = uniqueTeams.filter((t: any) => !existingIds.includes(t.id)) return [...prev, ...newTeams] }) } else { console.error('❌ 競賽團隊載入失敗:', teamsData.message) updatedCompetition.teams = [] } // 更新selectedCompetition setSelectedCompetition(updatedCompetition) } catch (error) { console.error('❌ 載入競賽評分數據失敗:', error) } } // 獲取初始評分項目的輔助函數 const getInitialScores = (competition: any, participantType: "individual" | "team") => { const initialScores: Record = {} // 獲取實際的評分項目(與顯示邏輯一致) let currentRules: any[] = []; if (competition?.type === 'mixed') { const config = participantType === 'individual' ? competition.individualConfig : competition.teamConfig; currentRules = config?.rules || []; } else { currentRules = competition?.rules || []; } // 如果有自定義規則,使用自定義規則;否則使用預設規則 const scoringItems = currentRules.length > 0 ? currentRules : getDefaultScoringItems(participantType); scoringItems.forEach(item => { initialScores[item.name] = 0 }) return initialScores } // 計算總分(10分制) const calculateTotalScore = () => { const scores = Object.values(manualScoring.scores); return scores.reduce((total, score) => total + (score || 0), 0); }; // 計算100分制總分 const calculateTotalScore100 = () => { const totalScore = calculateTotalScore(); // 獲取實際的評分項目數量 let currentRules: any[] = []; if (selectedCompetition?.type === 'mixed') { const config = selectedParticipantType === 'individual' ? selectedCompetition.individualConfig : selectedCompetition.teamConfig; currentRules = config?.rules || []; } else { currentRules = selectedCompetition?.rules || []; } // 如果有自定義規則,使用自定義規則的數量;否則使用預設規則 const scoringItems = currentRules.length > 0 ? currentRules : getDefaultScoringItems( selectedCompetition?.type === 'mixed' ? selectedParticipantType : selectedCompetition?.type || 'individual' ); const maxScore = scoringItems.length * 10; return Math.round((totalScore / maxScore) * 100); }; // 計算加權總分(100分制) const calculateWeightedTotalScore100 = () => { let currentRules: any[] = []; if (selectedCompetition?.type === 'mixed') { const config = selectedParticipantType === 'individual' ? selectedCompetition.individualConfig : selectedCompetition.teamConfig; currentRules = config?.rules || []; } else { currentRules = selectedCompetition?.rules || []; } if (currentRules.length === 0) { // 如果沒有自定義規則,使用預設權重(每個項目權重相等) return calculateTotalScore100(); } // 使用自定義權重計算 let weightedTotal = 0; let totalWeight = 0; currentRules.forEach((rule: any) => { const score = manualScoring.scores[rule.name] || 0; const weight = parseFloat(rule.weight) || 0; weightedTotal += (score * weight) / 10; // 10分制轉換 totalWeight += weight; }); // 如果權重總和不是100%,按比例調整 if (totalWeight > 0 && totalWeight !== 100) { weightedTotal = (weightedTotal * 100) / totalWeight; } return Math.round(weightedTotal); // 100分制,整數 }; // 獲取預設評分項目 const getDefaultScoringItems = (participantType: "individual" | "team") => { if (participantType === "team") { return [ { name: 'innovation', description: '創新程度和獨特性' }, { name: 'technical', description: '技術實現的複雜度和品質' }, { name: 'usability', description: '實際應用價值和用戶體驗' }, { name: 'presentation', description: '團隊展示的清晰度和吸引力' }, { name: 'impact', description: '對行業或社會的潛在影響' } ] } else { return [ { name: 'innovation', description: '創新程度和獨特性' }, { name: 'technical', description: '技術實現的複雜度和品質' }, { name: 'usability', description: '實際應用價值和用戶體驗' }, { name: 'presentation', description: '展示的清晰度和吸引力' }, { name: 'impact', description: '對行業或社會的潛在影響' } ] } } // 處理參賽者類型變更(僅針對混合賽) const handleParticipantTypeChange = (newType: "individual" | "team") => { setSelectedParticipantType(newType) // 重新初始化評分項目 const newScores = getInitialScores(selectedCompetition, newType) setManualScoring({ ...manualScoring, participantId: "", // 清空選擇的參賽者 scores: newScores, }) } const handleSubmitManualScore = async () => { setError("") if (!manualScoring.judgeId || !manualScoring.participantId) { setError("請選擇評審和參賽項目") return } const hasAllScores = Object.values(manualScoring.scores).every((score) => score > 0) if (!hasAllScores) { setError("請為所有評分項目打分") return } if (!manualScoring.comments.trim()) { setError("請填寫評審意見") return } setIsLoading(true) try { // 直接使用競賽規則的評分項目,不需要轉換 // API 會根據 competition_rules 表來驗證和處理評分 const apiScores = { ...manualScoring.scores }; // 驗證評分是否有效(1-10分) const invalidScores = Object.entries(apiScores).filter(([key, value]) => value < 1 || value > 10); if (invalidScores.length > 0) { setError(`評分必須在1-10分之間:${invalidScores.map(([key]) => key).join(', ')}`); return; } // 根據參賽者類型確定participantType和實際的APP ID let actualAppId = manualScoring.participantId; let participantType = 'app'; // 默認為APP評分 // 檢查是否為團隊選擇 const selectedTeam = selectedCompetition?.teams?.find((team: any) => team.id === manualScoring.participantId); if (selectedTeam) { // 如果是團隊,使用團隊下的第一個APP進行評分 if (selectedTeam.apps && selectedTeam.apps.length > 0) { actualAppId = selectedTeam.apps[0].id; // 使用團隊下的第一個APP participantType = 'app'; // 對APP進行評分 } else { setError("該團隊暫無APP,無法進行評分"); return; } } else { // 檢查是否為個人APP const selectedApp = selectedCompetition?.apps?.find((app: any) => app.id === manualScoring.participantId); if (selectedApp) { actualAppId = selectedApp.id; participantType = 'app'; } } const response = await fetch('/api/admin/scoring', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ judgeId: manualScoring.judgeId, participantId: actualAppId, // 使用實際的APP ID participantType: participantType, scores: apiScores, comments: manualScoring.comments.trim(), competitionId: selectedCompetition?.id // 添加競賽ID }) }) const data = await response.json() if (data.success) { const selectedTeam = selectedCompetition?.teams?.find((team: any) => team.id === manualScoring.participantId); const successMessage = selectedTeam ? `團隊「${selectedTeam.name}」的APP評分提交成功!` : "APP評分提交成功!"; setSuccess(successMessage) // 重置表單 setManualScoring({ judgeId: "", participantId: "", participantType: "individual", scores: { innovation: 0, technical: 0, usability: 0, presentation: 0, impact: 0, }, comments: "", }) setShowManualScoring(false) } else { setError(data.message || "評分提交失敗") } } catch (err) { console.error('評分提交失敗:', err) setError("評分提交失敗,請重試") } finally { setIsLoading(false) setTimeout(() => setSuccess(""), 3000) } } const handleViewCompetition = (competition: any) => { setSelectedCompetitionForAction(competition) setShowCompetitionDetail(true) } const handleEditCompetition = (competition: any) => { setSelectedCompetitionForAction(competition) // 調試信息 console.log('🔍 handleEditCompetition 調試信息:'); console.log('競賽數據:', competition); console.log('startDate:', competition.startDate); console.log('endDate:', competition.endDate); console.log('start_date:', competition.start_date); console.log('end_date:', competition.end_date); // 將 ISO 日期字符串轉換為 YYYY-MM-DD 格式 const formatDateForInput = (dateString: string) => { if (!dateString) return ''; try { const date = new Date(dateString); return date.toISOString().split('T')[0]; } catch (error) { console.error('日期格式轉換錯誤:', error); return ''; } }; const startDate = competition.startDate || competition.start_date; const endDate = competition.endDate || competition.end_date; console.log('轉換後的 startDate:', formatDateForInput(startDate)); console.log('轉換後的 endDate:', formatDateForInput(endDate)); setNewCompetition({ name: competition.name, type: competition.type, year: competition.year, month: competition.month, startDate: formatDateForInput(startDate), endDate: formatDateForInput(endDate), description: competition.description, status: competition.status, // 將評審對象數組轉換為 ID 數組 judges: competition.judges ? competition.judges.map((judge: any) => judge.id) : [], // 將團隊對象數組轉換為 ID 數組 participatingTeams: competition.teams ? competition.teams.map((team: any) => team.id) : [], // 將應用對象數組轉換為 ID 數組 participatingApps: competition.apps ? competition.apps.map((app: any) => app.id) : [], evaluationFocus: competition.evaluationFocus || "", rules: competition.rules || [], awardTypes: competition.awardTypes || [], individualConfig: competition.individualConfig || { judges: [], rules: [], awardTypes: [], evaluationFocus: "", }, teamConfig: competition.teamConfig || { judges: [], rules: [], awardTypes: [], evaluationFocus: "", }, }) setShowCreateCompetition(true) // 使用創建競賽對話框 } const handleDeleteCompetition = (competition: any) => { setSelectedCompetitionForAction(competition) setShowDeleteCompetitionConfirm(true) } const handleChangeStatus = (competition: any) => { setSelectedCompetitionForAction(competition) setNewStatus(competition.status) setShowChangeStatusDialog(true) } const confirmDeleteCompetition = async () => { if (!selectedCompetitionForAction) return setIsLoading(true) try { // 調用 API 刪除競賽 const response = await fetch(`/api/admin/competitions/${selectedCompetitionForAction.id}`, { method: 'DELETE', }) const data = await response.json() if (data.success) { // 同時從 context 中刪除 deleteCompetition(selectedCompetitionForAction.id) setShowDeleteCompetitionConfirm(false) setSelectedCompetitionForAction(null) setSuccess("競賽刪除成功!") // 重新載入競賽列表 await fetchCompetitions() } else { setError("競賽刪除失敗: " + data.message) } } catch (error) { console.error('刪除競賽失敗:', error) setError("競賽刪除失敗") } finally { setIsLoading(false) setTimeout(() => setError(""), 3000) setTimeout(() => setSuccess(""), 3000) } } const handleUpdateStatus = async () => { if (!selectedCompetitionForAction) return setIsLoading(true) try { // 更新資料庫 const updatedCompetition = await updateCompetitionInDb(selectedCompetitionForAction.id, { status: newStatus, }) if (updatedCompetition) { // 同時更新 context updateCompetition(selectedCompetitionForAction.id, { ...selectedCompetitionForAction, status: newStatus, }) setShowChangeStatusDialog(false) setSelectedCompetitionForAction(null) setSuccess("競賽狀態更新成功!") } else { setError("競賽狀態更新失敗") } } catch (error) { console.error('更新競賽狀態失敗:', error) setError("競賽狀態更新失敗") } finally { setIsLoading(false) setTimeout(() => setError(""), 3000) } } const getCompetitionTypeIcon = (type: string) => { switch (type) { case "individual": return case "team": return case "mixed": return default: return } } const getCompetitionTypeColor = (type: string) => { switch (type) { case "individual": return "bg-blue-100 text-blue-800 border-blue-200" case "team": return "bg-purple-100 text-purple-800 border-purple-200" case "mixed": return "bg-orange-100 text-orange-800 border-orange-200" default: return "bg-gray-100 text-gray-800 border-gray-200" } } const getScoreLabelText = (key: string) => { switch (key) { case "innovation": return "創新性" case "technical": return "技術性" case "usability": return "實用性" case "presentation": return "展示性" case "impact": return "影響力" default: return key } } const getCompetitionTypeText = (type: string) => { switch (type) { case "individual": return "個人賽" case "team": return "團體賽" case "mixed": return "混合賽" default: return "未知類型" } } // 格式化時間顯示 const formatDateRange = (startDate: string, endDate: string) => { try { const start = new Date(startDate) const end = new Date(endDate) const formatDate = (date: Date) => { return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}` } return `${formatDate(start)} ~ ${formatDate(end)}` } catch (error) { return `${startDate} ~ ${endDate}` } } // 計算權重百分比 const calculateWeightPercentage = (weight: any) => { const weightNum = parseFloat(weight) || 0; // 如果權重已經是百分比格式(大於1),直接使用 if (weightNum > 1) { return weightNum; } // 如果權重是小數格式(0-1),轉換為百分比 return weightNum * 100; } // 計算總權重 const calculateTotalWeight = (rules: any[]) => { return rules.reduce((sum, rule) => { return sum + calculateWeightPercentage(rule.weight); }, 0); } const getParticipantCount = (competition: any) => { // 如果是從資料庫載入的競賽,使用 teams 屬性 if (competition.teams && Array.isArray(competition.teams)) { return competition.teams.length } // 如果是本地競賽數據,使用原來的邏輯 switch (competition.type) { case "individual": return competition.participatingApps?.length || 0 case "team": return competition.participatingTeams?.length || 0 case "mixed": return (competition.participatingApps?.length || 0) + (competition.participatingTeams?.length || 0) default: return 0 } } const [scoringProgress, setScoringProgress] = useState>({}); // 載入評分進度數據 const loadScoringProgress = async (competitionId: string) => { try { const response = await fetch(`/api/competitions/scoring-progress?competitionId=${competitionId}`); const data = await response.json(); if (data.success) { setScoringProgress(prev => ({ ...prev, [competitionId]: data.data })); } } catch (error) { console.error('載入評分進度失敗:', error); } }; const getScoringProgress = (competitionId: string) => { return scoringProgress[competitionId] || { completed: 0, total: 0, percentage: 0 }; } const getStatusColor = (status: string) => { switch (status) { case "completed": return "bg-green-100 text-green-800 border-green-200" case "ongoing": return "bg-yellow-100 text-yellow-800 border-yellow-200" case "active": return "bg-blue-100 text-blue-800 border-blue-200" case "judging": return "bg-orange-100 text-orange-800 border-orange-200" case "upcoming": return "bg-gray-100 text-gray-800 border-gray-200" default: return "bg-gray-100 text-gray-800 border-gray-200" } } const getStatusText = (status: string) => { switch (status) { case "completed": return "已完成" case "ongoing": case "active": return "進行中" case "judging": return "評審中" case "upcoming": return "即將開始" default: return status } } const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) setSuccess("連結已複製到剪貼簿!") setTimeout(() => setSuccess(""), 3000) } // 获取筛选后的奖项 const getFilteredAwards = () => { let filteredAwards = [...awards] // 搜索功能 - 按应用名称、创作者或奖项名称搜索 if (awardSearchQuery.trim()) { const query = awardSearchQuery.toLowerCase().trim() filteredAwards = filteredAwards.filter((award) => { return ( award.appName?.toLowerCase().includes(query) || award.creator?.toLowerCase().includes(query) || award.awardName?.toLowerCase().includes(query) ) }) } // 年份筛选 if (awardYearFilter !== "all") { filteredAwards = filteredAwards.filter((award) => award.year === Number.parseInt(awardYearFilter)) } // 月份筛选 if (awardMonthFilter !== "all") { filteredAwards = filteredAwards.filter((award) => award.month === Number.parseInt(awardMonthFilter)) } // 奖项类型筛选 if (awardTypeFilter !== "all") { if (awardTypeFilter === "ranking") { filteredAwards = filteredAwards.filter((award) => parseInt(award.rank) > 0 && parseInt(award.rank) <= 3) } else if (awardTypeFilter === "popular") { filteredAwards = filteredAwards.filter((award) => award.awardType === "popular") } else { filteredAwards = filteredAwards.filter((award) => award.awardType === awardTypeFilter) } } // 竞赛类型筛选 if (awardCompetitionTypeFilter !== "all") { filteredAwards = filteredAwards.filter((award) => award.competitionType === awardCompetitionTypeFilter) } return filteredAwards.sort((a, b) => { // 按年份、月份、排名排序 if (a.year !== b.year) return b.year - a.year if (a.month !== b.month) return b.month - a.month const aRank = parseInt(a.rank) || 0 const bRank = parseInt(b.rank) || 0 if (aRank !== bRank) { if (aRank === 0) return 1 if (bRank === 0) return -1 return aRank - bRank } return 0 }) } const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== "undefined" ? window.location.origin : 'http://localhost:3000') const judgeScoringUrl = `${baseUrl}/judge-scoring` // Filter out proposal competitions from display const displayCompetitions = competitions.filter((competition) => competition.type !== "proposal") return (
{/* Success/Error Messages */} {success && ( {success} )} {error && ( {error} )} {/* Header */}

競賽管理

管理個人賽、團體賽、混合賽競賽活動

{currentCompetition && (
當前競賽:{currentCompetition.name} ({getCompetitionTypeText(currentCompetition.type)})
)}
{/* Stats Cards */}

總競賽數

{isLoadingDb ? ( ) : ( dbStats?.total || displayCompetitions.length )}

進行中

{isLoadingDb ? ( ) : ( dbStats?.active || displayCompetitions.filter((c) => c.status === "active" || c.status === "ongoing").length )}

評審團

{judgeStats?.totalJudges || dbJudges.length || judges.length}

已頒獎項

{awards.length}

競賽列表 團隊管理 評審管理 評分管理 獎項管理 競賽列表 管理所有競賽活動 競賽名稱 類型 時間 狀態 參賽項目 評分進度 操作 {isLoadingDb ? (

載入競賽列表中...

) : (dbCompetitions.length > 0 ? dbCompetitions : displayCompetitions).length === 0 ? (

尚無競賽資料

點擊「創建競賽」按鈕開始建立第一個競賽

) : (dbCompetitions.length > 0 ? dbCompetitions : displayCompetitions).map((competition) => { const isCurrentCompetition = currentCompetition?.id === competition.id const scoringProgress = getScoringProgress(competition.id) const participantCount = getParticipantCount(competition) return (
{isCurrentCompetition && }

{competition.name}

{competition.description}

{getCompetitionTypeIcon(competition.type)} {getCompetitionTypeText(competition.type)}

{competition.year}年{competition.month}月

{formatDateRange( competition.start_date || competition.startDate, competition.end_date || competition.endDate )}

{getStatusText(competition.status)}
{getCompetitionTypeIcon(competition.type)} {participantCount} 個
{scoringProgress.completed}/{scoringProgress.total}

{scoringProgress.percentage}% 完成

handleViewCompetition(competition)}> 查看詳情 handleEditCompetition(competition)}> 編輯競賽 handleChangeStatus(competition)}> 修改狀態 handleManualScoring(competition)}> 手動評分 {!isCurrentCompetition && ( setCurrentCompetitionInDb(competition.id)}> 設為當前競賽 )} {isCurrentCompetition && ( clearCurrentCompetitionInDb()}> 取消當前競賽 )} handleDeleteCompetition(competition)} className="text-red-600 focus:text-red-600" > 刪除競賽
) })}

團隊管理

{/* 搜索和篩選區域 */}
{/* 搜索框 */}
{ setTeamSearchTerm(e.target.value) setTeamCurrentPage(1) // 重置到第一頁 }} className="pl-10" />
{/* 部門篩選 */} {/* 清除篩選按鈕 */}
{/* 結果統計 */}
共找到 {(() => { const filtered = dbTeams.filter(team => { const matchesSearch = teamSearchTerm === "" || team.name.toLowerCase().includes(teamSearchTerm.toLowerCase()) || (team.leader_name && team.leader_name.toLowerCase().includes(teamSearchTerm.toLowerCase())) const matchesDepartment = teamDepartmentFilter === "all" || (teamDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(team.department) : team.department === teamDepartmentFilter) return matchesSearch && matchesDepartment }) return filtered.length })()} 個團隊
{(() => { // 篩選邏輯 const filtered = dbTeams.filter(team => { const matchesSearch = teamSearchTerm === "" || team.name.toLowerCase().includes(teamSearchTerm.toLowerCase()) || (team.leader_name && team.leader_name.toLowerCase().includes(teamSearchTerm.toLowerCase())) const matchesDepartment = teamDepartmentFilter === "all" || (teamDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(team.department) : team.department === teamDepartmentFilter) return matchesSearch && matchesDepartment }) // 分頁邏輯 const startIndex = (teamCurrentPage - 1) * teamsPerPage const endIndex = startIndex + teamsPerPage const paginatedTeams = filtered.slice(startIndex, endIndex) // 如果沒有找到任何團隊 if (filtered.length === 0) { return (

沒有找到團隊

{teamSearchTerm || teamDepartmentFilter !== "all" ? "請調整搜索條件或篩選條件" : "點擊「創建團隊」按鈕來添加第一個團隊"}

) } return paginatedTeams.map((team, index) => (

{team.name}

隊長:{team.leader_name || team.leader}
{team.department}
{team.contact_email}
{team.member_count || 0} 名成員
{team.app_count || 0} 個應用

{team.description}

)) })()}
{/* 分頁組件 */} {(() => { const filtered = dbTeams.filter(team => { const matchesSearch = teamSearchTerm === "" || team.name.toLowerCase().includes(teamSearchTerm.toLowerCase()) || (team.leader_name && team.leader_name.toLowerCase().includes(teamSearchTerm.toLowerCase())) const matchesDepartment = teamDepartmentFilter === "all" || (teamDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(team.department) : team.department === teamDepartmentFilter) return matchesSearch && matchesDepartment }) const totalPages = Math.ceil(filtered.length / teamsPerPage) // 如果當前頁面超出總頁數,重置到第一頁 if (teamCurrentPage > totalPages && totalPages > 0) { setTeamCurrentPage(1) } if (totalPages <= 1) return null return (
第 {teamCurrentPage} 頁,共 {totalPages} 頁
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let page; if (totalPages <= 5) { page = i + 1; } else if (teamCurrentPage <= 3) { page = i + 1; } else if (teamCurrentPage >= totalPages - 2) { page = totalPages - 4 + i; } else { page = teamCurrentPage - 2 + i; } return ( ) })}
) })()}

評審管理

{/* 搜索和篩選區域 */}
{/* 搜索框 */}
{ setJudgeSearchTerm(e.target.value) setJudgeCurrentPage(1) // 重置到第一頁 }} className="pl-10" />
{/* 部門篩選 */} {/* 專業領域篩選 */} {/* 狀態篩選 */} {/* 清除篩選按鈕 */}
{/* 結果統計 */}
共找到 {isLoadingJudges ? ( ) : (() => { const judgesToShow = dbJudges.length > 0 ? dbJudges : judges; const filtered = judgesToShow.filter(judge => { const matchesSearch = judgeSearchTerm === "" || judge.name.toLowerCase().includes(judgeSearchTerm.toLowerCase()) || judge.title.toLowerCase().includes(judgeSearchTerm.toLowerCase()) const matchesDepartment = judgeDepartmentFilter === "all" || (judgeDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(judge.department) : judge.department === judgeDepartmentFilter) const matchesExpertise = judgeExpertiseFilter === "all" || judge.expertise.some(exp => exp.includes(judgeExpertiseFilter)) const matchesStatus = judgeStatusFilter === "all" || (judgeStatusFilter === "active" && judge.is_active === true) || (judgeStatusFilter === "inactive" && judge.is_active === false) return matchesSearch && matchesDepartment && matchesExpertise && matchesStatus }) return filtered.length })()} 位評審
{isLoadingJudges ? (

載入評審列表中...

) : (() => { // 使用資料庫數據或 context 數據 const judgesToShow = dbJudges.length > 0 ? dbJudges : judges; console.log('🔍 評審列表渲染 - dbJudges:', dbJudges.map(j => ({ id: j.id, name: j.name, is_active: j.is_active }))) console.log('🔍 評審列表渲染 - judges (context):', judges.map(j => ({ id: j.id, name: j.name, is_active: j.is_active }))) console.log('🔍 評審列表渲染 - judgesToShow:', judgesToShow.map(j => ({ id: j.id, name: j.name, is_active: j.is_active }))) // 篩選邏輯 const filtered = judgesToShow.filter(judge => { const matchesSearch = judgeSearchTerm === "" || judge.name.toLowerCase().includes(judgeSearchTerm.toLowerCase()) || judge.title.toLowerCase().includes(judgeSearchTerm.toLowerCase()) const matchesDepartment = judgeDepartmentFilter === "all" || (judgeDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(judge.department) : judge.department === judgeDepartmentFilter) const matchesExpertise = judgeExpertiseFilter === "all" || judge.expertise.some(exp => exp.includes(judgeExpertiseFilter)) const matchesStatus = judgeStatusFilter === "all" || (judgeStatusFilter === "active" && judge.is_active === true) || (judgeStatusFilter === "inactive" && judge.is_active === false) return matchesSearch && matchesDepartment && matchesExpertise && matchesStatus }) // 分頁邏輯 const startIndex = (judgeCurrentPage - 1) * judgesPerPage const endIndex = startIndex + judgesPerPage const paginatedJudges = filtered.slice(startIndex, endIndex) // 如果沒有找到任何評審 if (filtered.length === 0) { return (

沒有找到評審

{judgeSearchTerm || judgeDepartmentFilter !== "all" || judgeExpertiseFilter !== "all" ? "請調整搜索條件或篩選條件" : "點擊「新增評審」按鈕來添加第一位評審"}

) } return paginatedJudges.map((judge) => (
{judge.name[0]}

{judge.name}

{judge.is_active === false && ( 已停用 )}

{judge.title}

{judge.department}

ID: {judge.id}

{judge.expertise.slice(0, 3).map((skill) => ( {skill} ))} {judge.expertise.length > 3 && ( +{judge.expertise.length - 3} )}
{judge.is_active !== false ? ( ) : ( )}
)) })()}
{/* 分頁組件 */} {(() => { // 使用與主列表相同的數據源 const judgesToShow = dbJudges.length > 0 ? dbJudges : judges; const filtered = judgesToShow.filter(judge => { const matchesSearch = judgeSearchTerm === "" || judge.name.toLowerCase().includes(judgeSearchTerm.toLowerCase()) || judge.title.toLowerCase().includes(judgeSearchTerm.toLowerCase()) const matchesDepartment = judgeDepartmentFilter === "all" || (judgeDepartmentFilter === "其他" ? !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(judge.department) : judge.department === judgeDepartmentFilter) const matchesExpertise = judgeExpertiseFilter === "all" || judge.expertise.some(exp => exp.includes(judgeExpertiseFilter)) const matchesStatus = judgeStatusFilter === "all" || (judgeStatusFilter === "active" && judge.is_active === true) || (judgeStatusFilter === "inactive" && judge.is_active === false) return matchesSearch && matchesDepartment && matchesExpertise && matchesStatus }) const totalPages = Math.ceil(filtered.length / judgesPerPage) // 如果當前頁面超出總頁數,重置到第一頁 if (judgeCurrentPage > totalPages && totalPages > 0) { setJudgeCurrentPage(1) } if (totalPages <= 1) return null return (
第 {judgeCurrentPage} 頁,共 {totalPages} 頁
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let page; if (totalPages <= 5) { page = i + 1; } else if (judgeCurrentPage <= 3) { page = i + 1; } else if (judgeCurrentPage >= totalPages - 2) { page = totalPages - 4 + i; } else { page = judgeCurrentPage - 2 + i; } return ( ) })}
) })()}

獎項管理

{/* 搜索和筛选控件 */} {awards.length > 0 && (
{/* 搜索栏 */}
{ setAwardSearchQuery(e.target.value) resetAwardPagination() }} className="pl-10 pr-10 w-full md:w-96" /> {awardSearchQuery && ( )}
{/* 筛选控件 */}
年份:
月份:
獎項類型:
競賽類型:
{/* 清除筛选按钮 */} {(awardSearchQuery || awardYearFilter !== "all" || awardMonthFilter !== "all" || awardTypeFilter !== "all" || awardCompetitionTypeFilter !== "all") && (
)}
{/* 统计信息 */}
{getFilteredAwards().length}
篩選結果
{getFilteredAwards().filter((a) => parseInt(a.rank) > 0 && parseInt(a.rank) <= 3).length}
前三名獎項
{getFilteredAwards().filter((a) => a.awardType === "popular").length}
人氣獎項
{new Set(getFilteredAwards().map((a) => `${a.year}-${a.month}`)).size}
競賽場次
)} {awards.length === 0 ? (

尚未頒發任何獎項

為競賽參賽者創建獎項,展示他們的成就

) : ( <> {(() => { const filteredAwards = getFilteredAwards() if (filteredAwards.length === 0) { return (
{awardSearchQuery ? ( ) : ( )}

{awardSearchQuery ? ( <>找不到包含「{awardSearchQuery}」的獎項 ) : ( <>暫無符合篩選條件的獎項 )}

{awardSearchQuery ? "嘗試使用其他關鍵字或調整篩選條件" : "請調整篩選條件查看其他獎項"}

{awardSearchQuery && ( )}
) } const startIndex = (awardCurrentPage - 1) * awardsPerPage const endIndex = startIndex + awardsPerPage const paginatedAwards = filteredAwards.slice(startIndex, endIndex) return (
{paginatedAwards.map((award: any) => (
{award.icon}
{/* 獎項基本資訊 */}
{award.award_name}

{award.team_name || award.app_name || "作品"}

by {award.creator}

{/* 評分顯示 */} {award.score && parseFloat(award.score) > 0 && (
{parseFloat(award.score).toFixed(1)}
評審評分
)} {/* 獎項排名 */} {award.rank && parseInt(award.rank) > 0 && ( 第 {parseInt(award.rank)} 名 )}
{/* 競賽資訊 */}
{award.year && award.month ? `${award.year}年${award.month}月` : ''} {getCompetitionTypeText(award.competition_type)}
{/* 應用連結摘要 */} {award.application_links && (

應用連結

{(() => { // 解析 application_links,可能是字符串或對象 let links = award.application_links; if (typeof links === 'string') { try { links = JSON.parse(links); } catch (e) { links = null; } } const linkItems = []; if (links && links.production) { linkItems.push( ); } if (links && links.demo) { linkItems.push( ); } if (links && links.github) { linkItems.push( ); } if (linkItems.length === 0) { return 無連結; } return linkItems; })()}
)} {/* 相關文檔摘要 */} {award.documents && award.documents.length > 0 && (

相關文檔

{award.documents.length} 個文檔 {award.documents.map((doc: any) => doc.type).join(", ")}
)} {/* 得獎照片摘要 */} {award.photos && award.photos.length > 0 && (

得獎照片

{award.photos.length} 張照片
{award.photos.slice(0, 3).map((photo: any, index: number) => (
{photo.url ? ( {`照片 { e.currentTarget.style.display = 'none'; e.currentTarget.nextElementSibling?.classList.remove('hidden'); }} /> ) : null}
📷
))} {award.photos.length > 3 && ( +{award.photos.length - 3} )}
)} {/* 獎項描述 */} {award.description && (

{award.description}

)} {/* 操作按鈕 */}
{(award as any).category || "innovation"}
))}
) })()} {/* 分頁組件 */} {(() => { const filteredAwards = getFilteredAwards() const totalPages = Math.ceil(filteredAwards.length / awardsPerPage) // 如果當前頁面超出總頁數,重置到第一頁 if (awardCurrentPage > totalPages && totalPages > 0) { setAwardCurrentPage(1) } if (totalPages <= 1) return null return (
第 {awardCurrentPage} 頁,共 {totalPages} 頁 • 篩選結果 {filteredAwards.length} 個獎項
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let page; if (totalPages <= 5) { page = i + 1; } else if (awardCurrentPage <= 3) { page = i + 1; } else if (awardCurrentPage >= totalPages - 2) { page = totalPages - 4 + i; } else { page = awardCurrentPage - 2 + i; } return ( ) })}
) })()} )}
{/* Create Competition Dialog - Enhanced for Mixed Competitions */} { setShowCreateCompetition(open) if (!open) { setCreateError("") setSelectedCompetitionForAction(null) // 清除編輯狀態 resetForm() // 重置表單 } }} > {selectedCompetitionForAction ? '編輯競賽' : '創建新競賽'} {selectedCompetitionForAction ? '修改競賽的基本資訊、類型和評比規則' : '設定競賽的基本資訊、類型和評比規則'}
{createError && ( {createError} )} {/* Basic Information */}

基本資訊

setNewCompetition({ ...newCompetition, name: e.target.value })} placeholder="輸入競賽名稱" />
setNewCompetition({ ...newCompetition, startDate: e.target.value })} />
setNewCompetition({ ...newCompetition, endDate: e.target.value })} />