diff --git a/frontend/src/app/dashboard/users/page.tsx b/frontend/src/app/dashboard/users/page.tsx index 3f00bc2..af8fe04 100644 --- a/frontend/src/app/dashboard/users/page.tsx +++ b/frontend/src/app/dashboard/users/page.tsx @@ -18,16 +18,30 @@ import { } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Plus, Search, Trash2, Loader2, RefreshCw, Pencil, Eye, ChevronLeft, ChevronRight } from "lucide-react" +import { + Plus, + Search, + Trash2, + Loader2, + RefreshCw, + Pencil, + Eye, + ChevronLeft, + ChevronRight, + Users, + ShieldAlert, + UserCheck, + Clock +} from "lucide-react" import { usersApi, adminCompaniesApi, type ApiUser, type AdminCompany } from "@/lib/api" import { getCurrentUser, isAdminUser } from "@/lib/auth" import { toast } from "sonner" import { Skeleton } from "@/components/ui/skeleton" import { useTranslation } from "@/lib/i18n" +import { motion } from "framer-motion" -const userDateFormatter = new Intl.DateTimeFormat("en-US", { +const userDateFormatter = new Intl.DateTimeFormat("pt-BR", { dateStyle: "medium", - timeZone: "UTC", }) export default function AdminUsersPage() { @@ -39,12 +53,10 @@ export default function AdminUsersPage() { const [page, setPage] = useState(1) const [totalUsers, setTotalUsers] = useState(0) - // Dialog States const [isDialogOpen, setIsDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) - // Action States const [creating, setCreating] = useState(false) const [updating, setUpdating] = useState(false) const [deleting, setDeleting] = useState(false) @@ -54,7 +66,6 @@ export default function AdminUsersPage() { const [companies, setCompanies] = useState([]) const [currentUser, setCurrentUser] = useState(null) - // Form Data const [formData, setFormData] = useState({ name: "", email: "", @@ -68,7 +79,7 @@ export default function AdminUsersPage() { email: "", role: "", status: "", - password: "", // Optional for edits + password: "", }) useEffect(() => { @@ -79,7 +90,6 @@ export default function AdminUsersPage() { } setCurrentUser(user as ApiUser) loadUsers() - if (user?.role === 'superadmin') { loadCompanies() } @@ -89,58 +99,43 @@ export default function AdminUsersPage() { try { const data = await adminCompaniesApi.list(undefined, 1, 100) setCompanies(data.data || []) - } catch (error) { - console.error("Error loading companies:", error) - } + } catch (error) { } } const limit = 10 const totalPages = Math.max(1, Math.ceil(totalUsers / limit)) const loadUsers = async (targetPage = page) => { - console.log(`[USER_FLOW] Loading users page: ${targetPage}`) + const pageNum = typeof targetPage === 'number' ? targetPage : page try { setLoading(true) - const data = await usersApi.list({ page: targetPage, limit }) - console.log("[USER_FLOW] Users loaded:", data) + const data = await usersApi.list({ page: pageNum, limit }) setUsers(data?.data || []) setTotalUsers(data?.pagination?.total || 0) - setPage(data?.pagination?.page || targetPage) + setPage(data?.pagination?.page || pageNum) } catch (error) { - console.error("[USER_FLOW] Error loading users:", error) - toast.error(t('admin.users.messages.load_error')) + toast.error("Erro ao carregar usuários") } finally { setLoading(false) } } const handleCreate = async () => { - console.log("[USER_FLOW] Creating user with data:", formData) try { setCreating(true) - const payload = { - ...formData, - roles: [formData.role], // Helper for legacy backend needing array - } - await usersApi.create(payload) - toast.success(t('admin.users.messages.create_success')) + await usersApi.create({ ...formData, roles: [formData.role] }) + toast.success("Usuário criado com sucesso") setIsDialogOpen(false) setFormData({ name: "", email: "", password: "", role: "candidate", status: "active", companyId: "" }) - setPage(1) loadUsers(1) - } catch (error) { - console.error("[USER_FLOW] Error creating user:", error) - const message = error instanceof Error && error.message - ? error.message - : t('admin.users.messages.create_error') - toast.error(message) + } catch (error: any) { + toast.error(error.message || "Erro ao criar usuário") } finally { setCreating(false) } } - const handleView = (user: ApiUser) => { - console.log("[USER_FLOW] Viewing user:", user) + const handleEdit = (user: ApiUser, isViewing = false) => { setSelectedUser(user) setEditFormData({ name: user.name, @@ -149,522 +144,311 @@ export default function AdminUsersPage() { status: user.status || "active", password: "", }) - setViewing(true) - setIsEditDialogOpen(true) - } - - const handleEdit = (user: ApiUser) => { - console.log("[USER_FLOW] Editing user:", user) - setSelectedUser(user) - setEditFormData({ - name: user.name, - email: user.email, - role: user.role, - status: user.status || "active", - password: "", - }) - setViewing(false) + setViewing(isViewing) setIsEditDialogOpen(true) } const handleUpdate = async () => { if (!selectedUser) return - console.log("[USER_FLOW] Updating user:", selectedUser.id, "with data:", editFormData) try { setUpdating(true) - const payload: any = { - ...editFormData, - roles: [editFormData.role], - } - // Remove empty password if not changing - if (!payload.password) delete payload.password; - + const payload: any = { ...editFormData, roles: [editFormData.role] } + if (!payload.password) delete payload.password await usersApi.update(selectedUser.id, payload) - toast.success(t('admin.users.messages.update_success')) + toast.success("Usuário atualizado com sucesso") setIsEditDialogOpen(false) loadUsers() } catch (error) { - console.error("[USER_FLOW] Error updating user:", error) - toast.error(t('admin.users.messages.update_error')) + toast.error("Erro ao atualizar usuário") } finally { setUpdating(false) } } - const handleDeleteClick = (user: ApiUser) => { - console.log("[USER_FLOW] Delete click for user:", user) - setSelectedUser(user) - setIsDeleteDialogOpen(true) - } - const confirmDelete = async () => { if (!selectedUser) return - console.log("[USER_FLOW] Confirming delete for user:", selectedUser.id) - try { setDeleting(true) await usersApi.delete(selectedUser.id) - toast.success(t('admin.users.messages.delete_success')) - - // UI Update logic - if (users.length === 1 && page > 1) { - setPage(page - 1) - loadUsers(page - 1) - } else { - loadUsers(page) - } + toast.success("Usuário excluído com sucesso") + loadUsers(page) setIsDeleteDialogOpen(false) - setSelectedUser(null) - } catch (error: any) { - console.error("[USER_FLOW] Error deleting user:", error) - // Error handling matching user request to see logs - if (error.message && error.message.includes("403")) { - toast.error("You don't have permission to delete this user (403)") - } else { - toast.error(t('admin.users.messages.delete_error')) - } + } catch (error) { + toast.error("Erro ao excluir usuário") } finally { setDeleting(false) } } + const getRoleBadge = (role: string) => { + const variants: any = { + superadmin: "destructive", + admin: "default", + recruiter: "secondary", + candidate: "outline" + } + return {role} + } + const filteredUsers = users.filter( (user) => user.name?.toLowerCase().includes(searchTerm.toLowerCase()) || user.email?.toLowerCase().includes(searchTerm.toLowerCase()) ) - const getRoleBadge = (role: string) => { - const labels: Record = { - superadmin: t('admin.users.roles.superadmin'), - admin: t('admin.users.roles.admin'), - recruiter: t('admin.users.roles.recruiter'), - candidate: t('admin.users.roles.candidate'), - company: t('admin.users.roles.admin') // Fallback for 'company' role legacy - } - const colors: Record = { - superadmin: "destructive", - admin: "default", - recruiter: "secondary", - candidate: "outline", - company: "default" - } - const label = labels[role] || role || "User" - return {label} - } - return ( -
+
{/* Header */} -
-
-

{t('admin.users.title')}

-

{t('admin.users.subtitle')}

-
-
- - { - setIsDialogOpen(open) - if (!open) { - setFormData({ name: "", email: "", password: "", role: "candidate", status: "active", companyId: "" }) - } - }}> + - - + - {t('admin.users.create_dialog.title')} - {t('admin.users.create_dialog.description')} + Criar Novo Usuário + Preencha os dados para adicionar um novo membro.
- - setFormData({ ...formData, name: e.target.value })} - placeholder={t('admin.users.table.name')} - /> + + setFormData({ ...formData, name: e.target.value })} />
- - setFormData({ ...formData, email: e.target.value })} - placeholder={t('admin.users.table.email')} - /> + + setFormData({ ...formData, email: e.target.value })} />
- - setFormData({ ...formData, password: e.target.value })} - placeholder={t('admin.users.form.password')} - /> + + setFormData({ ...formData, password: e.target.value })} />
- {currentUser?.role === 'superadmin' && ( +
- - setFormData({ ...formData, role: v })}> + - {companies.map((company) => ( - - {company.name} - - ))} + Admin + Recrutador + Candidato + + +
+
+ +
- )} -
- - -
-
- -
- +
- - {/* Edit / View Dialog */} - - - - {viewing ? t('admin.users.edit_dialog.title_view') : t('admin.users.edit_dialog.title_edit')} - {viewing ? t('admin.users.edit_dialog.description_view') : t('admin.users.edit_dialog.description_edit')} - -
-
- - setEditFormData({ ...editFormData, name: e.target.value })} - placeholder={t('admin.users.table.name')} - readOnly={viewing} - disabled={viewing} - /> -
-
- - setEditFormData({ ...editFormData, email: e.target.value })} - placeholder={t('admin.users.table.email')} - readOnly={viewing} - disabled={viewing} - /> -
- {!viewing && ( -
- - setEditFormData({ ...editFormData, password: e.target.value })} - placeholder="Leave blank to keep current" - /> -
- )} -
- - -
-
- - -
-
- - {viewing ? ( - <> - - - - ) : ( - <> - - - - )} - -
-
- - {/* Delete Confirmation Dialog */} - - - - {t('admin.users.delete_confirm.title')} - - {t('admin.users.delete_confirm.description')} - - -
- {selectedUser && ( -
-

Name: {selectedUser.name}

-

Email: {selectedUser.email}

-

ID: {selectedUser.id}

-
- )} -
- - - - -
-
- {/* Stats */} -
- - - {t('admin.users.total')} - {totalUsers} +
+ + + Total de Usuários + +
{totalUsers}
- - - {t('admin.users.admins')} - - {users.filter((u) => u.role === "superadmin" || u.role === "admin" || u.role === "admin").length} - + + + Administradores + +
{users.filter(u => u.role === "admin" || u.role === "superadmin").length}
- - - {t('admin.users.recruiters')} - {users.filter((u) => u.role === "recruiter").length} + + + Ativos agora + +
{users.filter(u => u.status === "active").length}
- - - {t('admin.users.candidates')} - {users.filter((u) => u.role === "candidate" || u.role === "candidate").length} + + + Novos (24h) + +
--
- {/* Table */} - - + {/* Main Content */} + +
setSearchTerm(e.target.value)} - className="pl-10" + className="pl-10 h-11 border-muted-foreground/20" />
- + {loading ? ( -
- {[...Array(5)].map((_, i) => ( -
- -
- ))} +
+ {[...Array(5)].map((_, i) => )}
) : ( -
-
- - - - {t('admin.users.table.name')} - {t('admin.users.table.role')} - {t('admin.users.table.status')} - {t('admin.users.table.created')} - {t('admin.users.table.actions')} +
+ + + Nome + E-mail + Papel + Status + Criado em + Ações + + + + {filteredUsers.length === 0 ? ( + Nenhum usuário encontrado + ) : ( + filteredUsers.map((user) => ( + + {user.name} + {user.email} + {getRoleBadge(user.role)} + + + {user.status === "active" ? "Ativo" : "Inativo"} + + + + {user.created_at || (user as any).createdAt ? userDateFormatter.format(new Date(user.created_at || (user as any).createdAt)) : "-"} + + +
+ + + +
+
- - - {filteredUsers.length === 0 ? ( - - - {t('admin.users.table.no_users')} - - - ) : ( - filteredUsers.map((user) => ( - - {user.name} - {getRoleBadge(user.role)} - - - {user.status ? user.status.toUpperCase() : "UNKNOWN"} - - - - {user.created_at || (user as any).createdAt ? userDateFormatter.format(new Date(user.created_at || (user as any).createdAt)) : "-"} - - -
- - - -
-
-
- )) - )} -
-
-
-
- - {totalUsers === 0 - ? t('admin.users.pagination.no_users_display') - : t('admin.users.pagination.showing', { start: (page - 1) * limit + 1, end: Math.min(page * limit, totalUsers), total: totalUsers })} - -
- - - {t('admin.users.pagination.page', { current: page, total: totalPages })} - - -
+ )) + )} + + + )} + + {!loading && totalPages > 1 && ( +
+

+ Página {page} de {totalPages} +

+
+ +
)} + + {/* Edit / View Dialog */} + + + + {viewing ? "Detalhes do Usuário" : "Editar Usuário"} + +
+
+ + setEditFormData({ ...editFormData, name: e.target.value })} readOnly={viewing} /> +
+
+ + setEditFormData({ ...editFormData, email: e.target.value })} readOnly={viewing} /> +
+ {!viewing && ( +
+ + setEditFormData({ ...editFormData, password: e.target.value })} /> +
+ )} +
+
+ + +
+
+ + +
+
+
+ + + {!viewing && ( + + )} + +
+
+ + setIsDeleteDialogOpen(false)} + onConfirm={confirmDelete} + title="Excluir Usuário" + description={`Tem certeza que deseja excluir ${selectedUser?.name}?`} + />
) }