Files
ai-showcase-platform/app/competition/page.tsx

574 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import { useAuth } from "@/contexts/auth-context"
import { useCompetition } from "@/contexts/competition-context"
import { Trophy, Award, Medal, Target, Users, Lightbulb, ArrowLeft, Plus, Search, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Input } from "@/components/ui/input"
import { PopularityRankings } from "@/components/competition/popularity-rankings"
import { CompetitionDetailDialog } from "@/components/competition/competition-detail-dialog"
import { AwardDetailDialog } from "@/components/competition/award-detail-dialog"
export default function CompetitionPage() {
const { user, canAccessAdmin } = useAuth()
const { competitions, awards, getAwardsByYear, getCompetitionRankings, currentCompetition, setCurrentCompetition } = useCompetition()
const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all")
const [selectedMonthFilter, setSelectedMonthFilter] = useState("all")
const [selectedAwardCategory, setSelectedAwardCategory] = useState("all")
const [selectedYear, setSelectedYear] = useState(2024)
const [searchQuery, setSearchQuery] = useState("")
const [showCompetitionDetail, setShowCompetitionDetail] = useState(false)
const [selectedRanking, setSelectedRanking] = useState<any>(null)
const [selectedCompetitionType, setSelectedCompetitionType] = useState<"individual" | "team" | "proposal">(
"individual",
)
const [showAwardDetail, setShowAwardDetail] = useState(false)
const [selectedAward, setSelectedAward] = useState<any>(null)
const getCompetitionTypeIcon = (type: string) => {
switch (type) {
case "individual":
return <Target className="w-4 h-4" />
case "team":
return <Users className="w-4 h-4" />
case "proposal":
return <Lightbulb className="w-4 h-4" />
case "mixed":
return <Trophy className="w-4 h-4" />
default:
return <Trophy className="w-4 h-4" />
}
}
const getCompetitionTypeText = (type: string) => {
switch (type) {
case "individual":
return "個人賽"
case "team":
return "團隊賽"
case "proposal":
return "提案賽"
case "mixed":
return "混合賽"
default:
return "競賽"
}
}
const getCompetitionTypeColor = (type: string) => {
switch (type) {
case "individual":
return "bg-blue-100 text-blue-800 border-blue-200"
case "team":
return "bg-green-100 text-green-800 border-green-200"
case "proposal":
return "bg-purple-100 text-purple-800 border-purple-200"
case "mixed":
return "bg-gradient-to-r from-blue-100 via-green-100 to-purple-100 text-gray-800 border-gray-200"
default:
return "bg-gray-100 text-gray-800 border-gray-200"
}
}
const handleShowCompetitionDetail = (ranking: any, type: "individual" | "team" | "proposal") => {
setSelectedRanking(ranking)
setSelectedCompetitionType(type)
setShowCompetitionDetail(true)
}
const handleShowAwardDetail = (award: any) => {
setSelectedAward(award)
setShowAwardDetail(true)
}
const getFilteredAwards = () => {
let filteredAwards = getAwardsByYear(selectedYear)
// 搜索功能 - 按应用名称、创作者或奖项名称搜索
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase().trim()
filteredAwards = filteredAwards.filter((award) => {
return (
award.appName?.toLowerCase().includes(query) ||
award.creator?.toLowerCase().includes(query) ||
award.awardName?.toLowerCase().includes(query)
)
})
}
if (selectedCompetitionTypeFilter !== "all") {
filteredAwards = filteredAwards.filter((award) => award.competitionType === selectedCompetitionTypeFilter)
}
if (selectedMonthFilter !== "all") {
filteredAwards = filteredAwards.filter((award) => award.month === Number.parseInt(selectedMonthFilter))
}
if (selectedAwardCategory !== "all") {
if (selectedAwardCategory === "ranking") {
filteredAwards = filteredAwards.filter((award) => award.rank > 0 && award.rank <= 3)
} else if (selectedAwardCategory === "popular") {
filteredAwards = filteredAwards.filter((award) => award.awardType === "popular")
} else {
filteredAwards = filteredAwards.filter((award) => award.category === selectedAwardCategory)
}
}
return filteredAwards.sort((a, b) => {
// Sort by month first, then by rank
if (a.month !== b.month) return b.month - a.month
if (a.rank !== b.rank) {
if (a.rank === 0) return 1
if (b.rank === 0) return -1
return a.rank - b.rank
}
return 0
})
}
const filteredAwards = getFilteredAwards()
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50">
{/* Header */}
<header className="bg-white/80 backdrop-blur-sm border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
<Button
variant="ghost"
size="sm"
onClick={() => window.history.back()}
className="text-gray-700 hover:text-blue-600 hover:bg-blue-50"
>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
<div className="flex items-center space-x-2">
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
<Trophy className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="text-xl font-bold text-gray-900"></h1>
<p className="text-xs text-gray-500">COMPETITION CENTER</p>
</div>
</div>
</div>
<div className="flex items-center space-x-4">
</div>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Hero Section */}
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4">AI </h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-8">
AI 耀
</p>
</div>
<Tabs defaultValue="rankings" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-8">
<TabsTrigger value="rankings" className="flex items-center space-x-2">
<Trophy className="w-4 h-4" />
<span></span>
</TabsTrigger>
<TabsTrigger value="awards" className="flex items-center space-x-2">
<Award className="w-4 h-4" />
<span></span>
</TabsTrigger>
</TabsList>
<TabsContent value="rankings">
<PopularityRankings />
</TabsContent>
<TabsContent value="awards">
<div className="space-y-8">
{/* Enhanced Filter Section */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center space-x-2">
<Medal className="w-5 h-5 text-purple-500" />
<span></span>
</CardTitle>
<div className="flex items-center space-x-3">
<Select
value={selectedYear.toString()}
onValueChange={(value) => setSelectedYear(Number.parseInt(value))}
>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2024">2024</SelectItem>
<SelectItem value="2023">2023</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-gray-600"> {selectedYear} </p>
{searchQuery && (
<div className="text-sm text-blue-600 bg-blue-50 px-3 py-1 rounded-full">
{searchQuery}
</div>
)}
</div>
{/* Search and Filter Controls */}
<div className="space-y-4">
{/* Search Bar */}
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-4 w-4 text-gray-400" />
</div>
<Input
type="text"
placeholder="搜尋應用名稱、創作者或獎項名稱..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 pr-10 w-full md:w-96"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600"
>
<X className="h-4 w-4" />
</button>
)}
</div>
{/* Filter Controls */}
<div className="flex flex-wrap gap-4 items-center">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedCompetitionTypeFilter} onValueChange={setSelectedCompetitionTypeFilter}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="individual"></SelectItem>
<SelectItem value="team"></SelectItem>
<SelectItem value="proposal"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedMonthFilter} onValueChange={setSelectedMonthFilter}>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="1">1</SelectItem>
<SelectItem value="2">2</SelectItem>
<SelectItem value="3">3</SelectItem>
<SelectItem value="4">4</SelectItem>
<SelectItem value="5">5</SelectItem>
<SelectItem value="6">6</SelectItem>
<SelectItem value="7">7</SelectItem>
<SelectItem value="8">8</SelectItem>
<SelectItem value="9">9</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="11">11</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700"></span>
<Select value={selectedAwardCategory} onValueChange={setSelectedAwardCategory}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="ranking"></SelectItem>
<SelectItem value="popular"></SelectItem>
<SelectItem value="innovation"></SelectItem>
<SelectItem value="technical"></SelectItem>
<SelectItem value="practical"></SelectItem>
</SelectContent>
</Select>
</div>
{/* Clear Filters Button */}
{(searchQuery || selectedCompetitionTypeFilter !== "all" || selectedMonthFilter !== "all" || selectedAwardCategory !== "all") && (
<div className="flex items-center">
<Button
variant="outline"
size="sm"
onClick={() => {
setSearchQuery("")
setSelectedCompetitionTypeFilter("all")
setSelectedMonthFilter("all")
setSelectedAwardCategory("all")
}}
className="text-gray-600 hover:text-gray-800"
>
<X className="w-4 h-4 mr-1" />
</Button>
</div>
)}
</div>
</div>
{/* Statistics */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-4">
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="text-lg font-bold text-blue-600">{filteredAwards.length}</div>
<div className="text-xs text-blue-600"></div>
</div>
<div className="text-center p-3 bg-yellow-50 rounded-lg">
<div className="text-lg font-bold text-yellow-600">
{filteredAwards.filter((a) => a.rank > 0 && a.rank <= 3).length}
</div>
<div className="text-xs text-yellow-600"></div>
</div>
<div className="text-center p-3 bg-red-50 rounded-lg">
<div className="text-lg font-bold text-red-600">
{filteredAwards.filter((a) => a.awardType === "popular").length}
</div>
<div className="text-xs text-red-600"></div>
</div>
<div className="text-center p-3 bg-green-50 rounded-lg">
<div className="text-lg font-bold text-green-600">
{new Set(filteredAwards.map((a) => `${a.year}-${a.month}`)).size}
</div>
<div className="text-xs text-green-600"></div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Awards Grid with Enhanced Display */}
{filteredAwards.length > 0 ? (
<div className="space-y-8">
{/* Group awards by month */}
{Array.from(new Set(filteredAwards.map((award) => award.month)))
.sort((a, b) => b - a)
.map((month) => {
const monthAwards = filteredAwards.filter((award) => award.month === month)
const competition = competitions.find((c) => c.month === month && c.year === selectedYear)
return (
<div key={month} className="space-y-4">
<div className="flex items-center space-x-4">
<h3 className="text-xl font-bold text-gray-900">
{selectedYear}{month}
</h3>
{competition && (
<Badge variant="outline" className={getCompetitionTypeColor(competition.type)}>
{getCompetitionTypeIcon(competition.type)}
<span className="ml-1">{getCompetitionTypeText(competition.type)}</span>
</Badge>
)}
<Badge variant="secondary" className="bg-gray-100 text-gray-700">
{monthAwards.length}
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{monthAwards.map((award) => (
<Card
key={award.id}
className="relative overflow-hidden border-0 shadow-lg bg-gradient-to-br from-white to-gray-50 hover:shadow-xl transition-shadow cursor-pointer"
onClick={() => handleShowAwardDetail(award)}
>
{/* Rank Badge */}
{award.rank > 0 && award.rank <= 3 && (
<div className="absolute top-2 left-2 z-10">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold text-white ${
award.rank === 1
? "bg-yellow-500"
: award.rank === 2
? "bg-gray-400"
: award.rank === 3
? "bg-orange-600"
: ""
}`}
>
{award.rank}
</div>
</div>
)}
<div className="absolute top-4 right-4 text-3xl">{award.icon}</div>
<CardHeader className="pb-3 pt-12">
<div className="space-y-2">
<div className="flex flex-wrap gap-2">
<Badge
variant="secondary"
className={`w-fit ${
award.awardType === "popular"
? "bg-red-100 text-red-800 border-red-200"
: award.rank === 1
? "bg-yellow-100 text-yellow-800 border-yellow-200"
: award.rank === 2
? "bg-gray-100 text-gray-800 border-gray-200"
: award.rank === 3
? "bg-orange-100 text-orange-800 border-orange-200"
: "bg-blue-100 text-blue-800 border-blue-200"
}`}
>
{award.awardName}
</Badge>
<Badge
variant="outline"
className={getCompetitionTypeColor(award.competitionType)}
>
{getCompetitionTypeIcon(award.competitionType)}
<span className="ml-1">{getCompetitionTypeText(award.competitionType)}</span>
</Badge>
</div>
<CardTitle className="text-lg line-clamp-2">
{award.appName || award.proposalTitle || award.teamName}
</CardTitle>
<p className="text-sm text-gray-500">by {award.creator}</p>
<div className="text-xs text-gray-400">
{award.year}{award.month}
</div>
</div>
</CardHeader>
<CardContent className="pt-0">
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">
{award.competitionType === "proposal"
? "評審評分"
: award.awardType === "popular"
? award.competitionType === "team"
? "人氣指數"
: "收藏數"
: "評審評分"}
</span>
<span className="font-bold text-lg text-gray-900">
{award.awardType === "popular" && award.competitionType === "team"
? `${award.score}`
: award.awardType === "popular"
? `${award.score}`
: award.score}
</span>
</div>
<Button
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
onClick={(e) => {
e.stopPropagation()
handleShowAwardDetail(award)
}}
>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)
})}
</div>
) : (
<Card>
<CardContent className="text-center py-12">
<div className="space-y-4">
{searchQuery ? (
<Search className="w-16 h-16 text-gray-400 mx-auto" />
) : (
<Medal className="w-16 h-16 text-gray-400 mx-auto" />
)}
<div>
<h3 className="text-xl font-semibold text-gray-600 mb-2">
{searchQuery ? (
<>{searchQuery}</>
) : (
<>
{selectedYear}{selectedMonthFilter !== "all" ? `${selectedMonthFilter}` : ""}
</>
)}
</h3>
<p className="text-gray-500">
{searchQuery
? "嘗試使用其他關鍵字或調整篩選條件"
: "請調整篩選條件查看其他得獎作品"}
</p>
</div>
<div className="flex justify-center gap-2">
<Button
variant="outline"
className="bg-transparent"
onClick={() => {
setSearchQuery("")
setSelectedCompetitionTypeFilter("all")
setSelectedMonthFilter("all")
setSelectedAwardCategory("all")
}}
>
<X className="w-4 h-4 mr-1" />
</Button>
{searchQuery && (
<Button
variant="outline"
className="bg-transparent"
onClick={() => setSearchQuery("")}
>
</Button>
)}
</div>
</div>
</CardContent>
</Card>
)}
</div>
</TabsContent>
</Tabs>
{/* Competition Detail Dialog */}
{selectedRanking && (
<CompetitionDetailDialog
open={showCompetitionDetail}
onOpenChange={setShowCompetitionDetail}
ranking={selectedRanking}
competitionType={selectedCompetitionType}
/>
)}
{/* Award Detail Dialog */}
{selectedAward && (
<AwardDetailDialog open={showAwardDetail} onOpenChange={setShowAwardDetail} award={selectedAward} />
)}
</div>
</div>
)
}