init2
This commit is contained in:
@@ -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>
|
||||
|
62
app/api/users/[id]/route.ts
Normal file
62
app/api/users/[id]/route.ts
Normal 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 }
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user