Initialized repository for project AI scoring application

Co-authored-by: 吳佩庭 <190080258+WuPeiTing0919@users.noreply.github.com>
This commit is contained in:
v0
2025-09-21 15:28:35 +00:00
commit 22a5727920
38 changed files with 6515 additions and 0 deletions

293
app/criteria/page.tsx Normal file
View File

@@ -0,0 +1,293 @@
"use client"
import { useState } from "react"
import { Sidebar } from "@/components/sidebar"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Slider } from "@/components/ui/slider"
import { Badge } from "@/components/ui/badge"
import { Plus, Trash2, Save, RotateCcw, FileText } from "lucide-react"
import { useToast } from "@/hooks/use-toast"
interface CriteriaItem {
id: string
name: string
description: string
weight: number
maxScore: number
}
const defaultCriteria: CriteriaItem[] = [
{
id: "1",
name: "內容品質",
description: "內容的準確性、完整性和專業度",
weight: 25,
maxScore: 10,
},
{
id: "2",
name: "視覺設計",
description: "版面設計、色彩搭配和視覺效果",
weight: 20,
maxScore: 10,
},
{
id: "3",
name: "邏輯結構",
description: "內容組織的邏輯性和條理性",
weight: 20,
maxScore: 10,
},
{
id: "4",
name: "創新性",
description: "創意思維和獨特觀點的展現",
weight: 15,
maxScore: 10,
},
{
id: "5",
name: "實用性",
description: "內容的實際應用價值和可操作性",
weight: 20,
maxScore: 10,
},
]
export default function CriteriaPage() {
const [criteria, setCriteria] = useState<CriteriaItem[]>(defaultCriteria)
const [templateName, setTemplateName] = useState("預設評分標準")
const { toast } = useToast()
const addCriteria = () => {
const newCriteria: CriteriaItem = {
id: Date.now().toString(),
name: "",
description: "",
weight: 10,
maxScore: 10,
}
setCriteria([...criteria, newCriteria])
}
const removeCriteria = (id: string) => {
setCriteria(criteria.filter((item) => item.id !== id))
}
const updateCriteria = (id: string, field: keyof CriteriaItem, value: string | number) => {
setCriteria(criteria.map((item) => (item.id === id ? { ...item, [field]: value } : item)))
}
const updateWeight = (id: string, weight: number[]) => {
updateCriteria(id, "weight", weight[0])
}
const totalWeight = criteria.reduce((sum, item) => sum + item.weight, 0)
const saveCriteria = () => {
if (totalWeight !== 100) {
toast({
title: "權重設定錯誤",
description: "所有評分項目的權重總和必須等於 100%",
variant: "destructive",
})
return
}
if (criteria.some((item) => !item.name.trim())) {
toast({
title: "設定不完整",
description: "請填寫所有評分項目的名稱",
variant: "destructive",
})
return
}
// 這裡會連接到後端 API 儲存評分標準
toast({
title: "儲存成功",
description: "評分標準已成功儲存",
})
}
const resetToDefault = () => {
setCriteria(defaultCriteria)
setTemplateName("預設評分標準")
toast({
title: "已重置",
description: "評分標準已重置為預設值",
})
}
return (
<div className="min-h-screen bg-background">
<Sidebar />
<main className="md:ml-64 p-6">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="mb-8 pt-8 md:pt-0">
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]"></h1>
<p className="text-muted-foreground"></p>
</div>
{/* Template Name */}
<Card className="mb-6">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent>
<Input
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
placeholder="輸入評分標準名稱"
className="max-w-md"
/>
</CardContent>
</Card>
{/* Weight Summary */}
<Card className="mb-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium"></span>
<Badge variant={totalWeight === 100 ? "default" : "destructive"}>{totalWeight}%</Badge>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className={`h-2 rounded-full transition-all ${totalWeight === 100 ? "bg-primary" : "bg-destructive"}`}
style={{ width: `${Math.min(totalWeight, 100)}%` }}
/>
</div>
{totalWeight !== 100 && <p className="text-sm text-destructive mt-2"> 100%</p>}
</CardContent>
</Card>
{/* Criteria List */}
<div className="space-y-4 mb-6">
{criteria.map((item, index) => (
<Card key={item.id}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg"> {index + 1}</CardTitle>
<Button
variant="ghost"
size="sm"
onClick={() => removeCriteria(item.id)}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor={`name-${item.id}`}></Label>
<Input
id={`name-${item.id}`}
value={item.name}
onChange={(e) => updateCriteria(item.id, "name", e.target.value)}
placeholder="例如:內容品質"
/>
</div>
<div>
<Label htmlFor={`maxScore-${item.id}`}>滿</Label>
<Input
id={`maxScore-${item.id}`}
type="number"
value={item.maxScore}
onChange={(e) => updateCriteria(item.id, "maxScore", Number.parseInt(e.target.value) || 10)}
min="1"
max="100"
/>
</div>
</div>
<div>
<Label htmlFor={`description-${item.id}`}></Label>
<Textarea
id={`description-${item.id}`}
value={item.description}
onChange={(e) => updateCriteria(item.id, "description", e.target.value)}
placeholder="描述此項目的評分標準和要求"
rows={2}
/>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<Label></Label>
<Badge variant="outline">{item.weight}%</Badge>
</div>
<Slider
value={[item.weight]}
onValueChange={(value) => updateWeight(item.id, value)}
max={100}
min={0}
step={5}
className="w-full"
/>
</div>
</CardContent>
</Card>
))}
</div>
{/* Add Criteria Button */}
<Card className="mb-6">
<CardContent className="pt-6">
<Button onClick={addCriteria} variant="outline" className="w-full bg-transparent">
<Plus className="h-4 w-4 mr-2" />
</Button>
</CardContent>
</Card>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-4">
<Button onClick={saveCriteria} className="flex-1">
<Save className="h-4 w-4 mr-2" />
</Button>
<Button onClick={resetToDefault} variant="outline">
<RotateCcw className="h-4 w-4 mr-2" />
</Button>
</div>
{/* Preview */}
<Card className="mt-8">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{criteria.map((item, index) => (
<div key={item.id} className="flex items-center justify-between p-3 bg-muted rounded-lg">
<div className="flex-1">
<div className="font-medium">{item.name || `評分項目 ${index + 1}`}</div>
{item.description && <div className="text-sm text-muted-foreground mt-1">{item.description}</div>}
</div>
<div className="text-right">
<div className="font-medium">: {item.weight}%</div>
<div className="text-sm text-muted-foreground">滿: {item.maxScore}</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</main>
</div>
)
}