diff --git a/app/api/admin/competitions/stats/route.ts b/app/api/admin/competitions/stats/route.ts index 938cc7f..81a23f8 100644 --- a/app/api/admin/competitions/stats/route.ts +++ b/app/api/admin/competitions/stats/route.ts @@ -12,7 +12,7 @@ export async function GET(request: NextRequest) { // 動態計算每個競賽的狀態 const now = new Date(); - const competitionsWithCalculatedStatus = competitions.map(competition => { + const competitionsWithCalculatedStatus = await Promise.all(competitions.map(async (competition) => { const startDate = new Date(competition.start_date); const endDate = new Date(competition.end_date); @@ -29,14 +29,26 @@ export async function GET(request: NextRequest) { } else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) { calculatedStatus = 'active'; // 進行中 } else if (nowUTC > endDateUTC) { - calculatedStatus = 'completed'; // 已完成 + // 競賽結束後,檢查評分進度 + try { + const { ScoringService } = await import('@/lib/services/database-service'); + const scoringProgress = await ScoringService.getCompetitionScoringProgress(competition.id); + if (scoringProgress.percentage >= 100) { + calculatedStatus = 'completed'; // 評分完成,競賽完成 + } else { + calculatedStatus = 'judging'; // 評分未完成,仍在評審中 + } + } catch (error) { + console.error('獲取評分進度失敗,使用預設狀態:', error); + calculatedStatus = 'judging'; // 無法獲取進度時,預設為評審中 + } } return { ...competition, status: calculatedStatus }; - }); + })); // 計算統計數據 const stats = { diff --git a/components/admin/scoring-management.tsx b/components/admin/scoring-management.tsx index 0257a71..12edb5a 100644 --- a/components/admin/scoring-management.tsx +++ b/components/admin/scoring-management.tsx @@ -366,8 +366,8 @@ export function ScoringManagement() { const selectedParticipant = competitionParticipants.find(p => p.id === manualScoring.participantId) console.log('🔍 選中的參賽者:', selectedParticipant); - // 根據參賽者的實際類型確定 participantType - const participantType = selectedParticipant?.type === 'team' ? 'team' : 'app' + // 由於所有參賽者都是團隊的 app,所以 participantType 應該是 'app' + const participantType = 'app' const requestData = { judgeId: manualScoring.judgeId, @@ -532,18 +532,33 @@ export function ScoringManagement() { } if (teamsData.success && teamsData.data && teamsData.data.teams) { - // 將每個團隊作為獨立的參賽項目(而不是團隊中的每個 app) + // 將每個團隊的每個 app 作為獨立的參賽項目 teamsData.data.teams.forEach((team: any) => { console.log('🔍 處理團隊:', team); - participants.push({ - id: team.id, // 使用團隊的 ID - name: team.name, // 團隊名稱 - type: 'team', - teamName: team.name || '未知團隊', // 團隊名稱 - displayName: team.name, // 顯示團隊名稱 - creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長', - teamId: team.id // 保存團隊 ID - }) + if (team.apps && team.apps.length > 0) { + team.apps.forEach((app: any) => { + console.log('🔍 處理團隊 app:', app); + participants.push({ + id: app.id, // 使用 app 的 ID + name: app.name, // app 名稱 + type: 'team', + teamName: team.name || '未知團隊', // 團隊名稱 + displayName: app.name, // 只顯示 app 名稱,團隊名稱通過 teamName 屬性獲取 + creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長', + teamId: team.id // 保存團隊 ID + }) + }) + } else { + // 如果團隊沒有 app,仍然顯示團隊本身 + participants.push({ + id: team.id, + name: team.name, + type: 'team', + teamName: team.name || '未知團隊', + creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長', + teamId: team.id + }) + } }) console.log('✅ 團隊數據載入成功:', teamsData.data.teams.length, '個團隊') } else { diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index 566ca82..8c5469c 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -1000,15 +1000,15 @@ export class JudgeService extends DatabaseServiceBase { a.name as display_name FROM apps a INNER JOIN competition_apps ca ON a.id = ca.app_id - LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? AND js.competition_id = ? WHERE ca.competition_id = ? AND a.is_active = 1 UNION ALL SELECT DISTINCT - t.id, - t.name, - 'team' as type, + a.id, + a.name, + 'app' as type, 'team' as participant_type, COALESCE(js.total_score, 0) as score, CASE @@ -1017,15 +1017,16 @@ export class JudgeService extends DatabaseServiceBase { END as status, js.submitted_at, t.name as team_name, - t.name as display_name - FROM teams t - INNER JOIN competition_teams ct ON t.id = ct.team_id - LEFT JOIN app_judge_scores js ON js.app_id = CONCAT('team_', t.id) AND js.judge_id = ? - WHERE ct.competition_id = ? AND t.is_active = 1 + CONCAT(t.name, ' - ', a.name) as display_name + FROM apps a + INNER JOIN competition_teams ct ON a.team_id = ct.team_id + LEFT JOIN teams t ON a.team_id = t.id + LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? AND js.competition_id = ? + WHERE ct.competition_id = ? AND a.is_active = 1 ORDER BY display_name `; - params = [judgeId, competitionId, judgeId, competitionId]; + params = [judgeId, competitionId, competitionId, judgeId, competitionId, competitionId]; } else { // 獲取所有競賽的任務 sql = ` @@ -1044,7 +1045,7 @@ export class JudgeService extends DatabaseServiceBase { a.name as display_name FROM apps a INNER JOIN competition_apps ca ON a.id = ca.app_id - LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? WHERE a.is_active = 1 UNION ALL @@ -1065,7 +1066,7 @@ export class JudgeService extends DatabaseServiceBase { FROM apps a INNER JOIN competition_teams ct ON a.team_id = ct.team_id LEFT JOIN teams t ON a.team_id = t.id - LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? WHERE a.is_active = 1 ORDER BY display_name @@ -2019,7 +2020,7 @@ export class CompetitionService extends DatabaseServiceBase { ScoringService.getCompetitionRules(competitionId) ]); - // 根據日期動態計算競賽狀態 + // 根據日期和評分進度動態計算競賽狀態 const now = new Date(); const startDate = new Date(competition.start_date); const endDate = new Date(competition.end_date); @@ -2037,7 +2038,18 @@ export class CompetitionService extends DatabaseServiceBase { } else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) { calculatedStatus = 'active'; // 進行中 } else if (nowUTC > endDateUTC) { - calculatedStatus = 'completed'; // 已完成 + // 競賽結束後,檢查評分進度 + try { + const scoringProgress = await ScoringService.getCompetitionScoringProgress(competitionId); + if (scoringProgress.percentage >= 100) { + calculatedStatus = 'completed'; // 評分完成,競賽完成 + } else { + calculatedStatus = 'judging'; // 評分未完成,仍在評審中 + } + } catch (error) { + console.error('獲取評分進度失敗,使用預設狀態:', error); + calculatedStatus = 'judging'; // 無法獲取進度時,預設為評審中 + } } // 轉換字段名稱以匹配前端期望的格式 @@ -3877,114 +3889,52 @@ export class ScoringService extends DatabaseServiceBase { try { console.log('🔍 獲取競賽評分進度,competitionId:', competitionId); - // 獲取競賽的評審數量 - const judgesResult = await DatabaseServiceBase.safeQuery(` - SELECT COUNT(DISTINCT cj.judge_id) as judge_count - FROM competition_judges cj - WHERE cj.competition_id = ? + // 獲取競賽的參賽APP數量(從 competition_teams 與 apps 串聯) + const appsResult = await DatabaseServiceBase.safeQuery(` + SELECT COUNT(DISTINCT a.id) as app_count + FROM competition_teams ct + INNER JOIN apps a ON ct.team_id = a.team_id + WHERE ct.competition_id = ? AND a.is_active = 1 `, [competitionId]); - const judgeCount = judgesResult[0]?.judge_count || 0; - console.log('🔍 評審數量:', judgeCount); + const appCount = appsResult[0]?.app_count || 0; + console.log('🔍 參賽APP數量 (從 competition_teams):', appCount); - // 獲取競賽類型 - const competitionResult = await DatabaseServiceBase.safeQuery(` - SELECT type FROM competitions WHERE id = ? + // 獲取競賽的評分項目數量(從 competition_rules) + const rulesResult = await DatabaseServiceBase.safeQuery(` + SELECT COUNT(*) as rule_count + FROM competition_rules + WHERE competition_id = ? `, [competitionId]); - const competitionType = competitionResult[0]?.type || 'individual'; - console.log('🔍 競賽類型:', competitionType); - - let participantCount = 0; - - if (competitionType === 'team') { - // 團隊競賽:獲取參賽團隊數量 - const teamsResult = await DatabaseServiceBase.safeQuery(` - SELECT COUNT(DISTINCT ct.team_id) as team_count - FROM competition_teams ct - WHERE ct.competition_id = ? - `, [competitionId]); - - participantCount = teamsResult[0]?.team_count || 0; - console.log('🔍 參賽團隊數量:', participantCount); - } else { - // 個人競賽:獲取參賽APP數量 - const appsResult = await DatabaseServiceBase.safeQuery(` - SELECT COUNT(DISTINCT ca.app_id) as app_count - FROM competition_apps ca - WHERE ca.competition_id = ? - `, [competitionId]); - - participantCount = appsResult[0]?.app_count || 0; - console.log('🔍 參賽APP數量:', participantCount); - } - - // 如果沒有評審或參賽者關聯,嘗試從其他方式獲取 - let finalJudgeCount = judgeCount; - let finalParticipantCount = participantCount; - - if (judgeCount === 0) { - // 嘗試從 judges 表獲取所有評審 - const allJudgesResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as judge_count FROM judges'); - finalJudgeCount = allJudgesResult[0]?.judge_count || 0; - console.log('🔍 使用所有評審數量:', finalJudgeCount); - } - - if (participantCount === 0) { - if (competitionType === 'team') { - // 嘗試從 teams 表獲取所有團隊 - const allTeamsResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as team_count FROM teams'); - finalParticipantCount = allTeamsResult[0]?.team_count || 0; - console.log('🔍 使用所有團隊數量:', finalParticipantCount); - } else { - // 嘗試從 apps 表獲取所有APP - const allAppsResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as app_count FROM apps'); - finalParticipantCount = allAppsResult[0]?.app_count || 0; - console.log('🔍 使用所有APP數量:', finalParticipantCount); - } - } + const ruleCount = rulesResult[0]?.rule_count || 0; + console.log('🔍 評分項目數量:', ruleCount); // 獲取已完成的評分數量 - let completedResult; - if (competitionType === 'team') { - // 團隊競賽:查詢團隊評分記錄 - completedResult = await DatabaseServiceBase.safeQuery(` - SELECT COUNT(*) as completed_count - FROM app_judge_scores js - WHERE js.app_id LIKE 'team_%' - AND EXISTS ( - SELECT 1 FROM competition_teams ct - WHERE ct.competition_id = ? - AND js.app_id = CONCAT('team_', ct.team_id) - ) - `, [competitionId]); - } else { - // 個人競賽:查詢應用評分記錄 - completedResult = await DatabaseServiceBase.safeQuery(` - SELECT COUNT(*) as completed_count - FROM app_judge_scores js - WHERE js.app_id NOT LIKE 'team_%' - AND EXISTS ( - SELECT 1 FROM competition_apps ca - WHERE ca.competition_id = ? - AND ca.app_id = js.app_id - ) - `, [competitionId]); - } + const completedResult = await DatabaseServiceBase.safeQuery(` + SELECT COUNT(*) as completed_count + FROM judge_scores js + WHERE js.competition_id = ? + `, [competitionId]); const completed = completedResult[0]?.completed_count || 0; - const total = finalJudgeCount * finalParticipantCount; + + // 計算總評分數:參賽APP數量 × 評分項目數量 + const total = appCount * ruleCount; + + // 計算進度百分比,確保不超過100% const percentage = total > 0 ? Math.min(Math.round((completed / total) * 100), 100) : 0; - - console.log('🔍 評分進度結果:', { - completed, - total, - percentage, - judgeCount: finalJudgeCount, - participantCount: finalParticipantCount, - competitionType + + console.log('🔍 進度計算詳情:', { + appCount, + ruleCount, + total, + completed, + percentage }); + console.log('🔍 評分進度結果:', { completed, total, percentage }); + return { completed, total,