新增競賽前台呈現、刪除競賽、修改競賽狀態

This commit is contained in:
2025-09-16 14:57:40 +08:00
parent 1f2fb14bd0
commit b4386dc481
21 changed files with 1714 additions and 127 deletions

View File

@@ -150,6 +150,67 @@ export function CompetitionManagement() {
}
}
// 獲取當前競賽
const fetchCurrentCompetition = async () => {
try {
const response = await fetch('/api/admin/competitions/current')
const data = await response.json()
if (data.success) {
setCurrentCompetition(data.data)
}
} catch (error) {
console.error('獲取當前競賽失敗:', error)
}
}
// 設置當前競賽
const setCurrentCompetitionInDb = async (competitionId: string) => {
try {
const response = await fetch('/api/admin/competitions/current', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ competitionId }),
})
const data = await response.json()
if (data.success) {
setCurrentCompetition(data.data)
setSuccess('當前競賽設置成功!')
setTimeout(() => setSuccess(''), 3000)
} else {
setError('設置當前競賽失敗: ' + data.message)
setTimeout(() => setError(''), 3000)
}
} catch (error) {
console.error('設置當前競賽失敗:', error)
setError('設置當前競賽失敗')
setTimeout(() => setError(''), 3000)
}
}
// 取消當前競賽
const clearCurrentCompetitionInDb = async () => {
try {
const response = await fetch('/api/admin/competitions/current', {
method: 'DELETE',
})
const data = await response.json()
if (data.success) {
setCurrentCompetition(null)
setSuccess('當前競賽已取消!')
setTimeout(() => setSuccess(''), 3000)
} else {
setError('取消當前競賽失敗: ' + data.message)
setTimeout(() => setError(''), 3000)
}
} catch (error) {
console.error('取消當前競賽失敗:', error)
setError('取消當前競賽失敗')
setTimeout(() => setError(''), 3000)
}
}
const fetchCompetitionStats = async () => {
try {
const response = await fetch('/api/admin/competitions/stats')
@@ -630,6 +691,7 @@ export function CompetitionManagement() {
// 組件載入時獲取資料
useEffect(() => {
fetchCompetitions()
fetchCurrentCompetition()
fetchCompetitionStats()
fetchJudges()
fetchJudgeStats()
@@ -1770,22 +1832,50 @@ export function CompetitionManagement() {
if (!selectedCompetitionForAction) return
setIsLoading(true)
await new Promise((resolve) => setTimeout(resolve, 500))
try {
// 調用 API 刪除競賽
const response = await fetch(`/api/admin/competitions/${selectedCompetitionForAction.id}`, {
method: 'DELETE',
})
const data = await response.json()
if (data.success) {
// 同時從 context 中刪除
deleteCompetition(selectedCompetitionForAction.id)
setShowDeleteCompetitionConfirm(false)
setSelectedCompetitionForAction(null)
setSuccess("競賽刪除成功!")
// 重新載入競賽列表
await fetchCompetitions()
} else {
setError("競賽刪除失敗: " + data.message)
}
} catch (error) {
console.error('刪除競賽失敗:', error)
setError("競賽刪除失敗")
} finally {
setIsLoading(false)
setTimeout(() => setError(""), 3000)
setTimeout(() => setSuccess(""), 3000)
}
}
const handleUpdateStatus = async () => {
if (!selectedCompetitionForAction) return
setIsLoading(true)
await new Promise((resolve) => setTimeout(resolve, 500))
try {
// 更新資料庫
const updatedCompetition = await updateCompetitionInDb(selectedCompetitionForAction.id, {
status: newStatus,
})
if (updatedCompetition) {
// 同時更新 context
updateCompetition(selectedCompetitionForAction.id, {
...selectedCompetitionForAction,
status: newStatus,
@@ -1794,8 +1884,16 @@ export function CompetitionManagement() {
setShowChangeStatusDialog(false)
setSelectedCompetitionForAction(null)
setSuccess("競賽狀態更新成功!")
} else {
setError("競賽狀態更新失敗")
}
} catch (error) {
console.error('更新競賽狀態失敗:', error)
setError("競賽狀態更新失敗")
} finally {
setIsLoading(false)
setTimeout(() => setSuccess(""), 3000)
setTimeout(() => setError(""), 3000)
}
}
const getCompetitionTypeIcon = (type: string) => {
@@ -1918,6 +2016,8 @@ export function CompetitionManagement() {
switch (status) {
case "completed":
return "bg-green-100 text-green-800 border-green-200"
case "ongoing":
return "bg-yellow-100 text-yellow-800 border-yellow-200"
case "active":
return "bg-blue-100 text-blue-800 border-blue-200"
case "judging":
@@ -1933,6 +2033,7 @@ export function CompetitionManagement() {
switch (status) {
case "completed":
return "已完成"
case "ongoing":
case "active":
return "進行中"
case "judging":
@@ -2079,7 +2180,7 @@ export function CompetitionManagement() {
{isLoadingDb ? (
<Loader2 className="w-6 h-6 animate-spin" />
) : (
dbStats?.active || displayCompetitions.filter((c) => c.status === "active").length
dbStats?.active || displayCompetitions.filter((c) => c.status === "active" || c.status === "ongoing").length
)}
</p>
</div>
@@ -2245,14 +2346,14 @@ export function CompetitionManagement() {
</DropdownMenuItem>
{!isCurrentCompetition && (
<DropdownMenuItem onClick={() => setCurrentCompetition(competition)}>
<DropdownMenuItem onClick={() => setCurrentCompetitionInDb(competition.id)}>
<Star className="w-4 h-4 mr-2" />
</DropdownMenuItem>
)}
{isCurrentCompetition && (
<DropdownMenuItem onClick={() => setCurrentCompetition(null)}>
<DropdownMenuItem onClick={() => clearCurrentCompetitionInDb()}>
<StarOff className="w-4 h-4 mr-2" />
</DropdownMenuItem>
@@ -5395,9 +5496,9 @@ export function CompetitionManagement() {
<div className="space-y-4">
<div className="space-y-2">
<Label></Label>
<p className="text-sm text-gray-600">
<div className="text-sm text-gray-600">
<Badge>{getStatusText(selectedCompetitionForAction.status)}</Badge>
</p>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="status"></Label>