修正獎勵評審資訊、評分資訊

This commit is contained in:
2025-09-27 17:24:26 +08:00
parent b06fd94c99
commit 88c3fde372
11 changed files with 2399 additions and 459 deletions

View File

@@ -149,9 +149,12 @@ export async function GET(request: NextRequest) {
competitionId: award.competition_id,
competitionName: (award as any).competition_name,
competitionType: (award as any).competition_type,
competitionDescription: (award as any).competition_description,
competitionStartDate: (award as any).competition_start_date,
competitionEndDate: (award as any).competition_end_date,
awardName: award.award_name,
awardType: award.award_type,
teamName: award.team_name,
teamName: (award as any).team_name_from_teams || award.team_name,
appName: award.app_name,
applicationLinks: award.application_links ? JSON.parse(award.application_links) : null,
documents: award.documents ? JSON.parse(award.documents) : [],

View File

@@ -0,0 +1,211 @@
// =====================================================
// 獲取獎項評分詳情 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const awardId = params.id;
console.log('🔍 獲取獎項評分詳情:', awardId);
// 先獲取獎項資訊
const awardSql = 'SELECT * FROM awards WHERE id = ?';
console.log('🔍 執行 SQL:', awardSql, '參數:', [awardId]);
const award = await db.queryOne(awardSql, [awardId]);
console.log('📊 查詢結果:', award);
if (!award) {
console.log('❌ 找不到獎項:', awardId);
return NextResponse.json({
success: false,
message: '找不到指定的獎項',
data: []
});
}
console.log('📊 獎項資訊:', {
id: award.id,
competitionType: award.competition_type,
appId: award.app_id,
proposalId: award.proposal_id
});
let scores = [];
// 根據競賽類型查詢對應的評分表
if (award.competition_type === 'individual' && award.app_id) {
// 查詢 app_judge_scores
const sql = `
SELECT
ajs.*,
j.name as judge_name,
j.title as judge_title,
j.avatar as judge_avatar,
j.department as judge_department
FROM app_judge_scores ajs
LEFT JOIN judges j ON ajs.judge_id = j.id
WHERE ajs.app_id = ?
ORDER BY ajs.created_at DESC
`;
console.log('🔍 執行個人賽 SQL:', sql, '參數:', [award.app_id]);
scores = await db.query(sql, [award.app_id]);
console.log('📊 從 app_judge_scores 獲取到:', scores.length, '筆');
} else if (award.competition_type === 'proposal' && award.proposal_id) {
// 查詢 proposal_judge_scores
const sql = `
SELECT
pjs.*,
j.name as judge_name,
j.title as judge_title,
j.avatar as judge_avatar,
j.department as judge_department
FROM proposal_judge_scores pjs
LEFT JOIN judges j ON pjs.judge_id = j.id
WHERE pjs.proposal_id = ?
ORDER BY pjs.created_at DESC
`;
console.log('🔍 執行提案賽 SQL:', sql, '參數:', [award.proposal_id]);
scores = await db.query(sql, [award.proposal_id]);
console.log('📊 從 proposal_judge_scores 獲取到:', scores.length, '筆');
} else if (award.competition_type === 'team' && award.app_id) {
// 查詢 judge_scores (團隊賽)
const sql = `
SELECT
js.*,
j.name as judge_name,
j.title as judge_title,
j.avatar as judge_avatar,
j.department as judge_department
FROM judge_scores js
LEFT JOIN judges j ON js.judge_id = j.id
WHERE js.app_id = ?
ORDER BY js.submitted_at DESC
`;
console.log('🔍 執行團隊賽 SQL:', sql, '參數:', [award.app_id]);
scores = await db.query(sql, [award.app_id]);
console.log('📊 從 judge_scores 獲取到:', scores.length, '筆');
} else {
console.log('❌ 無法確定評分表類型:', {
competitionType: award.competition_type,
hasAppId: !!award.app_id,
hasProposalId: !!award.proposal_id,
hasTeamId: !!award.team_id
});
}
// 處理評分資料
const processedScores = scores.map((score: any) => {
// 根據不同的評分表處理不同的欄位
let overallScore = 0;
let criteria: Array<{name: string, score: number, maxScore: number}> = [];
if (award.competition_type === 'individual') {
// app_judge_scores 的處理
overallScore = score.total_score || 0;
criteria = [
{ name: '創新性', score: score.innovation_score, maxScore: 10 },
{ name: '技術性', score: score.technical_score, maxScore: 10 },
{ name: '可用性', score: score.usability_score, maxScore: 10 },
{ name: '展示性', score: score.presentation_score, maxScore: 10 },
{ name: '影響力', score: score.impact_score, maxScore: 10 }
];
} else if (award.competition_type === 'proposal') {
// proposal_judge_scores 的處理
overallScore = score.total_score || 0;
criteria = [
{ name: '問題識別', score: score.problem_identification_score, maxScore: 10 },
{ name: '解決方案可行性', score: score.solution_feasibility_score, maxScore: 10 },
{ name: '創新性', score: score.innovation_score, maxScore: 10 },
{ name: '影響力', score: score.impact_score, maxScore: 10 },
{ name: '展示性', score: score.presentation_score, maxScore: 10 }
];
} else if (award.competition_type === 'team') {
// judge_scores (團隊賽) 的處理
overallScore = score.overall_score || score.total_score || 0;
// 嘗試解析 criteria 欄位,如果沒有則使用預設的團隊評分標準
if (score.criteria) {
try {
criteria = typeof score.criteria === 'string'
? JSON.parse(score.criteria)
: score.criteria;
} catch (e) {
console.error('解析 criteria 失敗:', e);
criteria = [];
}
}
// 如果沒有 criteria 或解析失敗,使用預設的團隊評分標準
if (!criteria || criteria.length === 0) {
criteria = [
{ name: '團隊合作', score: score.teamwork_score || 0, maxScore: 10 },
{ name: '創新性', score: score.innovation_score || 0, maxScore: 10 },
{ name: '技術實作', score: score.technical_score || 0, maxScore: 10 },
{ name: '展示能力', score: score.presentation_score || 0, maxScore: 10 },
{ name: '整體表現', score: score.overall_performance || 0, maxScore: 10 }
];
}
}
return {
id: score.id,
awardId: awardId,
judgeId: score.judge_id,
judgeName: score.judge_name || '未知評審',
judgeTitle: score.judge_title || '評審',
judgeAvatar: score.judge_avatar,
judgeDepartment: score.judge_department,
overallScore: overallScore,
submittedAt: score.submitted_at || score.created_at,
comment: score.comment || score.comments || '',
criteria: criteria,
};
});
console.log('✅ 處理後的評分資料:', processedScores.length, '筆');
return NextResponse.json({
success: true,
message: '評分詳情獲取成功',
data: processedScores
});
} catch (error) {
console.error('❌ 獲取評分詳情失敗:', error);
console.error('❌ 錯誤詳情:', {
message: error instanceof Error ? error.message : '未知錯誤',
stack: error instanceof Error ? error.stack : undefined,
awardId: params.id
});
// 檢查評分表是否存在
try {
const tableCheck = await db.query("SHOW TABLES LIKE 'judge_scores'");
console.log('🔍 judge_scores 表存在檢查:', tableCheck);
if (tableCheck.length > 0) {
// 檢查表結構
const structure = await db.query("DESCRIBE judge_scores");
console.log('🔍 judge_scores 表結構:', structure);
}
} catch (tableError) {
console.error('❌ 檢查表存在性失敗:', tableError);
}
return NextResponse.json({
success: false,
message: '獲取評分詳情失敗',
error: error instanceof Error ? error.message : '未知錯誤',
details: {
awardId: params.id,
errorType: error instanceof Error ? error.constructor.name : 'Unknown'
}
}, { status: 500 });
}
}

