From 6d25f5103b826ba790ce9a79ec5026cedb75dab1 Mon Sep 17 00:00:00 2001 From: Esteban Date: Mon, 26 Jan 2026 11:45:01 -0600 Subject: [PATCH] Add users and roles API integration to UsersPage Created API modules for users and roles management: - Added src/api/roles.ts with getAllRoles, getRoleById, createRole, etc. - Added src/api/users.ts with getAllUsers, createUser, updateUser, etc. Updated UsersPage to fetch data from backend: - Fetch roles from /api/roles endpoint on mount - Fetch users from /api/users endpoint on mount - Integrated createUser API call with form submission - Added proper validation and error handling - Split name field into firstName and lastName for API compatibility - Added loading states and refresh functionality --- src/api/roles.ts | 68 +++++++++ src/api/users.ts | 108 +++++++++++++ src/pages/UsersPage.tsx | 325 +++++++++++++++++++++++++++++++++------- 3 files changed, 451 insertions(+), 50 deletions(-) create mode 100644 src/api/roles.ts create mode 100644 src/api/users.ts diff --git a/src/api/roles.ts b/src/api/roles.ts new file mode 100644 index 0000000..80a66b6 --- /dev/null +++ b/src/api/roles.ts @@ -0,0 +1,68 @@ +/** + * Roles API + * Handles all role-related API requests + */ + +import { apiClient } from './client'; + +export interface Role { + id: string; + name: string; + description: string; + permissions: Record>; + created_at: string; + updated_at: string; +} + +export interface RoleListResponse { + success: boolean; + message: string; + data: Role[]; +} + +/** + * Get all roles + */ +export async function getAllRoles(): Promise { + const response = await apiClient.get('/api/roles'); + return response; +} + +/** + * Get a single role by ID + */ +export async function getRoleById(id: string): Promise { + return apiClient.get(`/api/roles/${id}`); +} + +/** + * Create a new role + */ +export async function createRole(data: { + name: string; + description: string; + permissions?: Record>; +}): Promise { + return apiClient.post('/api/roles', data); +} + +/** + * Update an existing role + */ +export async function updateRole( + id: string, + data: { + name?: string; + description?: string; + permissions?: Record>; + } +): Promise { + return apiClient.put(`/api/roles/${id}`, data); +} + +/** + * Delete a role + */ +export async function deleteRole(id: string): Promise { + return apiClient.delete(`/api/roles/${id}`); +} diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 0000000..a8bfb86 --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,108 @@ +/** + * Users API + * Handles all user-related API requests + */ + +import { apiClient } from './client'; + +export interface User { + id: string; + email: string; + first_name: string; + last_name: string; + role_id: string; + role?: { + id: string; + name: string; + description: string; + permissions: Record>; + }; + is_active: boolean; + last_login: string | null; + created_at: string; + updated_at: string; +} + +export interface CreateUserInput { + email: string; + password: string; + first_name: string; + last_name: string; + role_id: number; + is_active?: boolean; +} + +export interface UpdateUserInput { + email?: string; + first_name?: string; + last_name?: string; + role_id?: number; + is_active?: boolean; +} + +export interface ChangePasswordInput { + current_password: string; + new_password: string; +} + +export interface UserListResponse { + data: User[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + }; +} + +/** + * Get all users with optional filters and pagination + */ +export async function getAllUsers(params?: { + role_id?: number; + is_active?: boolean; + search?: string; + page?: number; + limit?: number; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; +}): Promise { + return apiClient.get('/api/users', { params }); +} + +/** + * Get a single user by ID + */ +export async function getUserById(id: string): Promise { + return apiClient.get(`/api/users/${id}`); +} + +/** + * Create a new user + */ +export async function createUser(data: CreateUserInput): Promise { + return apiClient.post('/api/users', data); +} + +/** + * Update an existing user + */ +export async function updateUser(id: string, data: UpdateUserInput): Promise { + return apiClient.put(`/api/users/${id}`, data); +} + +/** + * Delete (deactivate) a user + */ +export async function deleteUser(id: string): Promise { + return apiClient.delete(`/api/users/${id}`); +} + +/** + * Change user password + */ +export async function changePassword(id: string, data: ChangePasswordInput): Promise { + return apiClient.put(`/api/users/${id}/password`, data); +} diff --git a/src/pages/UsersPage.tsx b/src/pages/UsersPage.tsx index cc58ce0..c9df8b5 100644 --- a/src/pages/UsersPage.tsx +++ b/src/pages/UsersPage.tsx @@ -1,7 +1,8 @@ import { useState, useEffect } from "react"; import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react"; import MaterialTable from "@material-table/core"; -import { Role } from "./RolesPage"; // Importa los tipos de roles +import { getAllRoles, Role as ApiRole } from "../api/roles"; +import { createUser, getAllUsers, CreateUserInput, User as ApiUser } from "../api/users"; interface User { id: string; @@ -13,38 +14,177 @@ interface User { createdAt: string; } +interface UserForm { + firstName: string; + lastName: string; + email: string; + password?: string; + roleId: string; + status: "ACTIVE" | "INACTIVE"; + createdAt: string; +} + export default function UsersPage() { - const initialRoles: Role[] = [ - { id: "1", name: "SUPER_ADMIN", description: "Full access", status: "ACTIVE", createdAt: "2025-12-17" }, - { id: "2", name: "USER", description: "Regular user", status: "ACTIVE", createdAt: "2025-12-16" }, - ]; - - const initialUsers: User[] = [ - { id: "1", name: "Admin GRH", email: "grh@domain.com", roleId: "1", roleName: "SUPER_ADMIN", status: "ACTIVE", createdAt: "2025-12-17" }, - { id: "2", name: "User CESPT", email: "cespt@domain.com", roleId: "2", roleName: "USER", status: "ACTIVE", createdAt: "2025-12-16" }, - ]; - - const [users, setUsers] = useState(initialUsers); + const [users, setUsers] = useState([]); const [activeUser, setActiveUser] = useState(null); const [search, setSearch] = useState(""); const [showModal, setShowModal] = useState(false); const [editingId, setEditingId] = useState(null); - const [roles, setRoles] = useState(initialRoles); + const [roles, setRoles] = useState([]); + const [loadingRoles, setLoadingRoles] = useState(true); + const [loadingUsers, setLoadingUsers] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); - const emptyUser: Omit = { name: "", email: "", roleId: "", status: "ACTIVE", createdAt: new Date().toISOString().slice(0,10) }; - const [form, setForm] = useState>(emptyUser); + const emptyUser: UserForm = { firstName: "", lastName: "", email: "", roleId: "", password: "", status: "ACTIVE", createdAt: new Date().toISOString().slice(0,10) }; + const [form, setForm] = useState(emptyUser); - const handleSave = () => { - const roleName = roles.find(r => r.id === form.roleId)?.name || ""; - if (editingId) { - setUsers(prev => prev.map(u => u.id === editingId ? { id: editingId, roleName, ...form } : u)); - } else { - const newId = Date.now().toString(); - setUsers(prev => [...prev, { id: newId, roleName, ...form }]); + // Fetch roles and users from API on component mount + useEffect(() => { + const fetchRoles = async () => { + try { + setLoadingRoles(true); + const rolesData = await getAllRoles(); + console.log('Roles fetched:', rolesData); + setRoles(rolesData); + } catch (error) { + console.error('Failed to fetch roles:', error); + } finally { + setLoadingRoles(false); + } + }; + + const fetchUsers = async () => { + try { + setLoadingUsers(true); + const usersResponse = await getAllUsers(); + console.log('Users API response:', usersResponse); + + // Map API users to UI format + const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({ + id: apiUser.id, + name: `${apiUser.first_name} ${apiUser.last_name}`, + email: apiUser.email, + roleId: apiUser.role_id, + roleName: apiUser.role?.name || '', + status: apiUser.is_active ? "ACTIVE" : "INACTIVE", + createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10) + })); + + console.log('Mapped users:', mappedUsers); + setUsers(mappedUsers); + } catch (error) { + console.error('Failed to fetch users:', error); + // Keep empty array on error + setUsers([]); + } finally { + setLoadingUsers(false); + } + }; + + fetchRoles(); + fetchUsers(); + }, []); + + const handleSave = async () => { + setError(null); + + // Validation + if (!form.firstName || !form.lastName || !form.email || !form.roleId) { + setError("Please fill in all required fields"); + return; + } + + if (!editingId && !form.password) { + setError("Password is required for new users"); + return; + } + + if (form.password && form.password.length < 8) { + setError("Password must be at least 8 characters"); + return; + } + + try { + setSaving(true); + + if (editingId) { + // TODO: Implement update user + const roleName = roles.find(r => r.id === form.roleId)?.name || ""; + const fullName = `${form.firstName} ${form.lastName}`; + setUsers(prev => prev.map(u => u.id === editingId ? { + id: editingId, + name: fullName, + email: form.email, + roleId: form.roleId, + roleName, + status: form.status, + createdAt: form.createdAt + } : u)); + } else { + // Create new user + const roleIdNum = parseInt(form.roleId, 10); + + if (isNaN(roleIdNum)) { + setError("Please select a valid role"); + return; + } + + const createData: CreateUserInput = { + email: form.email, + password: form.password!, + first_name: form.firstName, + last_name: form.lastName, + role_id: roleIdNum, + is_active: form.status === "ACTIVE", + }; + + const newUser = await createUser(createData); + const roleName = roles.find(r => r.id === form.roleId)?.name || ""; + + // Add the new user to the list + const apiUser: ApiUser = newUser; + setUsers(prev => [...prev, { + id: apiUser.id, + name: `${apiUser.first_name} ${apiUser.last_name}`, + email: apiUser.email, + roleId: apiUser.role_id, + roleName: roleName, + status: apiUser.is_active ? "ACTIVE" : "INACTIVE", + createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10) + }]); + } + + setShowModal(false); + setEditingId(null); + setForm(emptyUser); + } catch (err) { + console.error('Failed to save user:', err); + setError(err instanceof Error ? err.message : 'Failed to save user'); + } finally { + setSaving(false); + } + }; + + const handleRefresh = async () => { + try { + setLoadingUsers(true); + const usersResponse = await getAllUsers(); + const mappedUsers: User[] = usersResponse.data.map((apiUser: ApiUser) => ({ + id: apiUser.id, + name: `${apiUser.first_name} ${apiUser.last_name}`, + email: apiUser.email, + roleId: apiUser.role_id, + roleName: apiUser.role?.name || '', + status: apiUser.is_active ? "ACTIVE" : "INACTIVE", + createdAt: new Date(apiUser.created_at).toISOString().slice(0, 10) + })); + setUsers(mappedUsers); + } catch (error) { + console.error('Failed to refresh users:', error); + } finally { + setLoadingUsers(false); } - setShowModal(false); - setEditingId(null); - setForm(emptyUser); }; const handleDelete = () => { @@ -61,8 +201,8 @@ export default function UsersPage() {

