161 lines
6.1 KiB
TypeScript
161 lines
6.1 KiB
TypeScript
/**
|
||
* 部门管理组件
|
||
* 提取自 app/admin/page.tsx 的部门管理功能
|
||
*/
|
||
|
||
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import type { Department } from '@/hooks/useAdminState'
|
||
|
||
interface DepartmentManagementProps {
|
||
departments: Department[]
|
||
setDepartments: React.Dispatch<React.SetStateAction<Department[]>>
|
||
showToast: (message: string, type?: 'success' | 'error') => void
|
||
fetchDepartments: () => Promise<void>
|
||
}
|
||
|
||
export default function DepartmentManagement({
|
||
departments,
|
||
setDepartments,
|
||
showToast,
|
||
fetchDepartments,
|
||
}: DepartmentManagementProps) {
|
||
const [newDepartmentName, setNewDepartmentName] = useState('')
|
||
const [addingDepartment, setAddingDepartment] = useState(false)
|
||
|
||
// 添加部门
|
||
const handleAddDepartment = async () => {
|
||
if (!newDepartmentName.trim()) {
|
||
showToast('请输入部门名称', 'error')
|
||
return
|
||
}
|
||
|
||
setAddingDepartment(true)
|
||
try {
|
||
const response = await fetch('/api/departments', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name: newDepartmentName.trim() }),
|
||
})
|
||
|
||
if (response.ok) {
|
||
await fetchDepartments()
|
||
setNewDepartmentName('')
|
||
showToast('部门添加成功')
|
||
} else {
|
||
const error = await response.json()
|
||
showToast(error.error || '添加失败', 'error')
|
||
}
|
||
} catch (error) {
|
||
console.error('Add department error:', error)
|
||
showToast('添加失败,请稍后重试', 'error')
|
||
} finally {
|
||
setAddingDepartment(false)
|
||
}
|
||
}
|
||
|
||
// 删除部门
|
||
const handleDeleteDepartment = async (id: string, name: string) => {
|
||
if (!confirm(`确定要删除部门"${name}"吗?`)) return
|
||
|
||
try {
|
||
const response = await fetch(`/api/departments?id=${id}`, {
|
||
method: 'DELETE',
|
||
})
|
||
|
||
if (response.ok) {
|
||
setDepartments(prev => prev.filter(d => d.id !== id))
|
||
showToast('部门删除成功')
|
||
} else {
|
||
const error = await response.json()
|
||
showToast(error.error || '删除失败', 'error')
|
||
}
|
||
} catch (error) {
|
||
console.error('Delete department error:', error)
|
||
showToast('删除失败,请稍后重试', 'error')
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="card-apple">
|
||
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">部门管理</h2>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">管理系统中的所有部门</p>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
{/* 添加部门 */}
|
||
<div className="flex gap-3 mb-6">
|
||
<input
|
||
type="text"
|
||
placeholder="输入新部门名称"
|
||
value={newDepartmentName}
|
||
onChange={(e) => setNewDepartmentName(e.target.value)}
|
||
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white focus:ring-2 focus:ring-gray-400 outline-none"
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' && !addingDepartment) {
|
||
handleAddDepartment()
|
||
}
|
||
}}
|
||
/>
|
||
<button
|
||
onClick={handleAddDepartment}
|
||
disabled={addingDepartment}
|
||
className="px-6 py-2 bg-gray-900 dark:bg-gray-700 text-white rounded-lg hover:bg-gray-800 dark:hover:bg-gray-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||
>
|
||
{addingDepartment ? (
|
||
<>
|
||
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||
</svg>
|
||
添加中...
|
||
</>
|
||
) : (
|
||
<>
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||
</svg>
|
||
添加部门
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
|
||
{/* 部门列表 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{departments.map((dept) => (
|
||
<div
|
||
key={dept.id}
|
||
className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-600 rounded-lg hover:shadow-md transition-shadow"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||
</svg>
|
||
<span className="font-medium text-gray-900 dark:text-white">{dept.name}</span>
|
||
</div>
|
||
<button
|
||
onClick={() => handleDeleteDepartment(dept.id, dept.name)}
|
||
className="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors"
|
||
title="删除部门"
|
||
>
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{departments.length === 0 && (
|
||
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
|
||
暂无部门,请添加新部门
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|