View File

@@ -451,7 +451,10 @@ export default function CompetitionPage() {
</div>
<CardTitle className="text-lg line-clamp-2">
{award.appName || award.proposalTitle || award.teamName}
{award.competitionType === "team"
? (award.teamName || "團隊名稱")
: (award.appName || award.proposalTitle || "應用名稱")
}
</CardTitle>
<p className="text-sm text-gray-500">by {award.creator}</p>
<div className="text-xs text-gray-400">

View File

@@ -148,7 +148,7 @@ export default function AIShowcasePlatform() {
canAccessAdmin,
} = useAuth()
const { competitions, awards, getAwardsByYear, getCompetitionRankings } = useCompetition()
const { competitions, awards, getAwardsByYear, getCompetitionRankings, loadingAwards, getAvailableYears } = useCompetition()
const [showLogin, setShowLogin] = useState(false)
const [showRegister, setShowRegister] = useState(false)
@@ -193,6 +193,17 @@ export default function AIShowcasePlatform() {
showAwardDetail
});
// 動態設定初始年份
useEffect(() => {
if (awards.length > 0 && selectedYear === 2024) {
const availableYears = getAvailableYears();
if (availableYears.length > 0) {
setSelectedYear(availableYears[0]);
console.log('🎯 自動設定年份為:', availableYears[0]);
}
}
}, [awards, selectedYear, getAvailableYears]);
// 載入應用數據
const loadApps = async () => {
try {
@@ -595,8 +606,11 @@ export default function AIShowcasePlatform() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2024">2024</SelectItem>
<SelectItem value="2023">2023</SelectItem>
{getAvailableYears().map((year) => (
<SelectItem key={year} value={year.toString()}>
{year}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
@@ -720,24 +734,26 @@ export default function AIShowcasePlatform() {
{/* Statistics */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="text-lg font-bold text-blue-600">{filteredAwards.length}</div>
<div className="text-lg font-bold text-blue-600">
{loadingAwards ? '...' : filteredAwards.length}
</div>
<div className="text-xs text-blue-600"></div>
</div>
<div className="text-center p-3 bg-yellow-50 rounded-lg">
<div className="text-lg font-bold text-yellow-600">
{filteredAwards.filter((a) => a.rank > 0 && a.rank <= 3).length}
{loadingAwards ? '...' : filteredAwards.filter((a) => a.rank > 0 && a.rank <= 3).length}
</div>
<div className="text-xs text-yellow-600"></div>
</div>
<div className="text-center p-3 bg-red-50 rounded-lg">
<div className="text-lg font-bold text-red-600">
{filteredAwards.filter((a) => a.awardType === "popular").length}
{loadingAwards ? '...' : filteredAwards.filter((a) => a.awardType === "popular").length}
</div>
<div className="text-xs text-red-600"></div>
</div>
<div className="text-center p-3 bg-green-50 rounded-lg">
<div className="text-lg font-bold text-green-600">
{new Set(filteredAwards.map((a) => `${a.year}-${a.month}`)).size}
{loadingAwards ? '...' : new Set(filteredAwards.map((a) => `${a.year}-${a.month}`)).size}
</div>
<div className="text-xs text-green-600"></div>
</div>
@@ -747,7 +763,12 @@ export default function AIShowcasePlatform() {
</Card>
{/* Awards Grid with Enhanced Display */}
{filteredAwards.length > 0 ? (
{loadingAwards ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
) : filteredAwards.length > 0 ? (
<div className="space-y-8">
{/* Group awards by month */}
{Array.from(new Set(filteredAwards.map((award) => award.month)))