建立檔案

This commit is contained in:
2025-08-05 08:22:44 +08:00
commit 042d03aff7
122 changed files with 34763 additions and 0 deletions

View File

@@ -0,0 +1,857 @@
"use client"
import { useState, useEffect } from "react"
import { useCompetition } from "@/contexts/competition-context"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Progress } from "@/components/ui/progress"
import { ScoringLinkDialog } from "./scoring-link-dialog"
import { JudgeListDialog } from "./judge-list-dialog"
import {
Trophy, Plus, Edit, CheckCircle, AlertTriangle, ClipboardList, User, Users, Search, Loader2, BarChart3, ChevronLeft, ChevronRight, Link
} from "lucide-react"
interface ScoringRecord {
id: string
judgeId: string
judgeName: string
participantId: string
participantName: string
participantType: "individual" | "team"
scores: Record<string, number>
totalScore: number
comments: string
submittedAt: string
status: "completed" | "pending" | "draft"
}
const mockIndividualApps: any[] = []
const initialTeams: any[] = []
export function ScoringManagement() {
const { competitions, judges, judgeScores, submitJudgeScore } = useCompetition()
const [selectedCompetition, setSelectedCompetition] = useState<any>(null)
const [scoringRecords, setScoringRecords] = useState<ScoringRecord[]>([])
const [showManualScoring, setShowManualScoring] = useState(false)
const [showEditScoring, setShowEditScoring] = useState(false)
const [selectedRecord, setSelectedRecord] = useState<ScoringRecord | null>(null)
const [manualScoring, setManualScoring] = useState({
judgeId: "", participantId: "", scores: {} as Record<string, number>, comments: ""
})
const [statusFilter, setStatusFilter] = useState<"all" | "completed" | "pending">("all")
const [searchQuery, setSearchQuery] = useState("")
const [isLoading, setIsLoading] = useState(false)
const [success, setSuccess] = useState("")
const [error, setError] = useState("")
const [showScoringLink, setShowScoringLink] = useState(false)
const [showJudgeList, setShowJudgeList] = useState(false)
useEffect(() => {
if (selectedCompetition) {
loadScoringData()
}
}, [selectedCompetition])
const loadScoringData = () => {
if (!selectedCompetition) return
const participants = [
...(selectedCompetition.participatingApps || []).map((appId: string) => {
const app = mockIndividualApps.find(a => a.id === appId)
return { id: appId, name: app?.name || `應用 ${appId}`, type: "individual" as const }
}),
...(selectedCompetition.participatingTeams || []).map((teamId: string) => {
const team = initialTeams.find(t => t.id === teamId)
return { id: teamId, name: team?.name || `團隊 ${teamId}`, type: "team" as const }
})
]
const records: ScoringRecord[] = []
participants.forEach(participant => {
selectedCompetition.judges.forEach((judgeId: string) => {
const judge = judges.find(j => j.id === judgeId)
if (!judge) return
const existingScore = judgeScores.find(score =>
score.judgeId === judgeId && score.appId === participant.id
)
if (existingScore) {
records.push({
id: `${judgeId}-${participant.id}`,
judgeId, judgeName: judge.name,
participantId: participant.id, participantName: participant.name,
participantType: participant.type, scores: existingScore.scores,
totalScore: calculateTotalScore(existingScore.scores, selectedCompetition.rules || []),
comments: existingScore.comments,
submittedAt: existingScore.submittedAt || new Date().toISOString(),
status: "completed" as const,
})
} else {
// 初始化評分項目
const initialScores: Record<string, number> = {}
if (selectedCompetition.rules && selectedCompetition.rules.length > 0) {
selectedCompetition.rules.forEach((rule: any) => {
initialScores[rule.name] = 0
})
} else {
// 預設評分項目
initialScores.innovation = 0
initialScores.technical = 0
initialScores.usability = 0
initialScores.presentation = 0
initialScores.impact = 0
}
records.push({
id: `${judgeId}-${participant.id}`,
judgeId, judgeName: judge.name,
participantId: participant.id, participantName: participant.name,
participantType: participant.type, scores: initialScores,
totalScore: 0, comments: "", submittedAt: "",
status: "pending" as const,
})
}
})
})
setScoringRecords(records)
}
const calculateTotalScore = (scores: Record<string, number>, rules: any[]): number => {
if (rules.length === 0) {
const values = Object.values(scores)
return values.length > 0 ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : 0
}
let totalScore = 0
let totalWeight = 0
rules.forEach((rule: any) => {
const score = scores[rule.name] || 0
const weight = rule.weight || 1
totalScore += score * weight
totalWeight += weight
})
return totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0
}
const getFilteredRecords = () => {
let filtered = [...scoringRecords]
if (statusFilter !== "all") {
filtered = filtered.filter(record => record.status === statusFilter)
}
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase().trim()
filtered = filtered.filter(record =>
record.judgeName.toLowerCase().includes(query) ||
record.participantName.toLowerCase().includes(query)
)
}
return filtered
}
const handleManualScoring = () => {
// 根據競賽規則初始化評分項目
const initialScores: Record<string, number> = {}
if (selectedCompetition?.rules && selectedCompetition.rules.length > 0) {
selectedCompetition.rules.forEach((rule: any) => {
initialScores[rule.name] = 0
})
} else {
// 預設評分項目
initialScores.innovation = 0
initialScores.technical = 0
initialScores.usability = 0
initialScores.presentation = 0
initialScores.impact = 0
}
setManualScoring({
judgeId: "",
participantId: "",
scores: initialScores,
comments: ""
})
setShowManualScoring(true)
}
const handleEditScoring = (record: ScoringRecord) => {
setSelectedRecord(record)
setManualScoring({
judgeId: record.judgeId,
participantId: record.participantId,
scores: { ...record.scores },
comments: record.comments,
})
setShowEditScoring(true)
}
const handleSubmitScore = async () => {
setError("")
if (!manualScoring.judgeId || !manualScoring.participantId) {
setError("請選擇評審和參賽項目")
return
}
// 檢查所有評分項目是否都已評分
const scoringRules = selectedCompetition?.rules || []
const defaultRules = [
{ name: "創新性" }, { name: "技術性" }, { name: "實用性" },
{ name: "展示效果" }, { name: "影響力" }
]
const rules = scoringRules.length > 0 ? scoringRules : defaultRules
const hasAllScores = rules.every((rule: any) =>
manualScoring.scores[rule.name] && manualScoring.scores[rule.name] > 0
)
if (!hasAllScores) {
setError("請為所有評分項目打分")
return
}
if (!manualScoring.comments.trim()) {
setError("請填寫評審意見")
return
}
setIsLoading(true)
try {
await submitJudgeScore({
judgeId: manualScoring.judgeId,
appId: manualScoring.participantId,
scores: manualScoring.scores,
comments: manualScoring.comments.trim(),
})
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
loadScoringData()
setShowManualScoring(false)
setShowEditScoring(false)
setSelectedRecord(null)
} catch (err) {
setError("評分提交失敗,請重試")
} finally {
setIsLoading(false)
setTimeout(() => setSuccess(""), 3000)
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "completed": return <Badge className="bg-green-100 text-green-800"></Badge>
case "pending": return <Badge className="bg-orange-100 text-orange-800"></Badge>
default: return <Badge variant="outline">{status}</Badge>
}
}
const getScoringProgress = () => {
const total = scoringRecords.length
const completed = scoringRecords.filter(r => r.status === "completed").length
const pending = scoringRecords.filter(r => r.status === "pending").length
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0
return { total, completed, pending, percentage }
}
const progress = getScoringProgress()
return (
<div className="space-y-6">
{success && (
<Alert className="border-green-200 bg-green-50">
<CheckCircle className="h-4 w-4 text-green-600" />
<AlertDescription className="text-green-800">{success}</AlertDescription>
</Alert>
)}
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Trophy className="w-5 h-5" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Select value={selectedCompetition?.id || ""} onValueChange={(value) => {
const competition = competitions.find(c => c.id === value)
setSelectedCompetition(competition)
}}>
<SelectTrigger className="w-full">
<SelectValue placeholder="選擇競賽" />
</SelectTrigger>
<SelectContent>
{competitions.map((competition) => (
<SelectItem key={competition.id} value={competition.id}>
<div className="flex flex-col">
<span className="font-medium">{competition.name}</span>
<span className="text-xs text-gray-500">
{competition.year}{competition.month} {competition.type === "individual" ? "個人賽" : competition.type === "team" ? "團體賽" : "混合賽"}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</CardContent>
</Card>
{selectedCompetition && (
<>
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<BarChart3 className="w-5 h-5" />
<span>{selectedCompetition.name} - </span>
<Badge variant="outline">
{selectedCompetition.type === "individual" ? "個人賽" : selectedCompetition.type === "team" ? "團體賽" : "混合賽"}
</Badge>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-blue-600">{progress.completed}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-orange-600">{progress.pending}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-green-600">{progress.percentage}%</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-center">
<p className="text-2xl font-bold text-purple-600">{progress.total}</p>
<p className="text-sm text-gray-600"></p>
</div>
</CardContent>
</Card>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span></span>
<span>{progress.completed} / {progress.total}</span>
</div>
<Progress value={progress.percentage} className="h-2" />
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle className="flex items-center space-x-2">
<ClipboardList className="w-5 h-5" />
<span></span>
</CardTitle>
<div className="flex space-x-2">
<Button
onClick={() => setShowScoringLink(true)}
variant="outline"
className="border-blue-200 text-blue-600 hover:bg-blue-50"
>
<Link className="w-4 h-4 mr-2" />
</Button>
<Button
onClick={() => setShowJudgeList(true)}
variant="outline"
>
<Users className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleManualScoring} variant="outline">
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4 mb-6">
<div className="flex flex-wrap gap-4 items-center">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium"></span>
<Select value={statusFilter} onValueChange={(value: any) => setStatusFilter(value)}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="completed"></SelectItem>
<SelectItem value="pending"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="搜尋評審或參賽者..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
</div>
</div>
</div>
<div className="space-y-6">
{(() => {
// 按評審分組
const groupedByJudge = getFilteredRecords().reduce((groups, record) => {
const judgeName = record.judgeName
if (!groups[judgeName]) {
groups[judgeName] = []
}
groups[judgeName].push(record)
return groups
}, {} as Record<string, ScoringRecord[]>)
return Object.entries(groupedByJudge).map(([judgeName, records]) => {
const completedCount = records.filter(r => r.status === "completed").length
const totalCount = records.length
const progressPercentage = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0
return (
<Card key={judgeName} className="border-l-4 border-l-blue-500">
<CardHeader className="pb-3">
<div className="flex justify-between items-center">
<div className="flex items-center space-x-3">
<Avatar className="w-10 h-10">
<AvatarFallback className="text-sm font-semibold">
{judgeName.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<h3 className="text-lg font-semibold">{judgeName}</h3>
<p className="text-sm text-gray-600">
{completedCount} / {totalCount}
</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-right">
<div className="text-2xl font-bold text-blue-600">{progressPercentage}%</div>
<div className="text-xs text-gray-500"></div>
</div>
<div className="w-16 h-16 relative">
<svg className="w-16 h-16 transform -rotate-90" viewBox="0 0 36 36">
<path
className="text-gray-200"
stroke="currentColor"
strokeWidth="2"
fill="none"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
/>
<path
className="text-blue-600"
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeDasharray={`${progressPercentage}, 100`}
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
/>
</svg>
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-xs font-semibold">{progressPercentage}%</span>
</div>
</div>
</div>
</div>
</CardHeader>
<CardContent>
<div className="relative px-6">
{/* 左滑動箭頭 */}
{records.length > 4 && (
<button
onClick={() => {
const container = document.getElementById(`scroll-${judgeName}`)
if (container) {
container.scrollLeft -= 280 // 滑動一個卡片的寬度
}
}}
className="absolute -left-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
>
<ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
)}
{/* 右滑動箭頭 */}
{records.length > 4 && (
<button
onClick={() => {
const container = document.getElementById(`scroll-${judgeName}`)
if (container) {
container.scrollLeft += 280 // 滑動一個卡片的寬度
}
}}
className="absolute -right-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
>
<ChevronRight className="w-4 h-4 text-gray-600" />
</button>
)}
<div
id={`scroll-${judgeName}`}
className="flex space-x-4 overflow-x-auto scrollbar-hide pb-2 scroll-smooth"
style={{
scrollbarWidth: 'none',
msOverflowStyle: 'none',
maxWidth: 'calc(4 * 256px + 3 * 16px)' // 4個卡片 + 3個間距
}}
>
{records.map((record) => (
<div
key={record.id}
className="flex-shrink-0 w-64 bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
>
<div className="space-y-3">
{/* 項目標題和類型 */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{record.participantType === "individual" ? (
<User className="w-4 h-4 text-blue-600" />
) : (
<Users className="w-4 h-4 text-green-600" />
)}
<span className="font-medium text-sm truncate">{record.participantName}</span>
</div>
<Badge variant="outline" className="text-xs">
{record.participantType === "individual" ? "個人" : "團隊"}
</Badge>
</div>
{/* 評分狀態 */}
<div className="flex items-center justify-between">
<div className="text-center">
<div className="flex items-center space-x-1">
<span className="font-bold text-lg">{record.totalScore}</span>
<span className="text-gray-500 text-sm">/ 10</span>
</div>
</div>
<div className="flex flex-col items-end space-y-1">
{getStatusBadge(record.status)}
{record.submittedAt && (
<span className="text-xs text-gray-500">
{new Date(record.submittedAt).toLocaleDateString()}
</span>
)}
</div>
</div>
{/* 操作按鈕 */}
<div className="flex justify-center pt-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEditScoring(record)}
className="w-full"
>
{record.status === "completed" ? (
<>
<Edit className="w-4 h-4 mr-2" />
</>
) : (
<>
<Plus className="w-4 h-4 mr-2" />
</>
)}
</Button>
</div>
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
)
})
})()}
</div>
</CardContent>
</Card>
</>
)}
<Dialog open={showManualScoring || showEditScoring} onOpenChange={(open) => {
if (!open) {
setShowManualScoring(false)
setShowEditScoring(false)
setSelectedRecord(null)
setManualScoring({
judgeId: "",
participantId: "",
scores: {} as Record<string, number>,
comments: ""
})
}
}}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Edit className="w-5 h-5" />
<span>{showEditScoring ? "編輯評分" : "手動輸入評分"}</span>
</DialogTitle>
<DialogDescription>
{showEditScoring ? "修改現有評分記錄" : "為參賽者手動輸入評分"}
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label> *</Label>
<Select
value={manualScoring.judgeId}
onValueChange={(value) => setManualScoring({ ...manualScoring, judgeId: value })}
>
<SelectTrigger>
<SelectValue placeholder="選擇評審" />
</SelectTrigger>
<SelectContent>
{judges.map((judge) => (
<SelectItem key={judge.id} value={judge.id}>
{judge.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label> *</Label>
<Select
value={manualScoring.participantId}
onValueChange={(value) => setManualScoring({ ...manualScoring, participantId: value })}
>
<SelectTrigger>
<SelectValue placeholder="選擇參賽者" />
</SelectTrigger>
<SelectContent>
{[
...(selectedCompetition?.participatingApps || []).map((appId: string) => {
const app = mockIndividualApps.find(a => a.id === appId)
return { id: appId, name: app?.name || `應用 ${appId}`, type: "individual" }
}),
...(selectedCompetition?.participatingTeams || []).map((teamId: string) => {
const team = initialTeams.find(t => t.id === teamId)
return { id: teamId, name: team?.name || `團隊 ${teamId}`, type: "team" }
})
].map((participant) => (
<SelectItem key={participant.id} value={participant.id}>
<div className="flex items-center space-x-2">
{participant.type === "individual" ? (
<User className="w-4 h-4 text-blue-600" />
) : (
<Users className="w-4 h-4 text-green-600" />
)}
<span>{participant.name}</span>
<Badge variant="outline" className="text-xs">
{participant.type === "individual" ? "個人" : "團隊"}
</Badge>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* 動態評分項目 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold"></h3>
{(() => {
const scoringRules = selectedCompetition?.rules || []
const defaultRules = [
{ name: "創新性", description: "創新程度和獨特性", weight: 25 },
{ name: "技術性", description: "技術實現的複雜度和品質", weight: 30 },
{ name: "實用性", description: "實際應用價值和用戶體驗", weight: 20 },
{ name: "展示效果", description: "展示的清晰度和吸引力", weight: 15 },
{ name: "影響力", description: "對行業或社會的潛在影響", weight: 10 }
]
const rules = scoringRules.length > 0 ? scoringRules : defaultRules
return rules.map((rule: any, index: number) => (
<div key={index} className="space-y-4 p-6 border rounded-lg bg-white shadow-sm">
<div className="flex justify-between items-start">
<div className="flex-1">
<Label className="text-lg font-semibold text-gray-900">{rule.name}</Label>
<p className="text-sm text-gray-600 mt-2 leading-relaxed">{rule.description}</p>
{rule.weight && (
<p className="text-xs text-purple-600 mt-2 font-medium">{rule.weight}%</p>
)}
</div>
<div className="text-right ml-4">
<span className="text-2xl font-bold text-blue-600">
{manualScoring.scores[rule.name] || 0} / 10
</span>
</div>
</div>
{/* 評分按鈕 */}
<div className="flex flex-wrap gap-3">
{Array.from({ length: 10 }, (_, i) => i + 1).map((score) => (
<button
key={score}
type="button"
onClick={() => setManualScoring({
...manualScoring,
scores: { ...manualScoring.scores, [rule.name]: score }
})}
className={`w-12 h-12 rounded-lg border-2 font-semibold text-lg transition-all duration-200 ${
(manualScoring.scores[rule.name] || 0) === score
? 'bg-blue-600 text-white border-blue-600 shadow-lg scale-105'
: 'bg-white text-gray-700 border-gray-300 hover:border-blue-400 hover:bg-blue-50 hover:scale-105'
}`}
>
{score}
</button>
))}
</div>
</div>
))
})()}
</div>
{/* 總分顯示 */}
<div className="p-6 bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg border-2 border-blue-200">
<div className="flex justify-between items-center">
<div>
<span className="text-xl font-bold text-gray-900"></span>
<p className="text-sm text-gray-600 mt-1"></p>
</div>
<div className="flex items-center space-x-3">
<span className="text-4xl font-bold text-blue-600">
{calculateTotalScore(manualScoring.scores, selectedCompetition?.rules || [])}
</span>
<span className="text-xl text-gray-500 font-medium">/ 10</span>
</div>
</div>
</div>
<div className="space-y-3">
<Label className="text-lg font-semibold"> *</Label>
<Textarea
placeholder="請詳細填寫評審意見、優點分析、改進建議等..."
value={manualScoring.comments}
onChange={(e) => setManualScoring({ ...manualScoring, comments: e.target.value })}
rows={6}
className="min-h-[120px] resize-none"
/>
<p className="text-xs text-gray-500"></p>
</div>
</div>
<div className="flex justify-end space-x-4 pt-6 border-t border-gray-200">
<Button
variant="outline"
size="lg"
onClick={() => {
setShowManualScoring(false)
setShowEditScoring(false)
setSelectedRecord(null)
}}
className="px-8"
>
</Button>
<Button
onClick={handleSubmitScore}
disabled={isLoading}
size="lg"
className="px-8 bg-blue-600 hover:bg-blue-700"
>
{isLoading ? (
<>
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
...
</>
) : (
<>
<CheckCircle className="w-5 h-5 mr-2" />
{showEditScoring ? "更新評分" : "提交評分"}
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
{/* 評分連結對話框 */}
<ScoringLinkDialog
open={showScoringLink}
onOpenChange={setShowScoringLink}
currentCompetition={selectedCompetition}
/>
{/* 評審清單對話框 */}
<JudgeListDialog
open={showJudgeList}
onOpenChange={setShowJudgeList}
judges={selectedCompetition ?
judges
.filter(judge => selectedCompetition.judges.includes(judge.id))
.map(judge => ({
id: judge.id,
name: judge.name,
specialty: "評審專家"
})) : []
}
/>
</div>
)
}