新增得獎更新、刪除的功能

This commit is contained in:
2025-09-29 22:52:53 +08:00
parent 57893128b2
commit ea6afb1675
6 changed files with 367 additions and 112 deletions

View File

@@ -0,0 +1,145 @@
// =====================================================
// 獎項編輯和刪除 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { AwardService } from '@/lib/services/database-service';
// 編輯獎項
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const { id } = await params;
const body = await request.json();
// 驗證必填欄位
if (!body.award_name || !body.creator) {
return NextResponse.json({
success: false,
message: '缺少必填欄位',
error: 'award_name, creator 為必填欄位'
}, { status: 400 });
}
// 驗證獎項類型
const validAwardTypes = ['gold', 'silver', 'bronze', 'popular', 'innovation', 'technical', 'custom'];
if (body.award_type && !validAwardTypes.includes(body.award_type)) {
return NextResponse.json({
success: false,
message: '無效的獎項類型',
error: `award_type 必須是以下之一: ${validAwardTypes.join(', ')}`
}, { status: 400 });
}
// 驗證競賽類型
const validCompetitionTypes = ['individual', 'team', 'proposal'];
if (body.competition_type && !validCompetitionTypes.includes(body.competition_type)) {
return NextResponse.json({
success: false,
message: '無效的競賽類型',
error: `competition_type 必須是以下之一: ${validCompetitionTypes.join(', ')}`
}, { status: 400 });
}
// 驗證獎項類別
const validCategories = ['innovation', 'technical', 'practical', 'popular', 'teamwork', 'solution', 'creativity'];
if (body.category && !validCategories.includes(body.category)) {
return NextResponse.json({
success: false,
message: '無效的獎項類別',
error: `category 必須是以下之一: ${validCategories.join(', ')}`
}, { status: 400 });
}
// 準備更新資料
const updateData = {
competition_id: body.competition_id,
app_id: body.app_id || null,
team_id: body.team_id || null,
proposal_id: body.proposal_id || null,
app_name: body.app_name || null,
team_name: body.team_name || null,
proposal_title: body.proposal_title || null,
creator: body.creator,
award_type: body.award_type,
award_name: body.award_name,
score: parseFloat(body.score) || 0,
year: parseInt(body.year) || new Date().getFullYear(),
month: parseInt(body.month) || new Date().getMonth() + 1,
icon: body.icon || '🏆',
custom_award_type_id: body.custom_award_type_id || null,
competition_type: body.competition_type,
rank: parseInt(body.rank) || 0,
category: body.category,
description: body.description || null,
judge_comments: body.judge_comments || null,
application_links: body.application_links ? JSON.stringify(body.application_links) : null,
documents: body.documents ? JSON.stringify(body.documents) : null,
photos: body.photos ? JSON.stringify(body.photos) : null,
};
// 更新獎項
const success = await AwardService.updateAward(id, updateData);
if (success) {
// 獲取更新後的獎項資料
const updatedAward = await AwardService.getAwardById(id);
return NextResponse.json({
success: true,
message: '獎項更新成功',
data: updatedAward
});
} else {
return NextResponse.json({
success: false,
message: '獎項更新失敗',
error: '找不到指定的獎項或更新失敗'
}, { status: 404 });
}
} catch (error) {
console.error('更新獎項失敗:', error);
return NextResponse.json({
success: false,
message: '更新獎項失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}
// 刪除獎項
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const { id } = await params;
// 刪除獎項
const success = await AwardService.deleteAward(id);
if (success) {
return NextResponse.json({
success: true,
message: '獎項刪除成功'
});
} else {
return NextResponse.json({
success: false,
message: '獎項刪除失敗',
error: '找不到指定的獎項或刪除失敗'
}, { status: 404 });
}
} catch (error) {
console.error('刪除獎項失敗:', error);
return NextResponse.json({
success: false,
message: '刪除獎項失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}

View File

@@ -160,9 +160,45 @@ export async function GET(request: NextRequest) {
awardType: award.award_type, awardType: award.award_type,
teamName: (award as any).team_name_from_teams || award.team_name, teamName: (award as any).team_name_from_teams || award.team_name,
appName: award.app_name, appName: award.app_name,
applicationLinks: (award as any).application_links ? JSON.parse((award as any).application_links) : null, applicationLinks: (() => {
documents: (award as any).documents ? JSON.parse((award as any).documents) : [], const links = (award as any).application_links;
photos: (award as any).photos ? JSON.parse((award as any).photos) : [], if (!links) return null;
if (typeof links === 'string') {
try {
return JSON.parse(links);
} catch (e) {
console.warn('解析 application_links JSON 失敗:', e);
return null;
}
}
return links; // 已經是對象
})(),
documents: (() => {
const docs = (award as any).documents;
if (!docs) return [];
if (typeof docs === 'string') {
try {
return JSON.parse(docs);
} catch (e) {
console.warn('解析 documents JSON 失敗:', e);
return [];
}
}
return docs; // 已經是數組
})(),
photos: (() => {
const pics = (award as any).photos;
if (!pics) return [];
if (typeof pics === 'string') {
try {
return JSON.parse(pics);
} catch (e) {
console.warn('解析 photos JSON 失敗:', e);
return [];
}
}
return pics; // 已經是數組
})(),
}; };
}); });