Project Information

Usuarios disponibles y sus roles.

- setForm({...form, roleId: e.target.value})} className="w-full border px-3 py-2 rounded mt-2" disabled={loadingRoles}> + {roles.map(r => )}
@@ -78,9 +218,23 @@ export default function UsersPage() {
- + - +
@@ -88,19 +242,25 @@ export default function UsersPage() { setSearch(e.target.value)} /> {/* TABLE */} - {rowData.status} }, - { title: "Created", field: "createdAt", type: "date" } - ]} - data={filtered} - onRowClick={(_, rowData) => setActiveUser(rowData as User)} - options={{ actionsColumnIndex: -1, search: false, paging: true, sorting: true, rowStyle: rowData => ({ backgroundColor: activeUser?.id === (rowData as User).id ? "#EEF2FF" : "#FFFFFF" }) }} - /> + {loadingUsers ? ( +
+

Loading users...

+
+ ) : ( + {rowData.status} }, + { title: "Created", field: "createdAt", type: "date" } + ]} + data={filtered} + onRowClick={(_, rowData) => setActiveUser(rowData as User)} + options={{ actionsColumnIndex: -1, search: false, paging: true, sorting: true, rowStyle: rowData => ({ backgroundColor: activeUser?.id === (rowData as User).id ? "#EEF2FF" : "#FFFFFF" }) }} + /> + )} {/* MODAL */} @@ -108,17 +268,82 @@ export default function UsersPage() {

{editingId ? "Edit User" : "Add User"}

- setForm({...form, name: e.target.value})} /> - setForm({...form, email: e.target.value})} /> - setForm({...form, firstName: e.target.value})} + disabled={saving} + /> + + setForm({...form, lastName: e.target.value})} + disabled={saving} + /> + + setForm({...form, email: e.target.value})} + disabled={saving} + /> + + {!editingId && ( + setForm({...form, password: e.target.value})} + disabled={saving} + /> + )} + + - - setForm({...form, createdAt: e.target.value})} /> + + +
- - + +