Files
ai-showcase-platform/components/competition/judge-scoring-dialog.tsx

262 lines
9.2 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 } from "react"
import { useCompetition } from "@/contexts/competition-context"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Textarea } from "@/components/ui/textarea"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Slider } from "@/components/ui/slider"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Star, User, Award, MessageSquare, CheckCircle } from "lucide-react"
interface JudgeScoringDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
appId: string
appName: string
judgeId: string
}
export function JudgeScoringDialog({ open, onOpenChange, appId, appName, judgeId }: JudgeScoringDialogProps) {
const { judges, submitJudgeScore, getAppJudgeScores } = useCompetition()
const judge = judges.find((j) => j.id === judgeId)
const existingScore = getAppJudgeScores(appId).find((s) => s.judgeId === judgeId)
const [scores, setScores] = useState({
innovation: existingScore?.scores.innovation || 8,
technical: existingScore?.scores.technical || 8,
usability: existingScore?.scores.usability || 8,
presentation: existingScore?.scores.presentation || 8,
impact: existingScore?.scores.impact || 8,
})
const [comments, setComments] = useState(existingScore?.comments || "")
const [isSubmitting, setIsSubmitting] = useState(false)
const [isSubmitted, setIsSubmitted] = useState(false)
if (!judge) return null
const handleScoreChange = (category: keyof typeof scores, value: number[]) => {
setScores((prev) => ({
...prev,
[category]: value[0],
}))
}
const handleSubmit = async () => {
if (!comments.trim()) {
return
}
setIsSubmitting(true)
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000))
submitJudgeScore({
judgeId,
appId,
scores,
comments: comments.trim(),
})
setIsSubmitting(false)
setIsSubmitted(true)
// Auto close after success
setTimeout(() => {
setIsSubmitted(false)
onOpenChange(false)
}, 2000)
}
const totalScore = Object.values(scores).reduce((sum, score) => sum + score, 0)
const averageScore = (totalScore / 5).toFixed(1)
const scoreCategories = [
{
key: "innovation" as const,
name: "創新性",
description: "技術創新程度和獨特性",
icon: "💡",
},
{
key: "technical" as const,
name: "技術性",
description: "技術實現難度和完成度",
icon: "⚙️",
},
{
key: "usability" as const,
name: "實用性",
description: "實際應用價值和用戶體驗",
icon: "🎯",
},
{
key: "presentation" as const,
name: "展示效果",
description: "介面設計和展示完整性",
icon: "🎨",
},
{
key: "impact" as const,
name: "影響力",
description: "對業務和用戶的潛在影響",
icon: "🚀",
},
]
if (isSubmitted) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<div className="flex flex-col items-center space-y-4 py-8">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-600">{appName}</p>
</div>
</div>
</DialogContent>
</Dialog>
)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Award className="w-5 h-5 text-purple-600" />
<span></span>
</DialogTitle>
<DialogDescription>{appName}</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* Judge Info */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center space-x-3">
<Avatar>
<AvatarImage src={judge.avatar} />
<AvatarFallback className="bg-purple-100 text-purple-700">{judge.name[0]}</AvatarFallback>
</Avatar>
<div>
<h4 className="font-semibold">{judge.name}</h4>
<p className="text-sm text-gray-600">{judge.title}</p>
<div className="flex flex-wrap gap-1 mt-1">
{judge.expertise.map((skill) => (
<Badge key={skill} variant="secondary" className="text-xs">
{skill}
</Badge>
))}
</div>
</div>
</div>
</CardHeader>
</Card>
{/* Scoring Categories */}
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span></span>
<div className="text-right">
<div className="text-2xl font-bold text-purple-600">{averageScore}</div>
<div className="text-sm text-gray-500"></div>
</div>
</CardTitle>
<CardDescription>1-10</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{scoreCategories.map((category) => (
<div key={category.key} className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-lg">{category.icon}</span>
<div>
<h4 className="font-medium">{category.name}</h4>
<p className="text-sm text-gray-600">{category.description}</p>
</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-purple-600">{scores[category.key]}</div>
<div className="flex">
{[...Array(scores[category.key])].map((_, i) => (
<Star key={i} className="w-3 h-3 text-yellow-400 fill-current" />
))}
</div>
</div>
</div>
<Slider
value={[scores[category.key]]}
onValueChange={(value) => handleScoreChange(category.key, value)}
max={10}
min={1}
step={1}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500">
<span>1 ()</span>
<span>5 ()</span>
<span>10 ()</span>
</div>
</div>
))}
</CardContent>
</Card>
{/* Comments */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<MessageSquare className="w-4 h-4" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<Textarea
placeholder="請分享您對此應用的專業評價、優點、改進建議等..."
value={comments}
onChange={(e) => setComments(e.target.value)}
rows={4}
maxLength={1000}
/>
<div className="text-xs text-gray-500 mt-2">{comments.length}/1000 </div>
</CardContent>
</Card>
{/* Submit */}
<div className="flex space-x-3">
<Button
onClick={handleSubmit}
disabled={isSubmitting || !comments.trim()}
className="flex-1 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
>
{isSubmitting ? "提交中..." : existingScore ? "更新評分" : "提交評分"}
</Button>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
</div>
{existingScore && (
<Alert>
<User className="h-4 w-4" />
<AlertDescription></AlertDescription>
</Alert>
)}
</div>
</DialogContent>
</Dialog>
)
}