Files
ai-showcase-platform/contexts/competition-context.tsx
2025-09-18 09:04:47 +08:00

755 lines
23 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 { createContext, useContext, useState, useEffect, type ReactNode } from "react"
import type { Judge, JudgeScore, Competition, Award, Team, Proposal, ProposalJudgeScore } from "@/types/competition"
interface CompetitionContextType {
// Judges
judges: Judge[]
addJudge: (judge: Omit<Judge, "id">) => void
updateJudge: (id: string, updates: Partial<Judge>) => void
deleteJudge: (id: string) => void
// Competitions
competitions: Competition[]
currentCompetition: Competition | null
setCurrentCompetition: (competition: Competition | null) => void
addCompetition: (competition: Omit<Competition, "id">) => void
updateCompetition: (id: string, updates: Partial<Competition>) => void
deleteCompetition: (id: string) => void
// Teams
teams: Team[]
addTeam: (team: Omit<Team, "id">) => void
updateTeam: (id: string, updates: Partial<Team>) => void
getTeamById: (id: string) => Team | undefined
// Proposals
proposals: Proposal[]
addProposal: (proposal: Omit<Proposal, "id">) => void
updateProposal: (id: string, updates: Partial<Proposal>) => void
getProposalById: (id: string) => Proposal | undefined
// Judge Scores
judgeScores: JudgeScore[]
proposalJudgeScores: ProposalJudgeScore[]
addJudgeScore: (score: Omit<JudgeScore, "submittedAt">) => void
addProposalJudgeScore: (score: Omit<ProposalJudgeScore, "submittedAt">) => void
submitJudgeScore: (score: Omit<JudgeScore, "submittedAt">) => void
submitProposalJudgeScore: (score: Omit<ProposalJudgeScore, "submittedAt">) => void
getAppJudgeScores: (appId: string) => JudgeScore[]
getProposalJudgeScores: (proposalId: string) => ProposalJudgeScore[]
getAppAverageScore: (appId: string) => number
getProposalAverageScore: (proposalId: string) => number
getAppDetailedScores: (appId: string) => {
innovation: number
technical: number
usability: number
presentation: number
impact: number
total: number
judgeCount: number
}
getProposalDetailedScores: (proposalId: string) => {
problemIdentification: number
solutionFeasibility: number
innovation: number
impact: number
presentation: number
total: number
judgeCount: number
}
// Awards
awards: Award[]
addAward: (award: Omit<Award, "id">) => void
getAwardsByYear: (year: number) => Award[]
getAwardsByMonth: (year: number, month: number) => Award[]
// Rankings
getCompetitionRankings: (competitionId?: string) => Array<{
appId?: string
proposalId?: string
teamId?: string
appName?: string
proposalTitle?: string
teamName?: string
creator: string
totalScore: number
rank: number
scores: any
competitionType: "individual" | "team" | "proposal"
}>
// Add new filtering functions
getAwardsByCompetitionType: (competitionType?: string) => Award[]
getAwardsByCategory: (category?: string) => Award[]
getTopRankingAwards: () => Award[]
getPopularityAwards: () => Award[]
getPopularityRankings: (competitionId?: string) => Array<{
id: string
name: string
creator: string
department: string
type: string
likes: number
rank: number
}>
}
const CompetitionContext = createContext<CompetitionContextType | undefined>(undefined)
// Mock data
const mockJudges: Judge[] = []
// Mock teams data
const mockTeams: Team[] = []
// Mock proposals data
const mockProposals: Proposal[] = []
// 競賽資料
const mockCompetitions: Competition[] = []
// Mock app likes counter
const appLikesCounter: Record<string, number> = {}
export function CompetitionProvider({ children }: { children: ReactNode }) {
const [judges, setJudges] = useState<Judge[]>(mockJudges)
const [competitions, setCompetitions] = useState<Competition[]>(mockCompetitions)
const [currentCompetition, setCurrentCompetition] = useState<Competition | null>(null)
const [teams, setTeams] = useState<Team[]>(mockTeams)
const [proposals, setProposals] = useState<Proposal[]>(mockProposals)
// 載入所有競賽和當前競賽
useEffect(() => {
const loadCompetitions = async () => {
try {
// 載入所有競賽
const competitionsResponse = await fetch('/api/competitions')
const competitionsData = await competitionsResponse.json()
if (competitionsData.success && competitionsData.data) {
setCompetitions(competitionsData.data)
}
// 載入當前競賽
const currentResponse = await fetch('/api/competitions/current')
const currentData = await currentResponse.json()
if (currentData.success && currentData.data) {
setCurrentCompetition(currentData.data)
}
} catch (error) {
console.error('載入競賽數據失敗:', error)
}
}
loadCompetitions()
}, [])
// Load judge scores from localStorage
const [judgeScores, setJudgeScores] = useState<JudgeScore[]>(() => {
if (typeof window !== "undefined") {
const saved = localStorage.getItem("judgeScores")
if (saved) {
return JSON.parse(saved)
}
}
// 評分資料
const mockScores: JudgeScore[] = []
return mockScores
})
// Load proposal judge scores from localStorage
const [proposalJudgeScores, setProposalJudgeScores] = useState<ProposalJudgeScore[]>(() => {
if (typeof window !== "undefined") {
const saved = localStorage.getItem("proposalJudgeScores")
if (saved) {
return JSON.parse(saved)
}
}
// 提案評分資料
const mockProposalScores: ProposalJudgeScore[] = []
return mockProposalScores
})
// Load awards from localStorage
const [awards, setAwards] = useState<Award[]>(() => {
if (typeof window !== "undefined") {
const saved = localStorage.getItem("competitionAwards")
if (saved) {
return JSON.parse(saved)
}
}
// 獎項資料
const mockAwards = []
return mockAwards
})
// Save to localStorage when data changes
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("judgeScores", JSON.stringify(judgeScores))
}
}, [judgeScores])
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("proposalJudgeScores", JSON.stringify(proposalJudgeScores))
}
}, [proposalJudgeScores])
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("competitionAwards", JSON.stringify(awards))
}
}, [awards])
const addJudge = (judge: Omit<Judge, "id">) => {
const newJudge: Judge = {
...judge,
id: `j${Date.now()}`,
}
setJudges((prev) => [...prev, newJudge])
}
const updateJudge = (id: string, updates: Partial<Judge>) => {
setJudges((prev) => prev.map((judge) => (judge.id === id ? { ...judge, ...updates } : judge)))
}
const deleteJudge = (id: string) => {
console.log('🗑️ Context deleteJudge 被調用ID:', id)
console.log('🗑️ 刪除前的 judges 狀態:', judges.map(j => ({ id: j.id, name: j.name, is_active: j.is_active })))
setJudges((prev) => {
const filtered = prev.filter((judge) => judge.id !== id)
console.log('🗑️ 刪除後的 judges 狀態:', filtered.map(j => ({ id: j.id, name: j.name, is_active: j.is_active })))
return filtered
})
setCompetitions((prev) =>
prev.map((comp) => ({
...comp,
judges: comp.judges.filter((judgeId) => judgeId !== id),
})),
)
setJudgeScores((prev) => prev.filter((score) => score.judgeId !== id))
setProposalJudgeScores((prev) => prev.filter((score) => score.judgeId !== id))
}
const addCompetition = (competition: Omit<Competition, "id">) => {
const newCompetition: Competition = {
...competition,
id: `c${Date.now()}`,
}
setCompetitions((prev) => [...prev, newCompetition])
}
const updateCompetition = (id: string, updates: Partial<Competition>) => {
setCompetitions((prev) => prev.map((comp) => (comp.id === id ? { ...comp, ...updates } : comp)))
if (currentCompetition?.id === id) {
setCurrentCompetition((prev) => (prev ? { ...prev, ...updates } : null))
}
}
const deleteCompetition = (id: string) => {
setCompetitions((prev) => prev.filter((comp) => comp.id !== id))
if (currentCompetition?.id === id) {
setCurrentCompetition(null)
}
}
const addTeam = (team: Omit<Team, "id">) => {
const newTeam: Team = {
...team,
id: `t${Date.now()}`,
}
setTeams((prev) => [...prev, newTeam])
}
const updateTeam = (id: string, updates: Partial<Team>) => {
setTeams((prev) => prev.map((team) => (team.id === id ? { ...team, ...updates } : team)))
}
const getTeamById = (id: string): Team | undefined => {
return teams.find((team) => team.id === id)
}
const addProposal = (proposal: Omit<Proposal, "id">) => {
const newProposal: Proposal = {
...proposal,
id: `p${Date.now()}`,
}
setProposals((prev) => [...prev, newProposal])
}
const updateProposal = (id: string, updates: Partial<Proposal>) => {
setProposals((prev) => prev.map((proposal) => (proposal.id === id ? { ...proposal, ...updates } : proposal)))
}
const getProposalById = (id: string): Proposal | undefined => {
return proposals.find((proposal) => proposal.id === id)
}
const addJudgeScore = (score: Omit<JudgeScore, "submittedAt">) => {
const newScore: JudgeScore = {
...score,
submittedAt: new Date().toISOString(),
}
const filteredScores = judgeScores.filter((s) => !(s.judgeId === score.judgeId && s.appId === score.appId))
setJudgeScores([...filteredScores, newScore])
}
const addProposalJudgeScore = (score: Omit<ProposalJudgeScore, "submittedAt">) => {
const newScore: ProposalJudgeScore = {
...score,
submittedAt: new Date().toISOString(),
}
const filteredScores = proposalJudgeScores.filter(
(s) => !(s.judgeId === score.judgeId && s.proposalId === score.proposalId),
)
setProposalJudgeScores([...filteredScores, newScore])
}
const submitJudgeScore = (score: Omit<JudgeScore, "submittedAt">) => {
addJudgeScore(score)
}
const submitProposalJudgeScore = (score: Omit<ProposalJudgeScore, "submittedAt">) => {
addProposalJudgeScore(score)
}
const getAppJudgeScores = (appId: string): JudgeScore[] => {
return judgeScores.filter((score) => score.appId === appId)
}
const getProposalJudgeScores = (proposalId: string): ProposalJudgeScore[] => {
return proposalJudgeScores.filter((score) => score.proposalId === proposalId)
}
const getAppAverageScore = (appId: string): number => {
const scores = getAppJudgeScores(appId)
if (scores.length === 0) return 0
const totalScore = scores.reduce((sum, score) => {
const scoreSum = Object.values(score.scores).reduce((a, b) => a + b, 0)
return sum + scoreSum
}, 0)
return Number((totalScore / (scores.length * 5)).toFixed(1))
}
const getProposalAverageScore = (proposalId: string): number => {
const scores = getProposalJudgeScores(proposalId)
if (scores.length === 0) return 0
const totalScore = scores.reduce((sum, score) => {
const scoreSum = Object.values(score.scores).reduce((a, b) => a + b, 0)
return sum + scoreSum
}, 0)
return Number((totalScore / (scores.length * 5)).toFixed(1))
}
const getAppDetailedScores = (appId: string) => {
const scores = getAppJudgeScores(appId)
if (scores.length === 0) {
return {
innovation: 0,
technical: 0,
usability: 0,
presentation: 0,
impact: 0,
total: 0,
judgeCount: 0,
}
}
const totals = scores.reduce(
(acc, score) => ({
innovation: acc.innovation + score.scores.innovation,
technical: acc.technical + score.scores.technical,
usability: acc.usability + score.scores.usability,
presentation: acc.presentation + score.scores.presentation,
impact: acc.impact + score.scores.impact,
}),
{ innovation: 0, technical: 0, usability: 0, presentation: 0, impact: 0 },
)
const judgeCount = scores.length
const averages = {
innovation: Number((totals.innovation / judgeCount).toFixed(1)),
technical: Number((totals.technical / judgeCount).toFixed(1)),
usability: Number((totals.usability / judgeCount).toFixed(1)),
presentation: Number((totals.presentation / judgeCount).toFixed(1)),
impact: Number((totals.impact / judgeCount).toFixed(1)),
}
const total = Number(
(
(totals.innovation + totals.technical + totals.usability + totals.presentation + totals.impact) /
(judgeCount * 5)
).toFixed(1),
)
return {
...averages,
total,
judgeCount,
}
}
const getProposalDetailedScores = (proposalId: string) => {
const scores = getProposalJudgeScores(proposalId)
if (scores.length === 0) {
return {
problemIdentification: 0,
solutionFeasibility: 0,
innovation: 0,
impact: 0,
presentation: 0,
total: 0,
judgeCount: 0,
}
}
const totals = scores.reduce(
(acc, score) => ({
problemIdentification: acc.problemIdentification + score.scores.problemIdentification,
solutionFeasibility: acc.solutionFeasibility + score.scores.solutionFeasibility,
innovation: acc.innovation + score.scores.innovation,
impact: acc.impact + score.scores.impact,
presentation: acc.presentation + score.scores.presentation,
}),
{ problemIdentification: 0, solutionFeasibility: 0, innovation: 0, impact: 0, presentation: 0 },
)
const judgeCount = scores.length
const averages = {
problemIdentification: Number((totals.problemIdentification / judgeCount).toFixed(1)),
solutionFeasibility: Number((totals.solutionFeasibility / judgeCount).toFixed(1)),
innovation: Number((totals.innovation / judgeCount).toFixed(1)),
impact: Number((totals.impact / judgeCount).toFixed(1)),
presentation: Number((totals.presentation / judgeCount).toFixed(1)),
}
const total = Number(
(
(totals.problemIdentification +
totals.solutionFeasibility +
totals.innovation +
totals.impact +
totals.presentation) /
(judgeCount * 5)
).toFixed(1),
)
return {
...averages,
total,
judgeCount,
}
}
const addAward = (award: Omit<Award, "id">) => {
const newAward: Award = {
...award,
id: `a${Date.now()}`,
}
setAwards((prev) => [...prev, newAward])
}
const getAwardsByYear = (year: number): Award[] => {
return awards.filter((award) => award.year === year)
}
const getAwardsByMonth = (year: number, month: number): Award[] => {
return awards.filter((award) => award.year === year && award.month === month)
}
const getCompetitionRankings = (competitionId?: string) => {
const targetCompetition = competitionId ? competitions.find((c) => c.id === competitionId) : currentCompetition
if (!targetCompetition) return []
const rankings: any[] = []
// Handle individual competitions
if (targetCompetition.type === "individual" || targetCompetition.type === "mixed") {
targetCompetition.participatingApps.forEach((appId) => {
const detailedScores = getAppDetailedScores(appId)
const appNames: Record<string, string> = {
"1": "智能對話助手",
"2": "圖像生成工具",
"3": "語音識別系統",
"4": "智能推薦引擎",
"5": "文本分析器",
"6": "AI創意寫作",
}
const creators: Record<string, string> = {
"1": "張小明",
"2": "李美華",
"3": "王大偉",
"4": "陳小芳",
"5": "劉志強",
"6": "黃小玲",
}
rankings.push({
appId,
appName: appNames[appId] || `應用 ${appId}`,
creator: creators[appId] || "未知",
totalScore: detailedScores.total,
scores: {
innovation: detailedScores.innovation,
technical: detailedScores.technical,
usability: detailedScores.usability,
presentation: detailedScores.presentation,
impact: detailedScores.impact,
},
rank: 0,
competitionType: "individual" as const,
})
})
}
// Handle team competitions
if (targetCompetition.type === "team" || targetCompetition.type === "mixed") {
targetCompetition.participatingTeams.forEach((teamId) => {
const team = getTeamById(teamId)
if (team) {
// Calculate team score based on their apps
let totalScore = 0
let appCount = 0
team.apps.forEach((appId) => {
const appScore = getAppDetailedScores(appId)
totalScore += appScore.total
appCount++
})
const averageScore = appCount > 0 ? totalScore / appCount : 0
rankings.push({
teamId,
teamName: team.name,
creator: `${team.members.find((m) => m.id === team.leader)?.name}團隊`,
totalScore: averageScore,
scores: {
teamwork: averageScore * 0.25,
technical: averageScore * 0.25,
innovation: averageScore * 0.25,
practical: averageScore * 0.25,
},
rank: 0,
competitionType: "team" as const,
appCount: team.apps.length,
totalLikes: team.totalLikes,
members: team.members,
})
}
})
}
// Handle proposal competitions
if (targetCompetition.type === "proposal" || targetCompetition.type === "mixed") {
targetCompetition.participatingProposals.forEach((proposalId) => {
const proposal = getProposalById(proposalId)
const detailedScores = getProposalDetailedScores(proposalId)
if (proposal) {
const team = getTeamById(proposal.teamId)
rankings.push({
proposalId,
proposalTitle: proposal.title,
creator: team?.name || "未知團隊",
totalScore: detailedScores.total,
scores: {
problemIdentification: detailedScores.problemIdentification,
solutionFeasibility: detailedScores.solutionFeasibility,
innovation: detailedScores.innovation,
impact: detailedScores.impact,
presentation: detailedScores.presentation,
},
rank: 0,
competitionType: "proposal" as const,
proposal,
team,
})
}
})
}
// Sort by total score and assign ranks within each competition type
const individualRankings = rankings
.filter((r) => r.competitionType === "individual")
.sort((a, b) => b.totalScore - a.totalScore)
.map((item, index) => ({ ...item, rank: index + 1 }))
const teamRankings = rankings
.filter((r) => r.competitionType === "team")
.sort((a, b) => b.totalScore - a.totalScore)
.map((item, index) => ({ ...item, rank: index + 1 }))
const proposalRankings = rankings
.filter((r) => r.competitionType === "proposal")
.sort((a, b) => b.totalScore - a.totalScore)
.map((item, index) => ({ ...item, rank: index + 1 }))
// Combine all rankings
return [...individualRankings, ...teamRankings, ...proposalRankings]
}
// Add new filtering functions
const getAwardsByCompetitionType = (competitionType?: string) => {
if (!competitionType || competitionType === "all") return awards
return awards.filter((award) => award.competitionType === competitionType)
}
const getAwardsByCategory = (category?: string) => {
if (!category || category === "all") return awards
return awards.filter((award) => award.category === category)
}
const getTopRankingAwards = () => {
return awards.filter((award) => award.rank > 0 && award.rank <= 3)
}
const getPopularityAwards = () => {
return awards.filter((award) => award.awardType === "popular")
}
const getPopularityRankings = (competitionId?: string) => {
const targetCompetition = competitionId ? competitions.find((c) => c.id === competitionId) : currentCompetition
if (!targetCompetition) return []
const rankings: any[] = []
// Get participants based on competition type
const participants =
targetCompetition.type === "individual"
? targetCompetition.participatingApps
: targetCompetition.type === "team"
? targetCompetition.participatingTeams
: targetCompetition.participatingProposals
// For each participant, calculate popularity based on likes
participants.forEach((participantId) => {
const appNames: Record<string, string> = {
"1": "智能對話助手",
"2": "圖像生成工具",
"3": "語音識別系統",
"4": "智能推薦引擎",
"5": "文本分析器",
"6": "AI創意寫作",
}
const creators: Record<string, string> = {
"1": "張小明",
"2": "李美華",
"3": "王大偉",
"4": "陳小芳",
"5": "劉志強",
"6": "黃小玲",
}
const departments: Record<string, string> = {
"1": "HQBU",
"2": "ITBU",
"3": "MBU1",
"4": "SBU",
"5": "HQBU",
"6": "ITBU",
}
const types: Record<string, string> = {
"1": "文字處理",
"2": "圖像生成",
"3": "語音辨識",
"4": "推薦系統",
"5": "文字處理",
"6": "文字處理",
}
// Get likes from appLikesCounter
const likes = appLikesCounter[participantId] || 0
rankings.push({
id: participantId,
name: appNames[participantId] || `應用 ${participantId}`,
creator: creators[participantId] || "未知",
department: departments[participantId] || "未知",
type: types[participantId] || "未知",
likes: likes,
rank: 0, // Will be calculated after sorting
})
})
// Sort by likes and assign ranks
return rankings.sort((a, b) => b.likes - a.likes).map((item, index) => ({ ...item, rank: index + 1 }))
}
// Add these functions to the context value
return (
<CompetitionContext.Provider
value={{
judges,
addJudge,
updateJudge,
deleteJudge,
competitions,
currentCompetition,
setCurrentCompetition,
addCompetition,
updateCompetition,
deleteCompetition,
teams,
addTeam,
updateTeam,
getTeamById,
proposals,
addProposal,
updateProposal,
getProposalById,
judgeScores,
proposalJudgeScores,
addJudgeScore,
addProposalJudgeScore,
submitJudgeScore,
submitProposalJudgeScore,
getAppJudgeScores,
getProposalJudgeScores,
getAppAverageScore,
getProposalAverageScore,
getAppDetailedScores,
getProposalDetailedScores,
awards,
addAward,
getAwardsByYear,
getAwardsByMonth,
getCompetitionRankings,
getAwardsByCompetitionType,
getAwardsByCategory,
getTopRankingAwards,
getPopularityAwards,
getPopularityRankings,
}}
>
{children}
</CompetitionContext.Provider>
)
}
export function useCompetition() {
const context = useContext(CompetitionContext)
if (context === undefined) {
throw new Error("useCompetition must be used within a CompetitionProvider")
}
return context
}