diff --git a/app/api/admin/awards/[id]/route.ts b/app/api/admin/awards/[id]/route.ts new file mode 100644 index 0000000..74895f7 --- /dev/null +++ b/app/api/admin/awards/[id]/route.ts @@ -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 }); + } +} diff --git a/app/api/admin/awards/route.ts b/app/api/admin/awards/route.ts index d286eeb..6af479d 100644 --- a/app/api/admin/awards/route.ts +++ b/app/api/admin/awards/route.ts @@ -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; // 已經是數組 + })(), }; }); diff --git a/components/admin/competition-management.tsx b/components/admin/competition-management.tsx index 7febc49..50adcc1 100644 --- a/components/admin/competition-management.tsx +++ b/components/admin/competition-management.tsx @@ -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,8 +1985,9 @@ export function CompetitionManagement() { }) setShowCreateAward(false) - setSuccess("獎項創建成功!") - setTimeout(() => setSuccess(""), 3000) + setSelectedAward(null) + setSuccess(isEditMode ? "獎項更新成功!" : "獎項創建成功!") + setTimeout(() => setSuccess(""), 3000) } catch (error) { console.error('創建獎項失敗:', 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) - - setShowDeleteAwardConfirm(false) - setAwardToDelete(null) - setSuccess("獎項刪除成功!") - setIsLoading(false) - setTimeout(() => setSuccess(""), 3000) + 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("獎項刪除成功!") + 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 (
{paginatedAwards.map((award: any) => ( - +
{award.icon}
@@ -9127,36 +9173,63 @@ export function CompetitionManagement() { )} {/* 得獎照片 */} - {(selectedAward as any).photos && (selectedAward as any).photos.length > 0 && ( -
-

得獎照片

-
- {(selectedAward as any).photos.map((photo: any) => ( -
-
- {photo.url ? ( - {photo.caption { - e.currentTarget.style.display = 'none'; - e.currentTarget.nextElementSibling?.classList.remove('hidden'); - }} - /> - ) : null} -
- 🖼️ + {(() => { + 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 ( +
+

得獎照片

+
+ {photos.map((photo: any, index: number) => { + console.log('📸 處理照片:', { index, photo }); + return ( +
+
+ {photo.url ? ( + {photo.caption { + console.log('❌ 圖片載入失敗:', photo.url); + e.currentTarget.style.display = 'none'; + e.currentTarget.nextElementSibling?.classList.remove('hidden'); + }} + onLoad={() => { + console.log('✅ 圖片載入成功:', photo.url); + }} + /> + ) : ( +
+ 🖼️ +
+ )} +
+ 🖼️ +
+
+ {(photo.caption || photo.name) && ( +

+ {photo.caption || photo.name} +

+ )}
-
- {photo.caption && ( -

{photo.caption}

- )} -
- ))} + ); + })} +
-
- )} + ); + })()}