622 lines
34 KiB
TypeScript
622 lines
34 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
|
import { Calendar, Clock, User, Star, Plus, Edit, CheckCircle, AlertTriangle } from "lucide-react"
|
|
|
|
// Mock reviews data
|
|
const reviewsData = [
|
|
{
|
|
id: 1,
|
|
reviewee: "陳雅雯",
|
|
reviewer: "執行長",
|
|
type: "quarterly",
|
|
scheduledDate: "2024-02-15",
|
|
status: "scheduled",
|
|
priority: "high",
|
|
department: "業務部",
|
|
position: "業務副總",
|
|
lastReview: "2023-11-15",
|
|
overallRating: null,
|
|
color: "red",
|
|
},
|
|
{
|
|
id: 2,
|
|
reviewee: "王志明",
|
|
reviewer: "執行長",
|
|
type: "one_on_one",
|
|
scheduledDate: "2024-02-18",
|
|
status: "in_progress",
|
|
priority: "medium",
|
|
department: "技術部",
|
|
position: "技術長",
|
|
lastReview: "2024-01-18",
|
|
overallRating: null,
|
|
color: "yellow",
|
|
},
|
|
{
|
|
id: 3,
|
|
reviewee: "李美玲",
|
|
reviewer: "執行長",
|
|
type: "annual",
|
|
scheduledDate: "2024-02-20",
|
|
status: "completed",
|
|
priority: "high",
|
|
department: "行銷部",
|
|
position: "行銷長",
|
|
lastReview: "2023-02-20",
|
|
overallRating: 4,
|
|
color: "green",
|
|
},
|
|
{
|
|
id: 4,
|
|
reviewee: "張建國",
|
|
reviewer: "執行長",
|
|
type: "goal_setting",
|
|
scheduledDate: "2024-02-25",
|
|
status: "scheduled",
|
|
priority: "medium",
|
|
department: "研發部",
|
|
position: "研發總監",
|
|
lastReview: "2023-12-25",
|
|
overallRating: null,
|
|
color: "blue",
|
|
},
|
|
]
|
|
|
|
const reviewQuestions = [
|
|
{ id: 1, question: "您如何評價本季度的整體表現?", type: "rating", category: "績效", required: true },
|
|
{ id: 2, question: "本期間您的主要成就是什麼?", type: "text", category: "績效", required: true },
|
|
{ id: 3, question: "您面臨了哪些挑戰,如何克服?", type: "text", category: "挑戰", required: false },
|
|
{ id: 4, question: "您如何有效領導團隊?", type: "rating", category: "領導力", required: true },
|
|
{ id: 5, question: "下一季度的目標是什麼?", type: "text", category: "目標", required: true },
|
|
]
|
|
|
|
const statusColors = {
|
|
scheduled: { bg: "bg-blue-100", text: "text-blue-800", border: "border-blue-300" },
|
|
in_progress: { bg: "bg-yellow-100", text: "text-yellow-800", border: "border-yellow-300" },
|
|
completed: { bg: "bg-green-100", text: "text-green-800", border: "border-green-300" },
|
|
cancelled: { bg: "bg-red-100", text: "text-red-800", border: "border-red-300" },
|
|
}
|
|
|
|
const typeColors = {
|
|
quarterly: { bg: "bg-purple-100", text: "text-purple-800" },
|
|
annual: { bg: "bg-indigo-100", text: "text-indigo-800" },
|
|
one_on_one: { bg: "bg-emerald-100", text: "text-emerald-800" },
|
|
goal_setting: { bg: "bg-orange-100", text: "text-orange-800" },
|
|
}
|
|
|
|
export default function ReviewsPage() {
|
|
const [activeTab, setActiveTab] = useState("overview")
|
|
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false)
|
|
const [isReviewDialogOpen, setIsReviewDialogOpen] = useState(false)
|
|
const [selectedReview, setSelectedReview] = useState<any>(null)
|
|
|
|
const handleStartReview = (review: any) => {
|
|
setSelectedReview(review)
|
|
setIsReviewDialogOpen(true)
|
|
}
|
|
|
|
const reviewStats = {
|
|
total: reviewsData.length,
|
|
scheduled: reviewsData.filter((r) => r.status === "scheduled").length,
|
|
inProgress: reviewsData.filter((r) => r.status === "in_progress").length,
|
|
completed: reviewsData.filter((r) => r.status === "completed").length,
|
|
overdue: reviewsData.filter((r) => new Date(r.scheduledDate) < new Date() && r.status === "scheduled").length,
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-indigo-50 to-purple-50 p-3 sm:p-6">
|
|
<div className="max-w-7xl mx-auto space-y-4 sm:space-y-6">
|
|
{/* Header */}
|
|
<div className="flex flex-col space-y-4 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
|
|
<div className="flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-4">
|
|
<img
|
|
src="data:image/png;base64,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"
|
|
alt="公司 Logo"
|
|
className="h-10 w-auto sm:h-12 md:hidden"
|
|
/>
|
|
<div>
|
|
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
|
審查管理中心
|
|
</h1>
|
|
<p className="text-sm sm:text-base text-gray-600 mt-1">績效審查與一對一面談管理</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
className="bg-white/80 backdrop-blur-sm"
|
|
onClick={() => setIsScheduleDialogOpen(true)}
|
|
>
|
|
<Calendar className="h-4 w-4 mr-2" />
|
|
安排審查
|
|
</Button>
|
|
<Button className="bg-gradient-to-r from-indigo-500 to-purple-500 hover:from-indigo-600 hover:to-purple-600 text-white shadow-lg">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
新增審查
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<div className="grid grid-cols-2 sm:grid-cols-5 gap-3 sm:gap-4">
|
|
<Card className="shadow-lg bg-gradient-to-br from-blue-50 to-cyan-100 border-0">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-blue-700">總審查</CardTitle>
|
|
<Calendar className="h-4 w-4 text-blue-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-blue-800">{reviewStats.total}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="shadow-lg bg-gradient-to-br from-yellow-50 to-orange-100 border-0">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-yellow-700">待審查</CardTitle>
|
|
<Clock className="h-4 w-4 text-yellow-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-yellow-800">{reviewStats.scheduled}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="shadow-lg bg-gradient-to-br from-orange-50 to-amber-100 border-0">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-orange-700">進行中</CardTitle>
|
|
<User className="h-4 w-4 text-orange-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-orange-800">{reviewStats.inProgress}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="shadow-lg bg-gradient-to-br from-green-50 to-emerald-100 border-0">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-green-700">已完成</CardTitle>
|
|
<CheckCircle className="h-4 w-4 text-green-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-green-800">{reviewStats.completed}</div>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="shadow-lg bg-gradient-to-br from-red-50 to-pink-100 border-0">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-red-700">逾期</CardTitle>
|
|
<AlertTriangle className="h-4 w-4 text-red-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-red-800">{reviewStats.overdue}</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<Card className="shadow-lg bg-white/80 backdrop-blur-sm">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg font-bold bg-gradient-to-r from-gray-800 to-gray-600 bg-clip-text text-transparent">
|
|
審查管理
|
|
</CardTitle>
|
|
<CardDescription>管理績效審查和一對一面談</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="overview">總覽</TabsTrigger>
|
|
<TabsTrigger value="schedule">排程</TabsTrigger>
|
|
<TabsTrigger value="history">歷史記錄</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Overview Tab */}
|
|
<TabsContent value="overview" className="space-y-4">
|
|
<div className="grid gap-4">
|
|
{reviewsData.map((review) => (
|
|
<Card
|
|
key={review.id}
|
|
className={`shadow-md hover:shadow-lg transition-all duration-200 border-l-4 ${
|
|
review.priority === "high"
|
|
? "border-red-400 bg-gradient-to-r from-red-50 to-pink-50"
|
|
: review.priority === "medium"
|
|
? "border-yellow-400 bg-gradient-to-r from-yellow-50 to-amber-50"
|
|
: "border-green-400 bg-gradient-to-r from-green-50 to-emerald-50"
|
|
}`}
|
|
>
|
|
<CardContent className="p-6">
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-4 sm:space-y-0">
|
|
<div className="flex items-center space-x-4">
|
|
<Avatar className="h-12 w-12 ring-2 ring-white shadow-md">
|
|
<AvatarFallback
|
|
className={`${
|
|
review.priority === "high"
|
|
? "bg-gradient-to-br from-red-400 to-pink-500"
|
|
: review.priority === "medium"
|
|
? "bg-gradient-to-br from-yellow-400 to-amber-500"
|
|
: "bg-gradient-to-br from-green-400 to-emerald-500"
|
|
} text-white`}
|
|
>
|
|
{review.reviewee.charAt(0)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<h3 className="font-semibold text-gray-900">{review.reviewee}</h3>
|
|
<p className="text-sm text-gray-600">{review.position}</p>
|
|
<p className="text-xs text-gray-500">{review.department}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col sm:items-end space-y-2">
|
|
<div className="flex items-center space-x-2">
|
|
<Badge
|
|
className={`${
|
|
typeColors[review.type as keyof typeof typeColors]?.bg
|
|
} ${typeColors[review.type as keyof typeof typeColors]?.text} border-0`}
|
|
>
|
|
{review.type === "quarterly"
|
|
? "季度審查"
|
|
: review.type === "annual"
|
|
? "年度審查"
|
|
: review.type === "one_on_one"
|
|
? "一對一"
|
|
: "目標設定"}
|
|
</Badge>
|
|
<Badge
|
|
className={`${
|
|
statusColors[review.status as keyof typeof statusColors]?.bg
|
|
} ${statusColors[review.status as keyof typeof statusColors]?.text} border-0`}
|
|
>
|
|
{review.status === "scheduled"
|
|
? "待審查"
|
|
: review.status === "in_progress"
|
|
? "進行中"
|
|
: review.status === "completed"
|
|
? "已完成"
|
|
: "已取消"}
|
|
</Badge>
|
|
</div>
|
|
<div className="flex items-center space-x-4 text-sm text-gray-600">
|
|
<span>預定: {review.scheduledDate}</span>
|
|
<span>審查者: {review.reviewer}</span>
|
|
</div>
|
|
{review.overallRating && (
|
|
<div className="flex items-center space-x-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<Star
|
|
key={i}
|
|
className={`h-4 w-4 ${
|
|
i < review.overallRating ? "text-yellow-400 fill-current" : "text-gray-300"
|
|
}`}
|
|
/>
|
|
))}
|
|
<span className="text-sm text-gray-600 ml-2">{review.overallRating}/5</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
{review.status === "scheduled" && (
|
|
<Button
|
|
onClick={() => handleStartReview(review)}
|
|
className="bg-gradient-to-r from-blue-500 to-indigo-500 hover:from-blue-600 hover:to-indigo-600 text-white"
|
|
>
|
|
開始審查
|
|
</Button>
|
|
)}
|
|
{review.status === "in_progress" && (
|
|
<Button
|
|
onClick={() => handleStartReview(review)}
|
|
className="bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 text-white"
|
|
>
|
|
繼續審查
|
|
</Button>
|
|
)}
|
|
{review.status === "completed" && (
|
|
<Button variant="outline" className="bg-white/80">
|
|
查看結果
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="sm">
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Schedule Tab */}
|
|
<TabsContent value="schedule" className="space-y-4">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>受審者</TableHead>
|
|
<TableHead>審查類型</TableHead>
|
|
<TableHead>預定日期</TableHead>
|
|
<TableHead>狀態</TableHead>
|
|
<TableHead>優先級</TableHead>
|
|
<TableHead>操作</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{reviewsData.map((review) => (
|
|
<TableRow key={review.id}>
|
|
<TableCell>
|
|
<div className="flex items-center space-x-3">
|
|
<Avatar className="h-8 w-8">
|
|
<AvatarFallback className="bg-gradient-to-br from-blue-400 to-purple-500 text-white">
|
|
{review.reviewee.charAt(0)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<div className="font-medium">{review.reviewee}</div>
|
|
<div className="text-sm text-gray-500">{review.position}</div>
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
className={`${
|
|
typeColors[review.type as keyof typeof typeColors]?.bg
|
|
} ${typeColors[review.type as keyof typeof typeColors]?.text} border-0`}
|
|
>
|
|
{review.type === "quarterly"
|
|
? "季度審查"
|
|
: review.type === "annual"
|
|
? "年度審查"
|
|
: review.type === "one_on_one"
|
|
? "一對一"
|
|
: "目標設定"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell className="text-sm">{review.scheduledDate}</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
className={`${
|
|
statusColors[review.status as keyof typeof statusColors]?.bg
|
|
} ${statusColors[review.status as keyof typeof statusColors]?.text} border-0`}
|
|
>
|
|
{review.status === "scheduled"
|
|
? "待審查"
|
|
: review.status === "in_progress"
|
|
? "進行中"
|
|
: review.status === "completed"
|
|
? "已完成"
|
|
: "已取消"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
variant={
|
|
review.priority === "high"
|
|
? "destructive"
|
|
: review.priority === "medium"
|
|
? "secondary"
|
|
: "default"
|
|
}
|
|
>
|
|
{review.priority === "high" ? "高" : review.priority === "medium" ? "中" : "低"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center space-x-2">
|
|
<Button variant="ghost" size="sm">
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm">
|
|
<Calendar className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TabsContent>
|
|
|
|
{/* History Tab */}
|
|
<TabsContent value="history" className="space-y-4">
|
|
<div className="space-y-4">
|
|
{reviewsData
|
|
.filter((r) => r.status === "completed")
|
|
.map((review) => (
|
|
<Card key={review.id} className="shadow-md">
|
|
<CardContent className="p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-4">
|
|
<Avatar className="h-10 w-10">
|
|
<AvatarFallback className="bg-gradient-to-br from-green-400 to-emerald-500 text-white">
|
|
{review.reviewee.charAt(0)}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<h3 className="font-semibold">{review.reviewee}</h3>
|
|
<p className="text-sm text-gray-600">{review.position}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-4">
|
|
{review.overallRating && (
|
|
<div className="flex items-center space-x-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<Star
|
|
key={i}
|
|
className={`h-4 w-4 ${
|
|
i < review.overallRating ? "text-yellow-400 fill-current" : "text-gray-300"
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
<Badge className="bg-green-100 text-green-800 border-0">已完成</Badge>
|
|
<span className="text-sm text-gray-500">{review.scheduledDate}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Schedule Review Dialog */}
|
|
<Dialog open={isScheduleDialogOpen} onOpenChange={setIsScheduleDialogOpen}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
|
安排新審查
|
|
</DialogTitle>
|
|
<DialogDescription>為高階主管安排績效審查或一對一面談</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="reviewee">受審者</Label>
|
|
<Select>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="選擇受審者" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="chen">陳雅雯 - 業務副總</SelectItem>
|
|
<SelectItem value="wang">王志明 - 技術長</SelectItem>
|
|
<SelectItem value="li">李美玲 - 行銷長</SelectItem>
|
|
<SelectItem value="zhang">張建國 - 研發總監</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="reviewType">審查類型</Label>
|
|
<Select>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="選擇審查類型" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="quarterly">季度審查</SelectItem>
|
|
<SelectItem value="annual">年度審查</SelectItem>
|
|
<SelectItem value="one_on_one">一對一面談</SelectItem>
|
|
<SelectItem value="goal_setting">目標設定</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="scheduledDate">預定日期</Label>
|
|
<Input id="scheduledDate" type="date" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="priority">優先級</Label>
|
|
<Select>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="選擇優先級" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="high">高</SelectItem>
|
|
<SelectItem value="medium">中</SelectItem>
|
|
<SelectItem value="low">低</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="notes">備註</Label>
|
|
<Textarea id="notes" placeholder="審查重點或特殊說明..." />
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white">
|
|
安排審查
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Review Dialog */}
|
|
<Dialog open={isReviewDialogOpen} onOpenChange={setIsReviewDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle className="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
|
|
進行審查 - {selectedReview?.reviewee}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{selectedReview?.type === "quarterly"
|
|
? "季度績效審查"
|
|
: selectedReview?.type === "annual"
|
|
? "年度績效審查"
|
|
: selectedReview?.type === "one_on_one"
|
|
? "一對一面談"
|
|
: "目標設定會議"}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-6">
|
|
{reviewQuestions.map((question, index) => (
|
|
<div key={question.id} className="space-y-3">
|
|
<div className="flex items-start space-x-2">
|
|
<Badge variant="outline" className="mt-1">
|
|
{index + 1}
|
|
</Badge>
|
|
<div className="flex-1">
|
|
<div className="flex items-center space-x-2 mb-2">
|
|
<Label className="text-base font-medium">{question.question}</Label>
|
|
{question.required && (
|
|
<Badge variant="destructive" className="text-xs">
|
|
必填
|
|
</Badge>
|
|
)}
|
|
<Badge variant="outline" className="text-xs">
|
|
{question.category}
|
|
</Badge>
|
|
</div>
|
|
{question.type === "rating" ? (
|
|
<div className="flex items-center space-x-2">
|
|
{[1, 2, 3, 4, 5].map((rating) => (
|
|
<button key={rating} className="p-1 hover:bg-gray-100 rounded" type="button">
|
|
<Star className="h-6 w-6 text-gray-300 hover:text-yellow-400" />
|
|
</button>
|
|
))}
|
|
<span className="text-sm text-gray-500 ml-2">點擊星星評分</span>
|
|
</div>
|
|
) : (
|
|
<Textarea placeholder="請輸入回答..." className="min-h-[100px]" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
<div className="border-t pt-4">
|
|
<Label className="text-base font-medium">整體評分</Label>
|
|
<div className="flex items-center space-x-2 mt-2">
|
|
{[1, 2, 3, 4, 5].map((rating) => (
|
|
<button key={rating} className="p-1 hover:bg-gray-100 rounded" type="button">
|
|
<Star className="h-8 w-8 text-gray-300 hover:text-yellow-400" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="summary">審查總結</Label>
|
|
<Textarea id="summary" placeholder="請輸入審查總結和建議..." className="min-h-[120px]" />
|
|
</div>
|
|
</div>
|
|
<DialogFooter className="flex space-x-2">
|
|
<Button variant="outline">儲存草稿</Button>
|
|
<Button className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white">
|
|
完成審查
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|