修正評審評分清單失敗問題
This commit is contained in:
@@ -860,32 +860,11 @@ export function CompetitionManagement() {
|
||||
|
||||
// Get participants based on competition type
|
||||
const getParticipants = (competitionType: string) => {
|
||||
console.log('🔍 getParticipants 調用:', {
|
||||
competitionType,
|
||||
dbTeamsLength: dbTeams.length,
|
||||
teamsLength: teams.length,
|
||||
isLoadingTeams
|
||||
})
|
||||
switch (competitionType) {
|
||||
case "individual":
|
||||
console.log('🔍 個人賽APP數據:', {
|
||||
availableAppsLength: availableApps.length,
|
||||
availableApps: availableApps.slice(0, 2)
|
||||
})
|
||||
return availableApps
|
||||
case "team":
|
||||
// 總是使用 dbTeams,如果為空則返回空數組
|
||||
console.log('🔍 getParticipants 團隊數據:', {
|
||||
dbTeamsLength: dbTeams.length,
|
||||
dbTeams: dbTeams.slice(0, 2), // 只顯示前2個
|
||||
firstTeam: dbTeams[0] ? {
|
||||
id: dbTeams[0].id,
|
||||
name: dbTeams[0].name,
|
||||
leader_name: dbTeams[0].leader_name,
|
||||
member_count: dbTeams[0].member_count,
|
||||
submissionDate: dbTeams[0].submissionDate
|
||||
} : null
|
||||
})
|
||||
return dbTeams
|
||||
default:
|
||||
return []
|
||||
@@ -898,12 +877,6 @@ export function CompetitionManagement() {
|
||||
let searchTerm = participantSearchTerm
|
||||
let departmentFilterValue = departmentFilter
|
||||
|
||||
console.log('🔍 getFilteredParticipants 調用:', {
|
||||
competitionType,
|
||||
participantsLength: participants.length,
|
||||
searchTerm,
|
||||
departmentFilterValue
|
||||
})
|
||||
|
||||
// Use separate search terms for mixed competitions
|
||||
if (newCompetition.type === "mixed") {
|
||||
@@ -5319,22 +5292,6 @@ export function CompetitionManagement() {
|
||||
) : (
|
||||
getFilteredParticipants("team").map((participant) => {
|
||||
const isSelected = newCompetition.participatingTeams.includes(participant.id)
|
||||
console.log('🔍 團隊數據調試 - 完整對象:', participant)
|
||||
console.log('🔍 團隊數據調試 - 關鍵欄位:', {
|
||||
name: participant.name,
|
||||
leader_name: participant.leader_name,
|
||||
leader: participant.leader,
|
||||
member_count: participant.member_count,
|
||||
submissionDate: participant.submissionDate,
|
||||
hasLeaderName: 'leader_name' in participant,
|
||||
hasMemberCount: 'member_count' in participant,
|
||||
allKeys: Object.keys(participant)
|
||||
})
|
||||
console.log('🔍 渲染測試:', {
|
||||
leaderDisplay: participant.leader_name || participant.leader || '未知',
|
||||
memberDisplay: participant.member_count || participant.memberCount || 0,
|
||||
dateDisplay: participant.submissionDate ? new Date(participant.submissionDate).toLocaleDateString('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/') : '未知'
|
||||
})
|
||||
return (
|
||||
<div
|
||||
key={participant.id}
|
||||
|
@@ -19,12 +19,14 @@ interface ScoringLinkDialogProps {
|
||||
export function ScoringLinkDialog({ open, onOpenChange, currentCompetition }: ScoringLinkDialogProps) {
|
||||
const { toast } = useToast()
|
||||
|
||||
// 生成評分連結URL
|
||||
// 根據選擇的競賽生成評分連結URL
|
||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000')
|
||||
const scoringUrl = `${baseUrl}/judge-scoring`
|
||||
const scoringUrl = currentCompetition?.id
|
||||
? `${baseUrl}/judge-scoring?competitionId=${currentCompetition.id}`
|
||||
: `${baseUrl}/judge-scoring`
|
||||
|
||||
const accessCode = "judge2024"
|
||||
const competitionName = currentCompetition?.name || "2024年第四季綜合AI競賽"
|
||||
const competitionName = currentCompetition?.name || "未選擇競賽"
|
||||
|
||||
const handleCopyUrl = async () => {
|
||||
try {
|
||||
|
@@ -26,10 +26,11 @@ interface ScoringRecord {
|
||||
participantId: string
|
||||
participantName: string
|
||||
participantType: "individual" | "team"
|
||||
teamName?: string
|
||||
scores: Record<string, number>
|
||||
totalScore: number
|
||||
comments: string
|
||||
submittedAt: string
|
||||
submittedAt?: string
|
||||
status: "completed" | "pending" | "draft"
|
||||
}
|
||||
|
||||
@@ -198,8 +199,52 @@ export function ScoringManagement() {
|
||||
return totalWeight > 0 ? totalScore / totalWeight : 0
|
||||
}
|
||||
|
||||
// 生成所有評審和APP的組合
|
||||
const generateAllScoringCombinations = () => {
|
||||
if (!competitionJudges.length || !competitionParticipants.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const combinations: ScoringRecord[] = []
|
||||
|
||||
// 為每個評審和每個參賽者創建組合
|
||||
competitionJudges.forEach(judge => {
|
||||
competitionParticipants.forEach(participant => {
|
||||
// 檢查是否已有評分記錄
|
||||
const existingRecord = scoringRecords.find(record =>
|
||||
record.judgeId === judge.id && record.participantId === participant.id
|
||||
)
|
||||
|
||||
if (existingRecord) {
|
||||
// 使用現有記錄
|
||||
combinations.push(existingRecord)
|
||||
} else {
|
||||
// 創建新的待評分記錄
|
||||
combinations.push({
|
||||
id: `pending_${judge.id}_${participant.id}`,
|
||||
judgeId: judge.id,
|
||||
judgeName: judge.name,
|
||||
participantId: participant.id,
|
||||
participantName: participant.displayName || participant.name,
|
||||
participantType: participant.type as "individual" | "team",
|
||||
teamName: participant.teamName,
|
||||
scores: {},
|
||||
totalScore: 0,
|
||||
comments: "",
|
||||
status: "pending"
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return combinations
|
||||
}
|
||||
|
||||
const getFilteredRecords = () => {
|
||||
let filtered = [...scoringRecords]
|
||||
// 使用生成的組合而不是僅有的評分記錄
|
||||
const allCombinations = generateAllScoringCombinations()
|
||||
let filtered = [...allCombinations]
|
||||
|
||||
if (statusFilter !== "all") {
|
||||
filtered = filtered.filter(record => record.status === statusFilter)
|
||||
}
|
||||
@@ -272,7 +317,13 @@ export function ScoringManagement() {
|
||||
scores: initialScores,
|
||||
comments: record.comments || '',
|
||||
})
|
||||
setShowEditScoring(true)
|
||||
|
||||
// 如果是待評分項目,顯示手動評分對話框;如果是已完成項目,顯示編輯對話框
|
||||
if (record.status === "pending") {
|
||||
setShowManualScoring(true)
|
||||
} else {
|
||||
setShowEditScoring(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmitScore = async () => {
|
||||
@@ -344,7 +395,36 @@ export function ScoringManagement() {
|
||||
|
||||
if (data.success) {
|
||||
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
|
||||
await loadScoringData() // 重新載入數據
|
||||
|
||||
// 更新本地評分記錄
|
||||
const newRecord: ScoringRecord = {
|
||||
id: data.data?.id || `new_${Date.now()}`,
|
||||
judgeId: manualScoring.judgeId,
|
||||
judgeName: competitionJudges.find(j => j.id === manualScoring.judgeId)?.name || '未知評審',
|
||||
participantId: manualScoring.participantId,
|
||||
participantName: competitionParticipants.find(p => p.id === manualScoring.participantId)?.displayName || competitionParticipants.find(p => p.id === manualScoring.participantId)?.name || '未知參賽者',
|
||||
participantType: competitionParticipants.find(p => p.id === manualScoring.participantId)?.type as "individual" | "team" || "individual",
|
||||
scores: apiScores,
|
||||
totalScore: calculateTotalScore(apiScores, rules),
|
||||
comments: manualScoring.comments.trim(),
|
||||
status: "completed",
|
||||
submittedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
// 更新本地狀態
|
||||
setScoringRecords(prev => {
|
||||
const existingIndex = prev.findIndex(r => r.judgeId === newRecord.judgeId && r.participantId === newRecord.participantId)
|
||||
if (existingIndex >= 0) {
|
||||
// 更新現有記錄
|
||||
const updated = [...prev]
|
||||
updated[existingIndex] = newRecord
|
||||
return updated
|
||||
} else {
|
||||
// 添加新記錄
|
||||
return [...prev, newRecord]
|
||||
}
|
||||
})
|
||||
|
||||
setShowManualScoring(false)
|
||||
setShowEditScoring(false)
|
||||
setSelectedRecord(null)
|
||||
@@ -463,7 +543,7 @@ export function ScoringManagement() {
|
||||
name: app.name, // app 名稱
|
||||
type: 'team',
|
||||
teamName: team.name || '未知團隊', // 團隊名稱
|
||||
displayName: `${team.name || '未知團隊'} - ${app.name}`, // 顯示名稱:團隊名稱 - app名稱
|
||||
displayName: app.name, // 只顯示 app 名稱,團隊名稱通過 teamName 屬性獲取
|
||||
creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長',
|
||||
teamId: team.id // 保存團隊 ID
|
||||
})
|
||||
@@ -583,13 +663,24 @@ export function ScoringManagement() {
|
||||
setAppScoringDetails(null)
|
||||
}
|
||||
|
||||
const progress = {
|
||||
total: scoringStats.totalScores,
|
||||
completed: scoringStats.completedScores,
|
||||
pending: scoringStats.pendingScores,
|
||||
percentage: scoringStats.completionRate
|
||||
// 計算基於所有組合的統計數據
|
||||
const calculateProgressStats = () => {
|
||||
const allCombinations = generateAllScoringCombinations()
|
||||
const total = allCombinations.length
|
||||
const completed = allCombinations.filter(record => record.status === "completed").length
|
||||
const pending = allCombinations.filter(record => record.status === "pending").length
|
||||
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0
|
||||
|
||||
return {
|
||||
total,
|
||||
completed,
|
||||
pending,
|
||||
percentage
|
||||
}
|
||||
}
|
||||
|
||||
const progress = calculateProgressStats()
|
||||
|
||||
// 顯示初始載入狀態
|
||||
if (isInitialLoading) {
|
||||
return (
|
||||
@@ -677,7 +768,7 @@ export function ScoringManagement() {
|
||||
<CardContent className="p-4">
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-blue-600">
|
||||
{scoringSummary ? scoringSummary.overallStats.totalJudges : progress.completed}
|
||||
{competitionJudges.length}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">評審總數</p>
|
||||
</div>
|
||||
@@ -687,7 +778,7 @@ export function ScoringManagement() {
|
||||
<CardContent className="p-4">
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-green-600">
|
||||
{scoringSummary ? scoringSummary.overallStats.totalApps : progress.pending}
|
||||
{competitionParticipants.length}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">參賽APP數</p>
|
||||
</div>
|
||||
@@ -697,7 +788,7 @@ export function ScoringManagement() {
|
||||
<CardContent className="p-4">
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-orange-600">
|
||||
{scoringSummary ? scoringSummary.overallStats.completedScores : progress.percentage}
|
||||
{progress.completed}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">已完成評分</p>
|
||||
</div>
|
||||
@@ -707,7 +798,7 @@ export function ScoringManagement() {
|
||||
<CardContent className="p-4">
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-purple-600">
|
||||
{scoringSummary ? `${scoringSummary.overallStats.overallCompletionRate}%` : progress.total}
|
||||
{progress.percentage}%
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">總完成率</p>
|
||||
</div>
|
||||
@@ -718,15 +809,10 @@ export function ScoringManagement() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>評分進度</span>
|
||||
<span>
|
||||
{scoringSummary ?
|
||||
`${scoringSummary.overallStats.completedScores} / ${scoringSummary.overallStats.totalPossibleScores}` :
|
||||
`${progress.completed} / ${progress.total}`
|
||||
}
|
||||
</span>
|
||||
<span>{progress.completed} / {progress.total}</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={scoringSummary ? scoringSummary.overallStats.overallCompletionRate : progress.percentage}
|
||||
value={progress.percentage}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
@@ -876,7 +962,7 @@ export function ScoringManagement() {
|
||||
onClick={() => {
|
||||
const container = document.getElementById(`scroll-${judgeId}`)
|
||||
if (container) {
|
||||
container.scrollLeft -= 280 // 滑動一個卡片的寬度
|
||||
container.scrollLeft -= 304 // 滑動一個卡片的寬度 (288px + 16px間距)
|
||||
}
|
||||
}}
|
||||
className="absolute -left-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
||||
@@ -891,7 +977,7 @@ export function ScoringManagement() {
|
||||
onClick={() => {
|
||||
const container = document.getElementById(`scroll-${judgeId}`)
|
||||
if (container) {
|
||||
container.scrollLeft += 280 // 滑動一個卡片的寬度
|
||||
container.scrollLeft += 304 // 滑動一個卡片的寬度 (288px + 16px間距)
|
||||
}
|
||||
}}
|
||||
className="absolute -right-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
||||
@@ -906,26 +992,33 @@ export function ScoringManagement() {
|
||||
style={{
|
||||
scrollbarWidth: 'none',
|
||||
msOverflowStyle: 'none',
|
||||
maxWidth: 'calc(4 * 256px + 3 * 16px)' // 4個卡片 + 3個間距
|
||||
maxWidth: 'calc(4 * 288px + 3 * 16px)' // 4個卡片 + 3個間距
|
||||
}}
|
||||
>
|
||||
{records.map((record) => (
|
||||
<div
|
||||
key={record.id}
|
||||
className="flex-shrink-0 w-64 bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
|
||||
className="flex-shrink-0 w-72 bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{/* 項目標題和類型 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-start justify-between space-x-2">
|
||||
<div className="flex items-start space-x-2 flex-1 min-w-0">
|
||||
{record.participantType === "individual" ? (
|
||||
<User className="w-4 h-4 text-blue-600" />
|
||||
<User className="w-4 h-4 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||
) : (
|
||||
<Users className="w-4 h-4 text-green-600" />
|
||||
<Users className="w-4 h-4 text-green-600 flex-shrink-0 mt-0.5" />
|
||||
)}
|
||||
<span className="font-medium text-sm truncate">{record.participantName}</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="font-medium text-sm leading-tight break-words block">
|
||||
{record.participantType === "team" && record.teamName
|
||||
? `${record.teamName} - ${record.participantName}`
|
||||
: record.participantName
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Badge variant="outline" className="text-xs flex-shrink-0">
|
||||
{record.participantType === "individual" ? "個人" : "團隊"}
|
||||
</Badge>
|
||||
</div>
|
||||
|
@@ -79,11 +79,9 @@ export function TeamManagement() {
|
||||
if (data.success) {
|
||||
setApiTeams(data.data)
|
||||
} else {
|
||||
console.error('獲取團隊數據失敗:', data.message)
|
||||
setError('獲取團隊數據失敗')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('獲取團隊數據失敗:', error)
|
||||
setError('獲取團隊數據失敗')
|
||||
} finally {
|
||||
setIsLoadingTeams(false)
|
||||
@@ -105,11 +103,9 @@ export function TeamManagement() {
|
||||
if (data.success) {
|
||||
setAvailableUsers(data.data)
|
||||
} else {
|
||||
console.error('獲取用戶列表失敗:', data.message)
|
||||
setError('獲取用戶列表失敗')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('獲取用戶列表失敗:', error)
|
||||
setError('獲取用戶列表失敗')
|
||||
} finally {
|
||||
setIsLoadingUsers(false)
|
||||
|
@@ -198,11 +198,8 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
||||
if (department) params.append('department', department)
|
||||
|
||||
const url = `/api/apps/${app.id}/stats${params.toString() ? `?${params.toString()}` : ''}`
|
||||
console.log('🔍 調用統計 API:', url)
|
||||
console.log('🔍 應用 ID:', app.id)
|
||||
|
||||
const response = await fetch(url)
|
||||
console.log('🔍 API 響應狀態:', response.status, response.statusText)
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('❌ API 響應錯誤:', response.status, response.statusText)
|
||||
@@ -210,10 +207,8 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
console.log('🔍 API 響應數據:', data)
|
||||
|
||||
if (data.success) {
|
||||
console.log('📊 應用統計數據加載成功:', data.data)
|
||||
setAppStats(data.data)
|
||||
setCurrentRating(data.data.basic.rating)
|
||||
setReviewCount(data.data.basic.reviewCount)
|
||||
@@ -272,14 +267,12 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
||||
}, [startDate, endDate, open, app.id, handleDateRangeChange])
|
||||
|
||||
const handleTryApp = async () => {
|
||||
console.log('handleTryApp 被調用', { user: user?.id, appId: app.id })
|
||||
|
||||
if (user) {
|
||||
addToRecentApps(app.id.toString())
|
||||
|
||||
// 記錄用戶活動
|
||||
try {
|
||||
console.log('開始記錄用戶活動...')
|
||||
const response = await fetch('/api/user/activity', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -298,7 +291,6 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
console.log('活動記錄成功')
|
||||
} else {
|
||||
console.error('活動記錄失敗:', response.status, response.statusText)
|
||||
}
|
||||
@@ -306,7 +298,6 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
||||
console.error('記錄活動失敗:', error)
|
||||
}
|
||||
} else {
|
||||
console.log('用戶未登入,跳過活動記錄')
|
||||
}
|
||||
|
||||
// Increment view count when trying the app
|
||||
|
@@ -59,7 +59,6 @@ export function CompetitionDetailDialog({
|
||||
const handleTryApp = (appId: string) => {
|
||||
incrementViewCount(appId)
|
||||
addToRecentApps(appId)
|
||||
console.log(`Opening app ${appId}`)
|
||||
}
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
|
@@ -94,17 +94,14 @@ export function PopularityRankings() {
|
||||
if (appsData.success) {
|
||||
// 合併個人應用和團隊應用
|
||||
const allApps = appsData.data.apps || []
|
||||
console.log('📱 載入的應用數據:', allApps)
|
||||
setCompetitionApps(allApps)
|
||||
}
|
||||
if (teamsData.success) {
|
||||
const teams = teamsData.data.teams || []
|
||||
console.log('👥 載入的團隊數據:', teams)
|
||||
setCompetitionTeams(teams)
|
||||
}
|
||||
if (judgesData.success) {
|
||||
const judges = judgesData.data.judges || []
|
||||
console.log('👨⚖️ 載入的評審數據:', judges)
|
||||
setCompetitionJudges(judges)
|
||||
}
|
||||
} catch (error) {
|
||||
|
@@ -46,7 +46,6 @@ export function FavoritesPage() {
|
||||
}, [user])
|
||||
|
||||
const handleUseApp = async (app: any) => {
|
||||
console.log('handleUseApp 被調用', { user: user?.id, appId: app.id, appName: app.name })
|
||||
|
||||
try {
|
||||
// Increment view count when using the app
|
||||
@@ -65,7 +64,6 @@ export function FavoritesPage() {
|
||||
if (response.ok) {
|
||||
// 記錄用戶活動
|
||||
try {
|
||||
console.log('開始記錄用戶活動...')
|
||||
const activityResponse = await fetch('/api/user/activity', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -84,7 +82,6 @@ export function FavoritesPage() {
|
||||
})
|
||||
|
||||
if (activityResponse.ok) {
|
||||
console.log('活動記錄成功')
|
||||
} else {
|
||||
console.error('活動記錄失敗:', activityResponse.status, activityResponse.statusText)
|
||||
}
|
||||
@@ -98,7 +95,6 @@ export function FavoritesPage() {
|
||||
console.error('增加查看次數失敗:', response.status, response.statusText)
|
||||
}
|
||||
} else {
|
||||
console.log('用戶未登入,跳過活動記錄')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('增加查看次數失敗:', error)
|
||||
@@ -109,7 +105,6 @@ export function FavoritesPage() {
|
||||
const url = app.appUrl.startsWith('http') ? app.appUrl : `https://${app.appUrl}`
|
||||
window.open(url, "_blank", "noopener,noreferrer")
|
||||
} else {
|
||||
console.log(`Opening app: ${app.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -353,7 +353,6 @@ export function ReviewSystem({ appId, appName, currentRating, onRatingUpdate }:
|
||||
{[5, 4, 3, 2, 1].map((rating) => {
|
||||
const count = reviews.filter((r) => r.rating === rating).length
|
||||
const percentage = (count / reviews.length) * 100
|
||||
console.log(`評分 ${rating}: count=${count}, percentage=${percentage}%`) // 調試信息
|
||||
return (
|
||||
<div key={rating} className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-1 w-12">
|
||||
|
Reference in New Issue
Block a user