diff --git a/fe-admin/src/pages/system/UsersPage.tsx b/fe-admin/src/pages/system/UsersPage.tsx
index 0d7f7f3..b391b2b 100644
--- a/fe-admin/src/pages/system/UsersPage.tsx
+++ b/fe-admin/src/pages/system/UsersPage.tsx
@@ -1,6 +1,6 @@
import { useState, type FormEvent } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
-import { KeyRound, Plus, Shield, Unlock, Users, CheckCircle2, XCircle } from 'lucide-react'
+import { Building2, KeyRound, Pencil, Plus, Shield, Unlock, Users, CheckCircle2, XCircle } from 'lucide-react'
import { toast } from 'sonner'
import { PageHeader } from '@/components/PageHeader'
import { DataTable, Pagination, type Column } from '@/components/DataTable'
@@ -8,22 +8,43 @@ import { PermissionGuard } from '@/components/PermissionGuard'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label'
+import { Select } from '@/components/ui/Select'
import { Dialog } from '@/components/ui/Dialog'
import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError'
import { MenuKeys } from '@/lib/menuKeys'
-import type { Paged } from '@/types/master'
-import { AVAILABLE_ROLES, RoleLabel, type User } from '@/types/users'
+import type { Department, Paged } from '@/types/master'
+import { AVAILABLE_ROLES, RoleShortName, RoleLabel, type User } from '@/types/users'
const fmtDate = (s: string) => new Date(s).toLocaleDateString('vi-VN')
+type CreateForm = {
+ email: string
+ fullName: string
+ password: string
+ roles: string[]
+ departmentId: string
+ position: string
+}
+type EditForm = {
+ id: string
+ fullName: string
+ isActive: boolean
+ departmentId: string
+ position: string
+}
+
+const emptyCreate: CreateForm = { email: '', fullName: '', password: '', roles: [], departmentId: '', position: '' }
+
export function UsersPage() {
const qc = useQueryClient()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const [createOpen, setCreateOpen] = useState(false)
- const [createForm, setCreateForm] = useState({ email: '', fullName: '', password: '', roles: [] as string[] })
+ const [createForm, setCreateForm] = useState
(emptyCreate)
+
+ const [editForm, setEditForm] = useState(null)
const [rolesModal, setRolesModal] = useState(null)
const [roleSelection, setRoleSelection] = useState([])
@@ -37,15 +58,46 @@ export function UsersPage() {
(await api.get>('/users', { params: { page, pageSize: 20, search: search || undefined } })).data,
})
+ const departments = useQuery({
+ queryKey: ['departments-all'],
+ queryFn: async () => (await api.get>('/departments', { params: { page: 1, pageSize: 200 } })).data.items,
+ })
+
const createMut = useMutation({
mutationFn: async () => {
- await api.post('/users', createForm)
+ await api.post('/users', {
+ email: createForm.email,
+ fullName: createForm.fullName,
+ password: createForm.password,
+ roles: createForm.roles,
+ departmentId: createForm.departmentId || null,
+ position: createForm.position || null,
+ })
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['users'] })
toast.success('Đã tạo user')
setCreateOpen(false)
- setCreateForm({ email: '', fullName: '', password: '', roles: [] })
+ setCreateForm(emptyCreate)
+ },
+ onError: err => toast.error(getErrorMessage(err)),
+ })
+
+ const editMut = useMutation({
+ mutationFn: async () => {
+ if (!editForm) return
+ await api.put(`/users/${editForm.id}`, {
+ id: editForm.id,
+ fullName: editForm.fullName,
+ isActive: editForm.isActive,
+ departmentId: editForm.departmentId || null,
+ position: editForm.position || null,
+ })
+ },
+ onSuccess: () => {
+ qc.invalidateQueries({ queryKey: ['users'] })
+ toast.success('Đã lưu')
+ setEditForm(null)
},
onError: err => toast.error(getErrorMessage(err)),
})
@@ -86,7 +138,11 @@ export function UsersPage() {
})
const toggleActiveMut = useMutation({
- mutationFn: (u: User) => api.put(`/users/${u.id}`, { id: u.id, fullName: u.fullName, isActive: !u.isActive }),
+ mutationFn: (u: User) =>
+ api.put(`/users/${u.id}`, {
+ id: u.id, fullName: u.fullName, isActive: !u.isActive,
+ departmentId: u.departmentId, position: u.position,
+ }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['users'] })
toast.success('Đã cập nhật trạng thái')
@@ -98,11 +154,16 @@ export function UsersPage() {
setRolesModal(u)
setRoleSelection([...u.roles])
}
+ function openEdit(u: User) {
+ setEditForm({
+ id: u.id, fullName: u.fullName, isActive: u.isActive,
+ departmentId: u.departmentId ?? '', position: u.position ?? '',
+ })
+ }
function toggleRole(r: string) {
setRoleSelection(sel => (sel.includes(r) ? sel.filter(x => x !== r) : [...sel, r]))
}
-
function toggleCreateRole(r: string) {
setCreateForm(f => ({
...f,
@@ -113,6 +174,17 @@ export function UsersPage() {
const columns: Column[] = [
{ key: 'email', header: 'Email', render: u => {u.email} },
{ key: 'fullName', header: 'Họ tên', render: u => u.fullName },
+ {
+ key: 'departmentName',
+ header: 'Phòng ban',
+ width: 'w-44',
+ render: u => (
+
+
{u.departmentName ?? — }
+ {u.position &&
{u.position}
}
+
+ ),
+ },
{
key: 'roles',
header: 'Vai trò',
@@ -120,8 +192,12 @@ export function UsersPage() {
{u.roles.length === 0 && — }
{u.roles.map(r => (
-
- {RoleLabel[r] ?? r}
+
+ {RoleShortName[r] ?? r}
))}
@@ -130,39 +206,37 @@ export function UsersPage() {
{
key: 'isActive',
header: 'Active',
- width: 'w-24',
+ width: 'w-20',
align: 'center',
render: u =>
- u.isActive ? (
-
- ) : (
-
- ),
+ u.isActive ? : ,
},
{
key: 'isLocked',
header: 'Locked',
- width: 'w-24',
+ width: 'w-20',
align: 'center',
render: u =>
u.isLocked ? (
- Locked
) : (
—
),
},
- { key: 'createdAt', header: 'Ngày tạo', width: 'w-28', render: u => fmtDate(u.createdAt) },
+ { key: 'createdAt', header: 'Ngày tạo', width: 'w-24', render: u => fmtDate(u.createdAt) },
{
key: 'actions',
header: '',
align: 'right',
- width: 'w-52',
+ width: 'w-56',
render: u => (
+ openEdit(u)} title="Sửa thông tin">
+
+
openRoles(u)} title="Gán role">
@@ -192,7 +266,7 @@ export function UsersPage() {
Người dùng
}
- description="Tạo user + gán role để test quyền với non-admin."
+ description="Tạo user + gán phòng ban + gán role để test quyền với non-admin."
actions={
setCreateOpen(true)}>
@@ -231,21 +305,40 @@ export function UsersPage() {
}
>