Files
ai-scoring-application/app/criteria/page.tsx

294 lines
9.9 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 { 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>
)
}