View File

@@ -71,6 +71,8 @@ export function CompetitionManagement() {
deleteJudge, deleteJudge,
awards, awards,
addAward, addAward,
updateAward,
deleteAward,
getAppDetailedScores, getAppDetailedScores,
judgeScores, judgeScores,
getAppJudgeScores, getAppJudgeScores,
@@ -1081,9 +1083,11 @@ export function CompetitionManagement() {
} }
if (judgesData.success) { if (judgesData.success) {
console.log('✅ 載入評審成功:', judgesData.data?.length || 0, '位評審')
console.log('👥 評審詳細資料:', judgesData.data)
setCompetitionJudges(judgesData.data) setCompetitionJudges(judgesData.data)
} else { } else {
console.error('載入評審失敗:', judgesData.message) console.error('載入評審失敗:', judgesData.message)
setCompetitionJudges([]) setCompetitionJudges([])
} }
} catch (error) { } catch (error) {
@@ -1870,7 +1874,7 @@ export function CompetitionManagement() {
// 根據獎項類型設定圖標 // 根據獎項類型設定圖標
let icon = "🏆" let icon = "🏆"
switch (newAward.awardType) { switch (newAward.awardType as any) {
case "gold": icon = "🥇"; break; case "gold": icon = "🥇"; break;
case "silver": icon = "🥈"; break; case "silver": icon = "🥈"; break;
case "bronze": icon = "🥉"; break; case "bronze": icon = "🥉"; break;
@@ -1908,9 +1912,14 @@ export function CompetitionManagement() {
console.log('📝 準備獎項數據:', awardData) console.log('📝 準備獎項數據:', awardData)
// 調用 API 創建獎項 // 判斷是創建還是編輯模式
const response = await fetch('/api/admin/awards', { const isEditMode = selectedAward && selectedAward.id
method: 'POST', const apiUrl = isEditMode ? `/api/admin/awards/${selectedAward.id}` : '/api/admin/awards'
const method = isEditMode ? 'PUT' : 'POST'
// 調用 API 創建或更新獎項
const response = await fetch(apiUrl, {
method: method,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
@@ -1920,36 +1929,38 @@ export function CompetitionManagement() {
const result = await response.json() const result = await response.json()
if (!response.ok) { if (!response.ok) {
throw new Error(result.message || '創建獎項失敗') throw new Error(result.message || (isEditMode ? '更新獎項失敗' : '創建獎項失敗'))
} }
// 創建成功,添加到本地列表 // 創建或更新成功,處理本地列表
const newAwardItem = { const awardItem = {
id: result.data.id, id: result.data.id || selectedAward.id,
competitionId: newAward.competitionId, competitionId: newAward.competitionId,
competitionName: competition.name, appId: actualParticipantType === "individual" ? newAward.participantId : (participant?.app_id || null),
participantId: newAward.participantId, teamId: actualParticipantType === "team" ? newAward.participantId : null,
participantType: newAward.participantType, appName: actualParticipantType === "individual" ? participantName : (participant?.app_name || null),
participantName: participantName, teamName: actualParticipantType === "team" ? participantName : null,
creatorName: creatorName, creator: creatorName,
awardType: newAward.awardType, awardType: newAward.awardType as any,
awardName: newAward.awardName, awardName: newAward.awardName,
customAwardTypeId: newAward.customAwardTypeId,
score: newAward.score, 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(), year: new Date().getFullYear(),
month: new Date().getMonth() + 1, month: new Date().getMonth() + 1,
icon: icon, icon: icon,
createdAt: new Date().toISOString(), customAwardTypeId: newAward.customAwardTypeId,
competitionType: competition.type as any,
rank: newAward.rank,
category: newAward.category as any,
} }
addAward(newAwardItem) if (isEditMode) {
// 編輯模式:更新現有獎項
updateAward(awardItem)
} else {
// 創建模式:添加新獎項
const { id, ...awardWithoutId } = awardItem
addAward(awardWithoutId)
}
// 重置表單 // 重置表單
setNewAward({ setNewAward({
@@ -1974,8 +1985,9 @@ export function CompetitionManagement() {
}) })
setShowCreateAward(false) setShowCreateAward(false)
setSuccess("獎項創建成功!") setSelectedAward(null)
setTimeout(() => setSuccess(""), 3000) setSuccess(isEditMode ? "獎項更新成功!" : "獎項創建成功!")
setTimeout(() => setSuccess(""), 3000)
} catch (error) { } catch (error) {
console.error('創建獎項失敗:', error) console.error('創建獎項失敗:', error)
@@ -1991,7 +2003,10 @@ export function CompetitionManagement() {
id: award.id, id: award.id,
competitionId: award.competitionId, competitionId: award.competitionId,
awardName: award.award_name, awardName: award.award_name,
hasCompetitionId: !!award.competitionId hasCompetitionId: !!award.competitionId,
photos: award.photos,
photosType: typeof award.photos,
photosLength: award.photos?.length
} : null } : null
}); });
@@ -2022,7 +2037,8 @@ export function CompetitionManagement() {
} }
} }
const handleEditAward = (award: any) => { const handleEditAward = async (award: any) => {
console.log('🎯 開始編輯獎項:', award)
setSelectedAward(award) setSelectedAward(award)
setNewAward({ setNewAward({
competitionId: award.competitionId, competitionId: award.competitionId,
@@ -2044,6 +2060,16 @@ export function CompetitionManagement() {
judgeComments: (award as any).judgeComments || "", judgeComments: (award as any).judgeComments || "",
photos: (award as any).photos || [], photos: (award as any).photos || [],
}) })
// 載入對應競賽的評審和參賽者資料
if (award.competitionId) {
console.log('🔄 載入競賽資料競賽ID:', award.competitionId)
await loadCompetitionParticipants(award.competitionId)
console.log('✅ 載入完成,評審數量:', competitionJudges.length)
} else {
console.log('❌ 獎項沒有 competitionId')
}
setShowCreateAward(true) setShowCreateAward(true)
} }
@@ -2056,16 +2082,36 @@ export function CompetitionManagement() {
if (!awardToDelete) return if (!awardToDelete) return
setIsLoading(true) setIsLoading(true)
await new Promise((resolve) => setTimeout(resolve, 500))
// 這裡應該調用 context 中的刪除函數 try {
// deleteAward(awardToDelete.id) // 調用 API 刪除獎項
const response = await fetch(`/api/admin/awards/${awardToDelete.id}`, {
setShowDeleteAwardConfirm(false) method: 'DELETE',
setAwardToDelete(null) headers: {
setSuccess("獎項刪除成功!") 'Content-Type': 'application/json',
setIsLoading(false) },
setTimeout(() => setSuccess(""), 3000) })
const result = await response.json()
if (!response.ok) {
throw new Error(result.message || '刪除獎項失敗')
}
// 刪除成功,從本地列表移除
deleteAward(awardToDelete.id)
setShowDeleteAwardConfirm(false)
setAwardToDelete(null)
setSuccess("獎項刪除成功!")
setTimeout(() => setSuccess(""), 3000)
} catch (error) {
console.error('刪除獎項失敗:', error)
setError(error instanceof Error ? error.message : '刪除獎項失敗')
} finally {
setIsLoading(false)
}
} }
const handleManualScoring = async (competition: any) => { const handleManualScoring = async (competition: any) => {
@@ -4048,7 +4094,7 @@ export function CompetitionManagement() {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 min-h-[400px]"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 min-h-[400px]">
{paginatedAwards.map((award: any) => ( {paginatedAwards.map((award: any) => (
<Card key={award.id} className="relative overflow-hidden hover:shadow-lg transition-shadow h-[280px] flex flex-col"> <Card key={award.id} className="relative overflow-hidden hover:shadow-lg transition-shadow flex flex-col">
<div className="absolute top-4 right-4 text-2xl">{award.icon}</div> <div className="absolute top-4 right-4 text-2xl">{award.icon}</div>
<CardContent className="p-4 flex-grow flex flex-col justify-between"> <CardContent className="p-4 flex-grow flex flex-col justify-between">
<div className="space-y-3 flex-grow"> <div className="space-y-3 flex-grow">
@@ -9127,36 +9173,63 @@ export function CompetitionManagement() {
)} )}
{/* 得獎照片 */} {/* 得獎照片 */}
{(selectedAward as any).photos && (selectedAward as any).photos.length > 0 && ( {(() => {
<div className="space-y-3"> const photos = (selectedAward as any)?.photos;
<h4 className="text-lg font-semibold text-gray-900"></h4> console.log('🖼️ 檢查照片資料:', {
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> hasPhotos: !!photos,
{(selectedAward as any).photos.map((photo: any) => ( photosType: typeof photos,
<div key={photo.id} className="space-y-2"> photosLength: photos?.length,
<div className="aspect-video bg-gray-100 rounded-lg border overflow-hidden"> photosData: photos
{photo.url ? ( });
<img
src={photo.url} if (!photos || !Array.isArray(photos) || photos.length === 0) {
alt={photo.caption || "得獎照片"} return null;
className="w-full h-full object-cover" }
onError={(e) => {
e.currentTarget.style.display = 'none'; return (
e.currentTarget.nextElementSibling?.classList.remove('hidden'); <div className="space-y-3">
}} <h4 className="text-lg font-semibold text-gray-900"></h4>
/> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
) : null} {photos.map((photo: any, index: number) => {
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-2xl hidden"> console.log('📸 處理照片:', { index, photo });
🖼 return (
<div key={photo.id || photo.url || index} className="space-y-2">
<div className="aspect-video bg-gray-100 rounded-lg border overflow-hidden">
{photo.url ? (
<img
src={photo.url}
alt={photo.caption || photo.name || "得獎照片"}
className="w-full h-full object-cover"
onError={(e) => {
console.log('❌ 圖片載入失敗:', photo.url);
e.currentTarget.style.display = 'none';
e.currentTarget.nextElementSibling?.classList.remove('hidden');
}}
onLoad={() => {
console.log('✅ 圖片載入成功:', photo.url);
}}
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-2xl">
🖼
</div>
)}
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-2xl hidden">
🖼
</div>
</div>
{(photo.caption || photo.name) && (
<p className="text-xs text-gray-600 text-center">
{photo.caption || photo.name}
</p>
)}
</div> </div>
</div> );
{photo.caption && ( })}
<p className="text-xs text-gray-600 text-center">{photo.caption}</p> </div>
)}
</div>
))}
</div> </div>
</div> );
)} })()}
<div className="flex justify-end space-x-3 pt-4 border-t"> <div className="flex justify-end space-x-3 pt-4 border-t">
<Button variant="outline" onClick={() => setShowAwardDetail(false)}> <Button variant="outline" onClick={() => setShowAwardDetail(false)}>

View File

@@ -82,20 +82,14 @@ export function ScoringManagement() {
const [isLoadingRules, setIsLoadingRules] = useState(false) const [isLoadingRules, setIsLoadingRules] = useState(false)
// 調試:檢查競賽數據
console.log('📋 競賽數據:', competitions)
console.log('👨‍⚖️ 評審數據:', judges)
console.log('📊 競賽數量:', competitions?.length || 0)
// 檢查初始載入狀態 // 檢查初始載入狀態
useEffect(() => { useEffect(() => {
if (competitions && competitions.length > 0) { if (competitions && competitions.length > 0) {
console.log('✅ 競賽數據已載入,關閉初始載入狀態')
setIsInitialLoading(false) setIsInitialLoading(false)
// 自動選擇第一個競賽(如果沒有選中的話) // 自動選擇第一個競賽(如果沒有選中的話)
if (!selectedCompetition) { if (!selectedCompetition) {
console.log('🎯 自動選擇第一個競賽:', competitions[0].name)
setSelectedCompetition(competitions[0]) setSelectedCompetition(competitions[0])
} }
} }
@@ -176,7 +170,6 @@ export function ScoringManagement() {
setError('載入評分數據失敗') setError('載入評分數據失敗')
} }
} catch (err) { } catch (err) {
console.error('載入評分數據失敗:', err)
setError('載入評分數據失敗') setError('載入評分數據失敗')
} finally { } finally {
setIsLoading(false) setIsLoading(false)
@@ -376,7 +369,6 @@ export function ScoringManagement() {
// 根據參賽者類型確定participantType // 根據參賽者類型確定participantType
const selectedParticipant = competitionParticipants.find(p => p.id === manualScoring.participantId) const selectedParticipant = competitionParticipants.find(p => p.id === manualScoring.participantId)
console.log('🔍 選中的參賽者:', selectedParticipant);
// 由於所有參賽者都是團隊的 app所以 participantType 應該是 'app' // 由於所有參賽者都是團隊的 app所以 participantType 應該是 'app'
const participantType = 'app' const participantType = 'app'
@@ -392,7 +384,6 @@ export function ScoringManagement() {
recordId: selectedRecord?.id // 編輯時的記錄ID recordId: selectedRecord?.id // 編輯時的記錄ID
} }
console.log('🔍 提交評分請求數據:', requestData);
const response = await fetch('/api/admin/scoring', { const response = await fetch('/api/admin/scoring', {
method: 'POST', method: 'POST',
@@ -403,7 +394,6 @@ export function ScoringManagement() {
}) })
const data = await response.json() const data = await response.json()
console.log('🔍 API 回應:', data);
if (data.success) { if (data.success) {
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!") setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
@@ -444,7 +434,6 @@ export function ScoringManagement() {
setError(data.message || "評分提交失敗") setError(data.message || "評分提交失敗")
} }
} catch (err) { } catch (err) {
console.error('評分提交失敗:', err)
setError("評分提交失敗,請重試") setError("評分提交失敗,請重試")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
@@ -479,7 +468,6 @@ export function ScoringManagement() {
setScoringStats(data.data) setScoringStats(data.data)
} }
} catch (err) { } catch (err) {
console.error('載入評分統計失敗:', err)
} }
} }
@@ -496,32 +484,23 @@ export function ScoringManagement() {
const loadCompetitionData = async () => { const loadCompetitionData = async () => {
if (!selectedCompetition) return if (!selectedCompetition) return
console.log('🔍 開始載入競賽數據競賽ID:', selectedCompetition.id)
setIsLoadingData(true) setIsLoadingData(true)
setError("") setError("")
try { try {
// 載入競賽評審 // 載入競賽評審
console.log('📋 載入競賽評審...')
const judgesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/judges`) const judgesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/judges`)
const judgesData = await judgesResponse.json() const judgesData = await judgesResponse.json()
console.log('評審API回應:', judgesData)
if (judgesData.success && judgesData.data && judgesData.data.judges) { if (judgesData.success && judgesData.data && judgesData.data.judges) {
setCompetitionJudges(judgesData.data.judges) setCompetitionJudges(judgesData.data.judges)
console.log('✅ 評審數據載入成功:', judgesData.data.judges.length, '個評審')
} else { } else {
console.error('❌ 評審數據載入失敗:', judgesData.message || 'API回應格式錯誤')
setCompetitionJudges([]) setCompetitionJudges([])
} }
// 使用統一的評分進度API來獲取準確的參賽者數量 // 使用統一的評分進度API來獲取準確的參賽者數量
console.log('📱 載入競賽參賽者(使用統一計算邏輯)...')
const scoringProgressResponse = await fetch(`/api/competitions/scoring-progress?competitionId=${selectedCompetition.id}`) const scoringProgressResponse = await fetch(`/api/competitions/scoring-progress?competitionId=${selectedCompetition.id}`)
const scoringProgressData = await scoringProgressResponse.json() const scoringProgressData = await scoringProgressResponse.json()
console.log('評分進度API回應:', scoringProgressData)
let participants = [] let participants = []
@@ -533,7 +512,6 @@ export function ScoringManagement() {
const appsResponse = await fetch(`/api/competitions/${selectedCompetition.id}/apps`) const appsResponse = await fetch(`/api/competitions/${selectedCompetition.id}/apps`)
const appsData = await appsResponse.json() const appsData = await appsResponse.json()
console.log('應用API回應:', appsData)
if (appsData.success && appsData.data && appsData.data.apps) { if (appsData.success && appsData.data && appsData.data.apps) {
// 直接使用API返回的APP數據確保數量與評分進度一致 // 直接使用API返回的APP數據確保數量與評分進度一致
@@ -546,38 +524,26 @@ export function ScoringManagement() {
creator: app.creator || '未知作者', creator: app.creator || '未知作者',
teamId: app.teamId || null teamId: app.teamId || null
})) }))
console.log(`${selectedCompetition.type === 'team' ? '團隊' : '個人'}競賽APP數據載入成功:`, participants.length, '個APP')
} else {
console.error('❌ 應用數據載入失敗:', appsData.message || 'API回應格式錯誤')
} }
} else {
console.error('❌ 評分進度數據載入失敗:', scoringProgressData.message || 'API回應格式錯誤')
} }
setCompetitionParticipants(participants) setCompetitionParticipants(participants)
console.log('✅ 參賽者數據載入完成:', participants.length, '個參賽者')
console.log('🔍 參賽者詳細數據:', participants)
// 載入競賽規則 // 載入競賽規則
console.log('📋 載入競賽規則...')
const rulesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/rules`) const rulesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/rules`)
const rulesData = await rulesResponse.json() const rulesData = await rulesResponse.json()
if (rulesData.success && rulesData.data) { if (rulesData.success && rulesData.data) {
setCompetitionRules(rulesData.data) setCompetitionRules(rulesData.data)
console.log('✅ 競賽規則載入成功:', rulesData.data.length, '個規則')
} else { } else {
console.error('❌ 競賽規則載入失敗:', rulesData.message || 'API回應格式錯誤')
setCompetitionRules([]) setCompetitionRules([])
} }
// 如果沒有載入到任何數據,顯示警告 // 如果沒有載入到任何數據,顯示警告
if (participants.length === 0) { if (participants.length === 0) {
console.warn('⚠️ 沒有載入到任何參賽者數據')
setError('該競賽暫無參賽者數據,請檢查競賽設置') setError('該競賽暫無參賽者數據,請檢查競賽設置')
} }
} catch (err) { } catch (err) {
console.error('❌ 載入競賽數據失敗:', err)
setError('載入競賽數據失敗: ' + (err instanceof Error ? err.message : '未知錯誤')) setError('載入競賽數據失敗: ' + (err instanceof Error ? err.message : '未知錯誤'))
// 設置空數組以避免undefined錯誤 // 設置空數組以避免undefined錯誤
@@ -599,13 +565,10 @@ export function ScoringManagement() {
if (data.success) { if (data.success) {
setScoringSummary(data.data) setScoringSummary(data.data)
console.log('✅ 評分完成度匯總載入成功:', data.data)
} else { } else {
console.log('❌ 評分完成度匯總載入失敗:', data)
setScoringSummary(null) setScoringSummary(null)
} }
} catch (error) { } catch (error) {
console.error('載入評分完成度匯總失敗:', error)
setScoringSummary(null) setScoringSummary(null)
} finally { } finally {
setIsLoadingSummary(false) setIsLoadingSummary(false)
@@ -634,11 +597,9 @@ export function ScoringManagement() {
scoredJudges: data.data?.length || 0 scoredJudges: data.data?.length || 0
}) })
} else { } else {
console.error('載入APP評分詳情失敗:', data.message)
setError(data.message || '載入APP評分詳情失敗') setError(data.message || '載入APP評分詳情失敗')
} }
} catch (error) { } catch (error) {
console.error('載入APP評分詳情失敗:', error)
setError('載入APP評分詳情失敗') setError('載入APP評分詳情失敗')
} finally { } finally {
setIsLoadingAppDetails(false) setIsLoadingAppDetails(false)
@@ -713,9 +674,7 @@ export function ScoringManagement() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Select value={selectedCompetition?.id || ""} onValueChange={(value) => { <Select value={selectedCompetition?.id || ""} onValueChange={(value) => {
console.log('🎯 選擇競賽:', value)
const competition = competitions.find(c => c.id === value) const competition = competitions.find(c => c.id === value)
console.log('🏆 找到競賽:', competition)
setSelectedCompetition(competition) setSelectedCompetition(competition)
}}> }}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">

View File

@@ -65,6 +65,8 @@ interface CompetitionContextType {
loadingAwards: boolean loadingAwards: boolean
loadAwards: () => Promise<void> loadAwards: () => Promise<void>
addAward: (award: Omit<Award, "id">) => void addAward: (award: Omit<Award, "id">) => void
updateAward: (award: Award) => void
deleteAward: (awardId: string) => void
getAwardsByYear: (year: number) => Award[] getAwardsByYear: (year: number) => Award[]
getAwardsByMonth: (year: number, month: number) => Award[] getAwardsByMonth: (year: number, month: number) => Award[]
getAvailableYears: () => number[] getAvailableYears: () => number[]
@@ -571,6 +573,34 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
}) })
} }
const updateAward = (award: Award) => {
setAwards((prev) => {
const index = prev.findIndex((existingAward) => existingAward.id === award.id)
if (index === -1) {
console.log('⚠️ 找不到要更新的獎項:', award.id)
return prev
}
console.log('✅ 更新獎項:', award)
const updated = [...prev]
updated[index] = award
return updated
})
}
const deleteAward = (awardId: string) => {
setAwards((prev) => {
const filtered = prev.filter((award) => award.id !== awardId)
if (filtered.length === prev.length) {
console.log('⚠️ 找不到要刪除的獎項:', awardId)
return prev
}
console.log('✅ 刪除獎項:', awardId)
return filtered
})
}
const getAwardsByYear = (year: number): Award[] => { const getAwardsByYear = (year: number): Award[] => {
return awards.filter((award) => award.year === year) return awards.filter((award) => award.year === year)
} }
@@ -843,6 +873,8 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
loadingAwards, loadingAwards,
loadAwards, loadAwards,
addAward, addAward,
updateAward,
deleteAward,
getAwardsByYear, getAwardsByYear,
getAwardsByMonth, getAwardsByMonth,
getAvailableYears, getAvailableYears,

View File

@@ -4676,7 +4676,17 @@ export class AwardService extends DatabaseServiceBase {
if (fields.length === 0) return true; if (fields.length === 0) return true;
const setClause = fields.map(field => `${field} = ?`).join(', '); // 處理 MySQL 保留字,需要用反引號包圍
const setClause = fields.map(field => {
// 將駝峰命名轉換為下劃線命名,並處理保留字
const dbField = field.replace(/([A-Z])/g, '_$1').toLowerCase();
const reservedWords = ['rank', 'order', 'group', 'select', 'from', 'where', 'table'];
if (reservedWords.includes(dbField)) {
return `\`${dbField}\` = ?`;
}
return `${dbField} = ?`;
}).join(', ');
const values = fields.map(field => (updates as any)[field]); const values = fields.map(field => (updates as any)[field]);
const sql = `UPDATE awards SET ${setClause} WHERE id = ?`; const sql = `UPDATE awards SET ${setClause} WHERE id = ?`;