Files
ExecuBoard/app/admin/page.tsx
2025-08-01 01:19:19 +08:00

704 lines
37 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, useEffect } 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 { Switch } from "@/components/ui/switch"
import { Users, Settings, Target, MessageSquare, Plus, Edit, Trash2, Shield, Database, Bell } from "lucide-react"
// 用戶介面
interface User {
id: string
name: string
email: string
role: "executive" | "manager" | "hr"
avatar_url?: string
created_at: string
updated_at: string
}
// 用戶顏色映射
const getUserColor = (id: string) => {
const colors = ["emerald", "blue", "purple", "orange", "cyan", "pink", "indigo", "yellow"]
const index = id.charCodeAt(id.length - 1) % colors.length
return colors[index]
}
const kpiTemplates = [
{ id: 1, name: "營收成長率", category: "財務", defaultWeight: 30, isActive: true, usageCount: 15, color: "emerald" },
{ id: 2, name: "團隊滿意度", category: "團隊", defaultWeight: 25, isActive: true, usageCount: 12, color: "blue" },
{ id: 3, name: "市場佔有率", category: "營運", defaultWeight: 20, isActive: true, usageCount: 8, color: "purple" },
{ id: 4, name: "創新指數", category: "創新", defaultWeight: 25, isActive: false, usageCount: 3, color: "orange" },
{ id: 5, name: "客戶滿意度", category: "營運", defaultWeight: 20, isActive: true, usageCount: 10, color: "cyan" },
{ id: 6, name: "成本控制率", category: "財務", defaultWeight: 15, isActive: true, usageCount: 6, color: "red" },
]
const questionTemplates = [
{ id: 1, question: "您如何評價本季度的整體表現?", type: "評分", category: "績效", isRequired: true, color: "blue" },
{ id: 2, question: "本期間您的主要成就是什麼?", type: "文字", category: "績效", isRequired: true, color: "emerald" },
{
id: 3,
question: "您面臨了哪些挑戰,如何克服?",
type: "文字",
category: "挑戰",
isRequired: false,
color: "yellow",
},
{ id: 4, question: "您如何有效領導團隊?", type: "評分", category: "領導力", isRequired: true, color: "purple" },
{ id: 5, question: "下一季度的目標是什麼?", type: "文字", category: "目標", isRequired: true, color: "orange" },
]
const systemSettings = [
{ id: 1, name: "自動提醒", description: "自動發送審查提醒通知", enabled: true, category: "通知" },
{ id: 2, name: "郵件通知", description: "透過郵件發送系統通知", enabled: true, category: "通知" },
{ id: 3, name: "資料備份", description: "每日自動備份系統資料", enabled: true, category: "安全" },
{ id: 4, name: "雙重驗證", description: "要求使用者啟用雙重驗證", enabled: false, category: "安全" },
{ id: 5, name: "審計日誌", description: "記錄所有系統操作日誌", enabled: true, category: "安全" },
]
export default function AdminPanel() {
const [activeTab, setActiveTab] = useState("users")
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [dialogType, setDialogType] = useState("")
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [editingUser, setEditingUser] = useState<User | null>(null)
const [editForm, setEditForm] = useState({
name: "",
email: "",
role: "manager" as "executive" | "manager" | "hr"
})
// 載入用戶數據
useEffect(() => {
fetchUsers()
}, [])
const fetchUsers = async () => {
try {
setLoading(true)
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('載入用戶數據失敗')
}
const data = await response.json()
setUsers(data)
} catch (err) {
setError(err instanceof Error ? err.message : '載入用戶數據失敗')
} finally {
setLoading(false)
}
}
const handleOpenDialog = (type: string) => {
setDialogType(type)
setIsDialogOpen(true)
}
const handleEditUser = (user: User) => {
setEditingUser(user)
setEditForm({
name: user.name,
email: user.email,
role: user.role
})
setDialogType("edit-user")
setIsDialogOpen(true)
}
const handleUpdateUser = async () => {
if (!editingUser) return
try {
const response = await fetch(`/api/users/${editingUser.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(editForm),
})
if (!response.ok) {
throw new Error('更新用戶失敗')
}
// 重新載入用戶數據
await fetchUsers()
setIsDialogOpen(false)
setEditingUser(null)
setEditForm({ name: "", email: "", role: "manager" })
} catch (err) {
setError(err instanceof Error ? err.message : '更新用戶失敗')
}
}
const handleDeleteUser = async (userId: string) => {
if (!confirm('確定要刪除此用戶嗎?此操作無法復原。')) {
return
}
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'DELETE',
})
if (!response.ok) {
throw new Error('刪除用戶失敗')
}
// 重新載入用戶數據
await fetchUsers()
} catch (err) {
setError(err instanceof Error ? err.message : '刪除用戶失敗')
}
}
const adminStats = {
totalUsers: users.length,
activeUsers: users.length, // 所有用戶都視為活躍
totalKPIs: kpiTemplates.length,
activeKPIs: kpiTemplates.filter((k) => k.isActive).length,
totalQuestions: questionTemplates.length,
requiredQuestions: questionTemplates.filter((q) => q.isRequired).length,
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-purple-50 to-pink-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-purple-600 to-pink-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={() => handleOpenDialog("backup")}
>
<Database className="h-4 w-4 mr-2" />
</Button>
<Button
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white shadow-lg"
onClick={() => handleOpenDialog("user")}
>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 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>
<Users className="h-4 w-4 text-blue-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-blue-800">{adminStats.totalUsers}</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>
<Shield className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-800">{adminStats.activeUsers}</div>
</CardContent>
</Card>
<Card className="shadow-lg bg-gradient-to-br from-purple-50 to-violet-100 border-0">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-purple-700">KPI </CardTitle>
<Target className="h-4 w-4 text-purple-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-purple-800">{adminStats.totalKPIs}</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"> KPI</CardTitle>
<Settings className="h-4 w-4 text-orange-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-orange-800">{adminStats.activeKPIs}</div>
</CardContent>
</Card>
<Card className="shadow-lg bg-gradient-to-br from-pink-50 to-rose-100 border-0">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-pink-700"></CardTitle>
<MessageSquare className="h-4 w-4 text-pink-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-pink-800">{adminStats.totalQuestions}</div>
</CardContent>
</Card>
<Card className="shadow-lg bg-gradient-to-br from-indigo-50 to-blue-100 border-0">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-indigo-700"></CardTitle>
<Bell className="h-4 w-4 text-indigo-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-indigo-800">{adminStats.requiredQuestions}</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-4">
<TabsTrigger value="users"></TabsTrigger>
<TabsTrigger value="kpi-templates">KPI </TabsTrigger>
<TabsTrigger value="questions"></TabsTrigger>
<TabsTrigger value="settings"></TabsTrigger>
</TabsList>
{/* Users Tab */}
<TabsContent value="users" className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold"></h3>
<Button onClick={() => handleOpenDialog("user")}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
{loading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600 mx-auto"></div>
<p className="mt-2 text-gray-600">...</p>
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => {
const userColor = getUserColor(user.id)
return (
<TableRow key={user.id}>
<TableCell>
<div className="flex items-center space-x-3">
<Avatar className="h-8 w-8">
<AvatarFallback
className={`bg-gradient-to-br ${
userColor === "emerald"
? "from-emerald-400 to-green-500"
: userColor === "blue"
? "from-blue-400 to-cyan-500"
: userColor === "purple"
? "from-purple-400 to-violet-500"
: userColor === "orange"
? "from-orange-400 to-amber-500"
: userColor === "cyan"
? "from-cyan-400 to-teal-500"
: userColor === "pink"
? "from-pink-400 to-rose-500"
: userColor === "indigo"
? "from-indigo-400 to-blue-500"
: "from-yellow-400 to-amber-500"
} text-white`}
>
{user.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<div className="font-medium">{user.name}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant={user.role === "executive" ? "default" : "secondary"}>
{user.role === "executive" ? "高階主管" : user.role === "manager" ? "經理" : "HR"}
</Badge>
</TableCell>
<TableCell className="text-sm text-gray-500">
{new Date(user.created_at).toLocaleDateString('zh-TW')}
</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditUser(user)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
className="text-red-500"
onClick={() => handleDeleteUser(user.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
)}
</TabsContent>
{/* KPI Templates Tab */}
<TabsContent value="kpi-templates" className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold">KPI </h3>
<Button onClick={() => handleOpenDialog("kpi")}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{kpiTemplates.map((template) => (
<Card
key={template.id}
className={`shadow-md hover:shadow-lg transition-shadow ${
template.color === "emerald"
? "bg-gradient-to-br from-emerald-50 to-green-100"
: template.color === "blue"
? "bg-gradient-to-br from-blue-50 to-cyan-100"
: template.color === "purple"
? "bg-gradient-to-br from-purple-50 to-violet-100"
: template.color === "orange"
? "bg-gradient-to-br from-orange-50 to-amber-100"
: template.color === "cyan"
? "bg-gradient-to-br from-cyan-50 to-teal-100"
: "bg-gradient-to-br from-red-50 to-pink-100"
}`}
>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base">{template.name}</CardTitle>
<Badge variant={template.isActive ? "default" : "secondary"}>
{template.isActive ? "啟用" : "停用"}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-600">:</span>
<span className="font-medium">{template.category}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">:</span>
<span className="font-medium">{template.defaultWeight}%</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">使:</span>
<span className="font-medium">{template.usageCount}</span>
</div>
<div className="flex justify-between pt-2 border-t">
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
{/* Questions Tab */}
<TabsContent value="questions" className="space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-semibold"></h3>
<Button onClick={() => handleOpenDialog("question")}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
<div className="space-y-3">
{questionTemplates.map((question) => (
<Card key={question.id} className="shadow-md">
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<Badge
variant="outline"
className={`${
question.color === "blue"
? "border-blue-300 text-blue-700 bg-blue-50"
: question.color === "emerald"
? "border-emerald-300 text-emerald-700 bg-emerald-50"
: question.color === "yellow"
? "border-yellow-300 text-yellow-700 bg-yellow-50"
: question.color === "purple"
? "border-purple-300 text-purple-700 bg-purple-50"
: "border-orange-300 text-orange-700 bg-orange-50"
}`}
>
{question.category}
</Badge>
<Badge variant={question.type === "評分" ? "default" : "secondary"}>
{question.type}
</Badge>
{question.isRequired && (
<Badge variant="destructive" className="text-xs">
</Badge>
)}
</div>
<p className="text-gray-900 font-medium">{question.question}</p>
</div>
<div className="flex items-center space-x-2 ml-4">
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
{/* Settings Tab */}
<TabsContent value="settings" className="space-y-4">
<h3 className="text-lg font-semibold"></h3>
<div className="space-y-4">
{systemSettings.map((setting) => (
<Card key={setting.id} className="shadow-md">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1">
<h4 className="font-medium text-gray-900">{setting.name}</h4>
<Badge
variant="outline"
className={
setting.category === "通知"
? "border-blue-300 text-blue-700 bg-blue-50"
: "border-red-300 text-red-700 bg-red-50"
}
>
{setting.category}
</Badge>
</div>
<p className="text-sm text-gray-600">{setting.description}</p>
</div>
<Switch checked={setting.enabled} />
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* Dialog for various actions */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
{dialogType === "user"
? "新增用戶"
: dialogType === "edit-user"
? "編輯用戶"
: dialogType === "kpi"
? "新增 KPI 模板"
: dialogType === "question"
? "新增審查問題"
: "備份資料"}
</DialogTitle>
<DialogDescription>
{dialogType === "user"
? "建立新的系統用戶帳號"
: dialogType === "edit-user"
? "修改用戶資訊"
: dialogType === "kpi"
? "建立新的 KPI 模板"
: dialogType === "question"
? "建立新的審查問題"
: "備份系統資料到安全位置"}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{(dialogType === "user" || dialogType === "edit-user") && (
<>
<div>
<Label htmlFor="userName"></Label>
<Input
id="userName"
placeholder="輸入用戶姓名"
value={editForm.name}
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
/>
</div>
<div>
<Label htmlFor="userEmail"></Label>
<Input
id="userEmail"
type="email"
placeholder="輸入電子郵件"
value={editForm.email}
onChange={(e) => setEditForm({ ...editForm, email: e.target.value })}
/>
</div>
<div>
<Label htmlFor="userRole"></Label>
<Select
value={editForm.role}
onValueChange={(value: "executive" | "manager" | "hr") =>
setEditForm({ ...editForm, role: value })
}
>
<SelectTrigger>
<SelectValue placeholder="選擇角色" />
</SelectTrigger>
<SelectContent>
<SelectItem value="executive"></SelectItem>
<SelectItem value="manager"></SelectItem>
<SelectItem value="hr">HR</SelectItem>
</SelectContent>
</Select>
</div>
</>
)}
{dialogType === "kpi" && (
<>
<div>
<Label htmlFor="kpiName">KPI </Label>
<Input id="kpiName" placeholder="輸入 KPI 名稱" />
</div>
<div>
<Label htmlFor="kpiCategory"></Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="選擇類別" />
</SelectTrigger>
<SelectContent>
<SelectItem value="財務"></SelectItem>
<SelectItem value="團隊"></SelectItem>
<SelectItem value="營運"></SelectItem>
<SelectItem value="創新"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="defaultWeight"> (%)</Label>
<Input id="defaultWeight" type="number" placeholder="0-100" />
</div>
</>
)}
{dialogType === "question" && (
<>
<div>
<Label htmlFor="questionText"></Label>
<Textarea id="questionText" placeholder="輸入審查問題..." />
</div>
<div>
<Label htmlFor="questionType"></Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="選擇類型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="評分"></SelectItem>
<SelectItem value="文字"></SelectItem>
<SelectItem value="選擇"></SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="questionCategory"></Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="選擇類別" />
</SelectTrigger>
<SelectContent>
<SelectItem value="績效"></SelectItem>
<SelectItem value="領導力"></SelectItem>
<SelectItem value="目標"></SelectItem>
<SelectItem value="挑戰"></SelectItem>
</SelectContent>
</Select>
</div>
</>
)}
{dialogType === "backup" && (
<div className="text-center py-4">
<Database className="h-12 w-12 mx-auto text-gray-400 mb-4" />
<p className="text-gray-600"></p>
<p className="text-sm text-gray-500 mt-2"></p>
</div>
)}
</div>
<DialogFooter>
<Button
type="submit"
className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
onClick={dialogType === "edit-user" ? handleUpdateUser : undefined}
>
{dialogType === "backup" ? "開始備份" : dialogType === "edit-user" ? "更新" : "建立"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
)
}