This commit is contained in:
2025-08-01 01:19:19 +08:00
parent d96b0a511d
commit 4cc00b7f06
2 changed files with 295 additions and 135 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useState } from "react"
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"
@@ -22,53 +22,23 @@ 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"
// Mock data with colors
const users = [
{
id: 1,
name: "陳雅雯",
email: "sarah.chen@company.com",
role: "executive",
status: "active",
lastLogin: "2024-02-10",
department: "業務部",
position: "業務副總",
color: "emerald",
},
{
id: 2,
name: "王志明",
email: "mike.wang@company.com",
role: "manager",
status: "active",
lastLogin: "2024-02-09",
department: "技術部",
position: "技術長",
color: "blue",
},
{
id: 3,
name: "李美玲",
email: "lisa.lee@company.com",
role: "executive",
status: "inactive",
lastLogin: "2024-01-28",
department: "行銷部",
position: "行銷長",
color: "purple",
},
{
id: 4,
name: "張建國",
email: "john.chang@company.com",
role: "manager",
status: "active",
lastLogin: "2024-02-08",
department: "研發部",
position: "研發總監",
color: "orange",
},
]
// 用戶介面
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" },
@@ -106,15 +76,103 @@ 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.filter((u) => u.status === "active").length,
activeUsers: users.length, // 所有用戶都視為活躍
totalKPIs: kpiTemplates.length,
activeKPIs: kpiTemplates.filter((k) => k.isActive).length,
totalQuestions: questionTemplates.length,
@@ -242,74 +300,97 @@ export default function AdminPanel() {
</Button>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead>/</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<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 ${
user.color === "emerald"
? "from-emerald-400 to-green-500"
: user.color === "blue"
? "from-blue-400 to-cyan-500"
: user.color === "purple"
? "from-purple-400 to-violet-500"
: "from-orange-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>
<div>
<div className="font-medium">{user.department}</div>
<div className="text-sm text-gray-500">{user.position}</div>
</div>
</TableCell>
<TableCell>
<Badge variant={user.role === "executive" ? "default" : "secondary"}>
{user.role === "executive" ? "高階主管" : user.role === "manager" ? "經理" : "HR"}
</Badge>
</TableCell>
<TableCell>
<Badge variant={user.status === "active" ? "default" : "secondary"}>
{user.status === "active" ? "啟用" : "停用"}
</Badge>
</TableCell>
<TableCell className="text-sm text-gray-500">{user.lastLogin}</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" className="text-red-500">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
{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>
))}
</TableBody>
</Table>
</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 */}
@@ -474,36 +555,56 @@ export default function AdminPanel() {
<DialogTitle className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">
{dialogType === "user"
? "新增用戶"
: dialogType === "kpi"
? "新增 KPI 模板"
: dialogType === "question"
? "新增審查問題"
: "備份資料"}
: dialogType === "edit-user"
? "編輯用戶"
: dialogType === "kpi"
? "新增 KPI 模板"
: dialogType === "question"
? "新增審查問題"
: "備份資料"}
</DialogTitle>
<DialogDescription>
{dialogType === "user"
? "建立新的系統用戶帳號"
: dialogType === "kpi"
? "建立新的 KPI 模板"
: dialogType === "question"
? "建立新的審查問題"
: "備份系統資料到安全位置"}
: dialogType === "edit-user"
? "修改用戶資訊"
: dialogType === "kpi"
? "建立新的 KPI 模板"
: dialogType === "question"
? "建立新的審查問題"
: "備份系統資料到安全位置"}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{dialogType === "user" && (
{(dialogType === "user" || dialogType === "edit-user") && (
<>
<div>
<Label htmlFor="userName"></Label>
<Input id="userName" placeholder="輸入用戶姓名" />
<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="輸入電子郵件" />
<Input
id="userEmail"
type="email"
placeholder="輸入電子郵件"
value={editForm.email}
onChange={(e) => setEditForm({ ...editForm, email: e.target.value })}
/>
</div>
<div>
<Label htmlFor="userRole"></Label>
<Select>
<Select
value={editForm.role}
onValueChange={(value: "executive" | "manager" | "hr") =>
setEditForm({ ...editForm, role: value })
}
>
<SelectTrigger>
<SelectValue placeholder="選擇角色" />
</SelectTrigger>
@@ -514,10 +615,6 @@ export default function AdminPanel() {
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="department"></Label>
<Input id="department" placeholder="輸入部門名稱" />
</div>
</>
)}
{dialogType === "kpi" && (
@@ -593,8 +690,9 @@ export default function AdminPanel() {
<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 === "backup" ? "開始備份" : dialogType === "edit-user" ? "更新" : "建立"}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next/server'
import { userService } from '@/lib/database'
// 更新用戶
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json()
const { name, email, role, avatar_url } = body
// 驗證必填欄位
if (!name || !email || !role) {
return NextResponse.json(
{ error: '姓名、電子郵件和角色為必填欄位' },
{ status: 400 }
)
}
// 驗證角色
const validRoles = ['executive', 'manager', 'hr']
if (!validRoles.includes(role)) {
return NextResponse.json(
{ error: '無效的角色類型' },
{ status: 400 }
)
}
const updatedUser = await userService.updateUser(params.id, {
name,
email,
role,
avatar_url
})
return NextResponse.json(updatedUser)
} catch (error) {
console.error('更新用戶失敗:', error)
return NextResponse.json(
{ error: '更新用戶失敗' },
{ status: 500 }
)
}
}
// 刪除用戶
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await userService.deleteUser(params.id)
return NextResponse.json({ message: '用戶刪除成功' })
} catch (error) {
console.error('刪除用戶失敗:', error)
return NextResponse.json(
{ error: '刪除用戶失敗' },
{ status: 500 }
)
}
}