新增得獎更新、刪除的功能
This commit is contained in:
145
app/api/admin/awards/[id]/route.ts
Normal file
145
app/api/admin/awards/[id]/route.ts
Normal 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 });
|
||||
}
|
||||
}
|
@@ -160,9 +160,45 @@ export async function GET(request: NextRequest) {
|
||||
awardType: award.award_type,
|
||||
teamName: (award as any).team_name_from_teams || award.team_name,
|
||||
appName: award.app_name,
|
||||
applicationLinks: (award as any).application_links ? JSON.parse((award as any).application_links) : null,
|
||||
documents: (award as any).documents ? JSON.parse((award as any).documents) : [],
|
||||
photos: (award as any).photos ? JSON.parse((award as any).photos) : [],
|
||||
applicationLinks: (() => {
|
||||
const links = (award as any).application_links;
|
||||
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; // 已經是數組
|
||||
})(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@@ -71,6 +71,8 @@ export function CompetitionManagement() {
|
||||
deleteJudge,
|
||||
awards,
|
||||
addAward,
|
||||
updateAward,
|
||||
deleteAward,
|
||||
getAppDetailedScores,
|
||||
judgeScores,
|
||||
getAppJudgeScores,
|
||||
@@ -1081,9 +1083,11 @@ export function CompetitionManagement() {
|
||||
}
|
||||
|
||||
if (judgesData.success) {
|
||||
console.log('✅ 載入評審成功:', judgesData.data?.length || 0, '位評審')
|
||||
console.log('👥 評審詳細資料:', judgesData.data)
|
||||
setCompetitionJudges(judgesData.data)
|
||||
} else {
|
||||
console.error('載入評審失敗:', judgesData.message)
|
||||
console.error('❌ 載入評審失敗:', judgesData.message)
|
||||
setCompetitionJudges([])
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1870,7 +1874,7 @@ export function CompetitionManagement() {
|
||||
|
||||
// 根據獎項類型設定圖標
|
||||
let icon = "🏆"
|
||||
switch (newAward.awardType) {
|
||||
switch (newAward.awardType as any) {
|
||||
case "gold": icon = "🥇"; break;
|
||||
case "silver": icon = "🥈"; break;
|
||||
case "bronze": icon = "🥉"; break;
|
||||
@@ -1908,9 +1912,14 @@ export function CompetitionManagement() {
|
||||
|
||||
console.log('📝 準備獎項數據:', awardData)
|
||||
|
||||
// 調用 API 創建獎項
|
||||
const response = await fetch('/api/admin/awards', {
|
||||
method: 'POST',
|
||||
// 判斷是創建還是編輯模式
|
||||
const isEditMode = selectedAward && selectedAward.id
|
||||
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: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
@@ -1920,36 +1929,38 @@ export function CompetitionManagement() {
|
||||
const result = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.message || '創建獎項失敗')
|
||||
throw new Error(result.message || (isEditMode ? '更新獎項失敗' : '創建獎項失敗'))
|
||||
}
|
||||
|
||||
// 創建成功,添加到本地列表
|
||||
const newAwardItem = {
|
||||
id: result.data.id,
|
||||
// 創建或更新成功,處理本地列表
|
||||
const awardItem = {
|
||||
id: result.data.id || selectedAward.id,
|
||||
competitionId: newAward.competitionId,
|
||||
competitionName: competition.name,
|
||||
participantId: newAward.participantId,
|
||||
participantType: newAward.participantType,
|
||||
participantName: participantName,
|
||||
creatorName: creatorName,
|
||||
awardType: newAward.awardType,
|
||||
appId: actualParticipantType === "individual" ? newAward.participantId : (participant?.app_id || null),
|
||||
teamId: actualParticipantType === "team" ? newAward.participantId : null,
|
||||
appName: actualParticipantType === "individual" ? participantName : (participant?.app_name || null),
|
||||
teamName: actualParticipantType === "team" ? participantName : null,
|
||||
creator: creatorName,
|
||||
awardType: newAward.awardType as any,
|
||||
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(),
|
||||
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({
|
||||
@@ -1974,7 +1985,8 @@ export function CompetitionManagement() {
|
||||
})
|
||||
|
||||
setShowCreateAward(false)
|
||||
setSuccess("獎項創建成功!")
|
||||
setSelectedAward(null)
|
||||
setSuccess(isEditMode ? "獎項更新成功!" : "獎項創建成功!")
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
|
||||
} catch (error) {
|
||||
@@ -1991,7 +2003,10 @@ export function CompetitionManagement() {
|
||||
id: award.id,
|
||||
competitionId: award.competitionId,
|
||||
awardName: award.award_name,
|
||||
hasCompetitionId: !!award.competitionId
|
||||
hasCompetitionId: !!award.competitionId,
|
||||
photos: award.photos,
|
||||
photosType: typeof award.photos,
|
||||
photosLength: award.photos?.length
|
||||
} : null
|
||||
});
|
||||
|
||||
@@ -2022,7 +2037,8 @@ export function CompetitionManagement() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleEditAward = (award: any) => {
|
||||
const handleEditAward = async (award: any) => {
|
||||
console.log('🎯 開始編輯獎項:', award)
|
||||
setSelectedAward(award)
|
||||
setNewAward({
|
||||
competitionId: award.competitionId,
|
||||
@@ -2044,6 +2060,16 @@ export function CompetitionManagement() {
|
||||
judgeComments: (award as any).judgeComments || "",
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2056,16 +2082,36 @@ export function CompetitionManagement() {
|
||||
if (!awardToDelete) return
|
||||
|
||||
setIsLoading(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
// 這裡應該調用 context 中的刪除函數
|
||||
// deleteAward(awardToDelete.id)
|
||||
try {
|
||||
// 調用 API 刪除獎項
|
||||
const response = await fetch(`/api/admin/awards/${awardToDelete.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.message || '刪除獎項失敗')
|
||||
}
|
||||
|
||||
// 刪除成功,從本地列表移除
|
||||
deleteAward(awardToDelete.id)
|
||||
|
||||
setShowDeleteAwardConfirm(false)
|
||||
setAwardToDelete(null)
|
||||
setSuccess("獎項刪除成功!")
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
|
||||
} catch (error) {
|
||||
console.error('刪除獎項失敗:', error)
|
||||
setError(error instanceof Error ? error.message : '刪除獎項失敗')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleManualScoring = async (competition: any) => {
|
||||
@@ -4048,7 +4094,7 @@ export function CompetitionManagement() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 min-h-[400px]">
|
||||
{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>
|
||||
<CardContent className="p-4 flex-grow flex flex-col justify-between">
|
||||
<div className="space-y-3 flex-grow">
|
||||
@@ -9127,36 +9173,63 @@ export function CompetitionManagement() {
|
||||
)}
|
||||
|
||||
{/* 得獎照片 */}
|
||||
{(selectedAward as any).photos && (selectedAward as any).photos.length > 0 && (
|
||||
{(() => {
|
||||
const photos = (selectedAward as any)?.photos;
|
||||
console.log('🖼️ 檢查照片資料:', {
|
||||
hasPhotos: !!photos,
|
||||
photosType: typeof photos,
|
||||
photosLength: photos?.length,
|
||||
photosData: photos
|
||||
});
|
||||
|
||||
if (!photos || !Array.isArray(photos) || photos.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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">
|
||||
{(selectedAward as any).photos.map((photo: any) => (
|
||||
<div key={photo.id} className="space-y-2">
|
||||
{photos.map((photo: any, index: number) => {
|
||||
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 || "得獎照片"}
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
) : (
|
||||
<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 && (
|
||||
<p className="text-xs text-gray-600 text-center">{photo.caption}</p>
|
||||
{(photo.caption || photo.name) && (
|
||||
<p className="text-xs text-gray-600 text-center">
|
||||
{photo.caption || photo.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
|
||||
<div className="flex justify-end space-x-3 pt-4 border-t">
|
||||
<Button variant="outline" onClick={() => setShowAwardDetail(false)}>
|
||||
|
@@ -82,20 +82,14 @@ export function ScoringManagement() {
|
||||
const [isLoadingRules, setIsLoadingRules] = useState(false)
|
||||
|
||||
|
||||
// 調試:檢查競賽數據
|
||||
console.log('📋 競賽數據:', competitions)
|
||||
console.log('👨⚖️ 評審數據:', judges)
|
||||
console.log('📊 競賽數量:', competitions?.length || 0)
|
||||
|
||||
// 檢查初始載入狀態
|
||||
useEffect(() => {
|
||||
if (competitions && competitions.length > 0) {
|
||||
console.log('✅ 競賽數據已載入,關閉初始載入狀態')
|
||||
setIsInitialLoading(false)
|
||||
|
||||
// 自動選擇第一個競賽(如果沒有選中的話)
|
||||
if (!selectedCompetition) {
|
||||
console.log('🎯 自動選擇第一個競賽:', competitions[0].name)
|
||||
setSelectedCompetition(competitions[0])
|
||||
}
|
||||
}
|
||||
@@ -176,7 +170,6 @@ export function ScoringManagement() {
|
||||
setError('載入評分數據失敗')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('載入評分數據失敗:', err)
|
||||
setError('載入評分數據失敗')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
@@ -376,7 +369,6 @@ export function ScoringManagement() {
|
||||
|
||||
// 根據參賽者類型確定participantType
|
||||
const selectedParticipant = competitionParticipants.find(p => p.id === manualScoring.participantId)
|
||||
console.log('🔍 選中的參賽者:', selectedParticipant);
|
||||
|
||||
// 由於所有參賽者都是團隊的 app,所以 participantType 應該是 'app'
|
||||
const participantType = 'app'
|
||||
@@ -392,7 +384,6 @@ export function ScoringManagement() {
|
||||
recordId: selectedRecord?.id // 編輯時的記錄ID
|
||||
}
|
||||
|
||||
console.log('🔍 提交評分請求數據:', requestData);
|
||||
|
||||
const response = await fetch('/api/admin/scoring', {
|
||||
method: 'POST',
|
||||
@@ -403,7 +394,6 @@ export function ScoringManagement() {
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
console.log('🔍 API 回應:', data);
|
||||
|
||||
if (data.success) {
|
||||
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
|
||||
@@ -444,7 +434,6 @@ export function ScoringManagement() {
|
||||
setError(data.message || "評分提交失敗")
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('評分提交失敗:', err)
|
||||
setError("評分提交失敗,請重試")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
@@ -479,7 +468,6 @@ export function ScoringManagement() {
|
||||
setScoringStats(data.data)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('載入評分統計失敗:', err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,33 +484,24 @@ export function ScoringManagement() {
|
||||
const loadCompetitionData = async () => {
|
||||
if (!selectedCompetition) return
|
||||
|
||||
console.log('🔍 開始載入競賽數據,競賽ID:', selectedCompetition.id)
|
||||
setIsLoadingData(true)
|
||||
setError("")
|
||||
|
||||
try {
|
||||
// 載入競賽評審
|
||||
console.log('📋 載入競賽評審...')
|
||||
const judgesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/judges`)
|
||||
const judgesData = await judgesResponse.json()
|
||||
|
||||
console.log('評審API回應:', judgesData)
|
||||
|
||||
if (judgesData.success && judgesData.data && judgesData.data.judges) {
|
||||
setCompetitionJudges(judgesData.data.judges)
|
||||
console.log('✅ 評審數據載入成功:', judgesData.data.judges.length, '個評審')
|
||||
} else {
|
||||
console.error('❌ 評審數據載入失敗:', judgesData.message || 'API回應格式錯誤')
|
||||
setCompetitionJudges([])
|
||||
}
|
||||
|
||||
// 使用統一的評分進度API來獲取準確的參賽者數量
|
||||
console.log('📱 載入競賽參賽者(使用統一計算邏輯)...')
|
||||
const scoringProgressResponse = await fetch(`/api/competitions/scoring-progress?competitionId=${selectedCompetition.id}`)
|
||||
const scoringProgressData = await scoringProgressResponse.json()
|
||||
|
||||
console.log('評分進度API回應:', scoringProgressData)
|
||||
|
||||
let participants = []
|
||||
|
||||
if (scoringProgressData.success && scoringProgressData.data) {
|
||||
@@ -533,7 +512,6 @@ export function ScoringManagement() {
|
||||
const appsResponse = await fetch(`/api/competitions/${selectedCompetition.id}/apps`)
|
||||
const appsData = await appsResponse.json()
|
||||
|
||||
console.log('應用API回應:', appsData)
|
||||
|
||||
if (appsData.success && appsData.data && appsData.data.apps) {
|
||||
// 直接使用API返回的APP數據,確保數量與評分進度一致
|
||||
@@ -546,38 +524,26 @@ export function ScoringManagement() {
|
||||
creator: app.creator || '未知作者',
|
||||
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)
|
||||
console.log('✅ 參賽者數據載入完成:', participants.length, '個參賽者')
|
||||
console.log('🔍 參賽者詳細數據:', participants)
|
||||
|
||||
// 載入競賽規則
|
||||
console.log('📋 載入競賽規則...')
|
||||
const rulesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/rules`)
|
||||
const rulesData = await rulesResponse.json()
|
||||
|
||||
if (rulesData.success && rulesData.data) {
|
||||
setCompetitionRules(rulesData.data)
|
||||
console.log('✅ 競賽規則載入成功:', rulesData.data.length, '個規則')
|
||||
} else {
|
||||
console.error('❌ 競賽規則載入失敗:', rulesData.message || 'API回應格式錯誤')
|
||||
setCompetitionRules([])
|
||||
}
|
||||
|
||||
// 如果沒有載入到任何數據,顯示警告
|
||||
if (participants.length === 0) {
|
||||
console.warn('⚠️ 沒有載入到任何參賽者數據')
|
||||
setError('該競賽暫無參賽者數據,請檢查競賽設置')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ 載入競賽數據失敗:', err)
|
||||
setError('載入競賽數據失敗: ' + (err instanceof Error ? err.message : '未知錯誤'))
|
||||
|
||||
// 設置空數組以避免undefined錯誤
|
||||
@@ -599,13 +565,10 @@ export function ScoringManagement() {
|
||||
|
||||
if (data.success) {
|
||||
setScoringSummary(data.data)
|
||||
console.log('✅ 評分完成度匯總載入成功:', data.data)
|
||||
} else {
|
||||
console.log('❌ 評分完成度匯總載入失敗:', data)
|
||||
setScoringSummary(null)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('載入評分完成度匯總失敗:', error)
|
||||
setScoringSummary(null)
|
||||
} finally {
|
||||
setIsLoadingSummary(false)
|
||||
@@ -634,11 +597,9 @@ export function ScoringManagement() {
|
||||
scoredJudges: data.data?.length || 0
|
||||
})
|
||||
} else {
|
||||
console.error('載入APP評分詳情失敗:', data.message)
|
||||
setError(data.message || '載入APP評分詳情失敗')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('載入APP評分詳情失敗:', error)
|
||||
setError('載入APP評分詳情失敗')
|
||||
} finally {
|
||||
setIsLoadingAppDetails(false)
|
||||
@@ -713,9 +674,7 @@ export function ScoringManagement() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Select value={selectedCompetition?.id || ""} onValueChange={(value) => {
|
||||
console.log('🎯 選擇競賽:', value)
|
||||
const competition = competitions.find(c => c.id === value)
|
||||
console.log('🏆 找到競賽:', competition)
|
||||
setSelectedCompetition(competition)
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
|
@@ -65,6 +65,8 @@ interface CompetitionContextType {
|
||||
loadingAwards: boolean
|
||||
loadAwards: () => Promise<void>
|
||||
addAward: (award: Omit<Award, "id">) => void
|
||||
updateAward: (award: Award) => void
|
||||
deleteAward: (awardId: string) => void
|
||||
getAwardsByYear: (year: number) => Award[]
|
||||
getAwardsByMonth: (year: number, month: number) => Award[]
|
||||
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[] => {
|
||||
return awards.filter((award) => award.year === year)
|
||||
}
|
||||
@@ -843,6 +873,8 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
||||
loadingAwards,
|
||||
loadAwards,
|
||||
addAward,
|
||||
updateAward,
|
||||
deleteAward,
|
||||
getAwardsByYear,
|
||||
getAwardsByMonth,
|
||||
getAvailableYears,
|
||||
|
@@ -4676,7 +4676,17 @@ export class AwardService extends DatabaseServiceBase {
|
||||
|
||||
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 sql = `UPDATE awards SET ${setClause} WHERE id = ?`;
|
||||
|
Reference in New Issue
Block a user