新增競賽前台呈現、刪除競賽、修改競賽狀態
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
import { useCompetition } from "@/contexts/competition-context"
|
||||
import {
|
||||
@@ -55,10 +55,67 @@ export function PopularityRankings() {
|
||||
const [individualCurrentPage, setIndividualCurrentPage] = useState(0)
|
||||
const [teamCurrentPage, setTeamCurrentPage] = useState(0)
|
||||
|
||||
// 新增狀態
|
||||
const [competitionApps, setCompetitionApps] = useState<any[]>([])
|
||||
const [competitionTeams, setCompetitionTeams] = useState<any[]>([])
|
||||
const [competitionJudges, setCompetitionJudges] = useState<any[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const ITEMS_PER_PAGE = 3
|
||||
|
||||
// 載入當前競賽的數據
|
||||
useEffect(() => {
|
||||
if (currentCompetition) {
|
||||
loadCompetitionData(currentCompetition.id)
|
||||
} else {
|
||||
// 如果沒有當前競賽,清空數據
|
||||
setCompetitionApps([])
|
||||
setCompetitionTeams([])
|
||||
setCompetitionJudges([])
|
||||
}
|
||||
}, [currentCompetition])
|
||||
|
||||
const loadCompetitionData = async (competitionId: string) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// 並行載入競賽的應用、團隊和評審數據
|
||||
const [appsResponse, teamsResponse, judgesResponse] = await Promise.all([
|
||||
fetch(`/api/competitions/${competitionId}/apps`), // 移除 competitionType 參數,載入所有應用
|
||||
fetch(`/api/competitions/${competitionId}/teams`),
|
||||
fetch(`/api/competitions/${competitionId}/judges`)
|
||||
])
|
||||
|
||||
const [appsData, teamsData, judgesData] = await Promise.all([
|
||||
appsResponse.json(),
|
||||
teamsResponse.json(),
|
||||
judgesResponse.json()
|
||||
])
|
||||
|
||||
if (appsData.success) {
|
||||
// 合併個人應用和團隊應用
|
||||
const allApps = appsData.data.apps || []
|
||||
console.log('📱 載入的應用數據:', allApps)
|
||||
setCompetitionApps(allApps)
|
||||
}
|
||||
if (teamsData.success) {
|
||||
const teams = teamsData.data.teams || []
|
||||
console.log('👥 載入的團隊數據:', teams)
|
||||
setCompetitionTeams(teams)
|
||||
}
|
||||
if (judgesData.success) {
|
||||
const judges = judgesData.data.judges || []
|
||||
console.log('👨⚖️ 載入的評審數據:', judges)
|
||||
setCompetitionJudges(judges)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('載入競賽數據失敗:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter apps based on search criteria
|
||||
const filteredApps = aiApps.filter((app) => {
|
||||
const filteredApps = competitionApps.filter((app) => {
|
||||
const matchesSearch =
|
||||
app.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
app.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
@@ -72,9 +129,7 @@ export function PopularityRankings() {
|
||||
|
||||
// Sort apps by like count (popularity) and group by competition type
|
||||
const sortedApps = filteredApps.sort((a, b) => {
|
||||
const likesA = getLikeCount(a.id.toString())
|
||||
const likesB = getLikeCount(b.id.toString())
|
||||
return likesB - likesA
|
||||
return (b.likes || 0) - (a.likes || 0)
|
||||
})
|
||||
|
||||
// Group apps by competition type
|
||||
@@ -319,23 +374,8 @@ export function PopularityRankings() {
|
||||
return null
|
||||
}
|
||||
|
||||
// Calculate team popularity score: total apps × highest like count
|
||||
const teamsWithScores = teams
|
||||
.map((team) => {
|
||||
const appLikes = team.apps.map((appId: string) => getLikeCount(appId))
|
||||
const maxLikes = Math.max(...appLikes, 0)
|
||||
const totalApps = team.apps.length
|
||||
const popularityScore = totalApps * maxLikes
|
||||
|
||||
return {
|
||||
...team,
|
||||
popularityScore,
|
||||
maxLikes,
|
||||
totalApps,
|
||||
totalViews: team.apps.reduce((sum: number, appId: string) => sum + getViewCount(appId), 0),
|
||||
}
|
||||
})
|
||||
.sort((a, b) => b.popularityScore - a.popularityScore)
|
||||
// 團隊已經從 API 獲取了人氣分數,直接使用
|
||||
const teamsWithScores = teams.sort((a, b) => b.popularityScore - a.popularityScore)
|
||||
|
||||
const currentPage = teamCurrentPage
|
||||
const setCurrentPage = setTeamCurrentPage
|
||||
@@ -512,7 +552,7 @@ export function PopularityRankings() {
|
||||
<div className="flex items-center space-x-2">
|
||||
<ThumbsUp className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm font-medium">
|
||||
{team.apps.reduce((sum: number, appId: string) => sum + getLikeCount(appId), 0)} 總按讚
|
||||
{team.totalLikes || 0} 總按讚
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
@@ -593,7 +633,11 @@ export function PopularityRankings() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Users className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-sm">{filteredApps.length} 個參賽應用</span>
|
||||
<span className="text-sm">
|
||||
{currentCompetition.type === 'team'
|
||||
? `${competitionTeams.length} 個參賽團隊`
|
||||
: `${filteredApps.length} 個參賽應用`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge
|
||||
@@ -633,27 +677,39 @@ export function PopularityRankings() {
|
||||
<CardDescription>專業評審團隊</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{judges.map((judge) => (
|
||||
<div key={judge.id} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||
<Avatar>
|
||||
<AvatarImage src={judge.avatar || "/placeholder.svg"} />
|
||||
<AvatarFallback className="bg-purple-100 text-purple-700">{judge.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{judge.name}</h4>
|
||||
<p className="text-sm text-gray-600">{judge.title}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{judge.expertise.slice(0, 2).map((skill) => (
|
||||
<Badge key={skill} variant="secondary" className="text-xs">
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
{isLoading ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto"></div>
|
||||
<p className="mt-2 text-gray-600">載入評審團中...</p>
|
||||
</div>
|
||||
) : competitionJudges.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{competitionJudges.map((judge) => (
|
||||
<div key={judge.id} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||
<Avatar>
|
||||
<AvatarImage src={judge.avatar || "/placeholder.svg"} />
|
||||
<AvatarFallback className="bg-purple-100 text-purple-700">{judge.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{judge.name}</h4>
|
||||
<p className="text-sm text-gray-600">{judge.title}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{judge.expertise.slice(0, 2).map((skill) => (
|
||||
<Badge key={skill} variant="secondary" className="text-xs">
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<Crown className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p>暫無評審團成員</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -733,23 +789,30 @@ export function PopularityRankings() {
|
||||
|
||||
{/* Team Competition Section */}
|
||||
{(selectedCompetitionType === "all" || selectedCompetitionType === "team") &&
|
||||
currentCompetition?.type === "team" &&
|
||||
renderTeamCompetitionSection(
|
||||
mockTeams.filter(
|
||||
(team) =>
|
||||
team.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
team.members.some((member: any) => member.name.toLowerCase().includes(searchTerm.toLowerCase())) ||
|
||||
selectedDepartment === "all" ||
|
||||
team.department === selectedDepartment,
|
||||
competitionTeams.filter(
|
||||
(team) => {
|
||||
const matchesSearch = searchTerm === "" ||
|
||||
team.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
team.members?.some((member: any) => member.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
|
||||
const matchesDepartment = selectedDepartment === "all" || team.department === selectedDepartment;
|
||||
|
||||
return matchesSearch && matchesDepartment;
|
||||
}
|
||||
),
|
||||
"團隊賽",
|
||||
)}
|
||||
|
||||
{/* No Results */}
|
||||
{filteredApps.length === 0 && (
|
||||
{filteredApps.length === 0 && competitionTeams.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="text-center py-12">
|
||||
<Heart className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-semibold text-gray-600 mb-2">沒有找到符合條件的應用</h3>
|
||||
<h3 className="text-xl font-semibold text-gray-600 mb-2">
|
||||
{currentCompetition?.type === 'team' ? '沒有找到符合條件的團隊' : '沒有找到符合條件的應用'}
|
||||
</h3>
|
||||
<p className="text-gray-500">請調整篩選條件或清除搜尋關鍵字</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
Reference in New Issue
Block a user