Skip to content

Commit c8905bb

Browse files
committed
Funiones de perfil de usurio normal funcionando correctamente nombre, correo y paswort funcionan
1 parent ae5f9d7 commit c8905bb

6 files changed

Lines changed: 632 additions & 130 deletions

File tree

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import axios from 'axios';
2-
import { UserDTO } from '../../../types/admin/gestionuser/index';
2+
import {
3+
UserDTO,
4+
CreateUserRequest,
5+
UpdateUserRequest,
6+
UpdatePasswordRequest,
7+
} from '../../../types/admin/gestionuser/index';
38

49
const API_URL = '/api/User';
510

611
const apiClient = axios.create({
712
baseURL: API_URL,
813
});
914

10-
15+
// -------------------------------------------------
16+
// Interceptor: añadir JWT
17+
// -------------------------------------------------
1118
apiClient.interceptors.request.use((config) => {
1219
const token = localStorage.getItem('authToken');
1320
if (token) {
@@ -16,26 +23,67 @@ apiClient.interceptors.request.use((config) => {
1623
return config;
1724
});
1825

26+
// -------------------------------------------------
27+
// Interceptor: manejo global de errores (401 → logout)
28+
// -------------------------------------------------
29+
apiClient.interceptors.response.use(
30+
(response) => response,
31+
(error) => {
32+
if (error.response?.status === 401) {
33+
localStorage.removeItem('authToken');
34+
window.location.href = '/login';
35+
}
36+
return Promise.reject(error);
37+
}
38+
);
1939

40+
// -------------------------------------------------
41+
// SHA‑256 (para contraseñas)
42+
// -------------------------------------------------
2043
export async function sha256(message: string): Promise<string> {
2144
const msgBuffer = new TextEncoder().encode(message);
2245
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
2346
const hashArray = Array.from(new Uint8Array(hashBuffer));
24-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
47+
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
2548
}
2649

50+
// -------------------------------------------------
51+
// Servicio de usuarios
52+
// -------------------------------------------------
2753
export const userService = {
54+
/** Obtener todos los usuarios */
2855
getUsers: async (): Promise<UserDTO[]> => {
29-
const response = await apiClient.get<UserDTO[]>('/GetUsers');
30-
return response.data;
56+
const { data } = await apiClient.get<UserDTO[]>('/GetUsers');
57+
return data;
3158
},
32-
createUser: (userData: any) => {
33-
return apiClient.post('/CreateUser', userData);
59+
60+
/** Crear usuario (solo admin) */
61+
createUser: async (userData: CreateUserRequest) => {
62+
const { data } = await apiClient.post('/CreateUser', userData);
63+
return data;
3464
},
35-
updateUser: (id: string, userData: any) => {
36-
return apiClient.patch(`/PatchUser/${id}`, userData);
65+
66+
/** Actualizar datos del usuario (PATCH) */
67+
updateUser: async (id: string, userData: UpdateUserRequest) => {
68+
const { data } = await apiClient.patch(`/PatchUser/${id}`, userData);
69+
return data;
70+
},
71+
72+
/** Cambiar contraseña (PATCH separado) */
73+
changePassword: async (id: string, passwordData: UpdatePasswordRequest) => {
74+
const { data } = await apiClient.patch(`/ChangePassword/${id}`, passwordData);
75+
return data;
76+
},
77+
78+
/** Eliminar usuario */
79+
deleteUser: async (id: string) => {
80+
const { data } = await apiClient.delete(`/DeleteUser/${id}`);
81+
return data;
82+
},
83+
84+
/** Obtener un usuario por ID */
85+
getUserById: async (id: string): Promise<UserDTO> => {
86+
const { data } = await apiClient.get<UserDTO>(`/GetUserById/${id}`);
87+
return data;
3788
},
38-
deleteUser: (id: string) => {
39-
return apiClient.delete(`/DeleteUser/${id}`);
40-
}
4189
};
Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,70 @@
11
export interface UserDTO {
2-
id: string;
3-
email: string;
4-
userName: string;
5-
role: number;
2+
id: string;
3+
email: string;
4+
userName: string;
5+
role: number;
66
}
77

88
export interface UserTableType {
9-
key: string;
10-
id: string;
11-
name: string;
12-
email: string;
13-
role: string;
14-
roleId: number;
9+
key: string;
10+
id: string;
11+
name: string;
12+
email: string;
13+
role: string;
14+
roleId: number;
1515
}
1616

17+
/**
18+
* Solicitud para crear un nuevo usuario (usada en POST /CreateUser)
19+
* → Usa PascalCase para coincidir con el backend (AddUserRequest)
20+
*/
21+
export interface CreateUserRequest {
22+
UserName: string; // ← PascalCase
23+
Mail: string; // ← PascalCase
24+
Password: string; // ← hasheada con SHA-256
25+
Role: number; // ← 1 = Admin, 2 = User
26+
}
27+
28+
/**
29+
* Solicitud para actualizar datos del usuario (usada en PATCH /PatchUser/{id})
30+
* → Usa PascalCase para coincidir con el backend (PatchUserRequest)
31+
*/
32+
export interface UpdateUserRequest {
33+
UserName?: string; // opcional
34+
Mail?: string; // opcional
35+
Role?: number; // opcional (solo admin)
36+
}
37+
38+
/**
39+
* Solicitud para cambiar contraseña (usada en PATCH /ChangePassword/{id})
40+
*/
41+
export interface UpdatePasswordRequest {
42+
CurrentPassword?: string; // ← Añadido: requerido para verificar la contraseña actual (hasheada)
43+
NewPassword: string; // hasheada
44+
ConfirmNewPassword: string; // hasheada (debe coincidir)
45+
}
46+
47+
export interface UserProfile {
48+
id: string;
49+
userName: string;
50+
email: string;
51+
role: number;
52+
}
53+
54+
/**
55+
* Mapeo de roles (número → texto)
56+
*/
1757
export const ROLES: { [key: number]: string } = {
18-
1: 'Administrador',
19-
2: 'Usuario',
20-
};
58+
1: 'Administrador',
59+
2: 'Usuario',
60+
};
61+
62+
/**
63+
* Verifica si el rol es Administrador
64+
*/
65+
export const isAdmin = (role: number): boolean => role === 1;
66+
67+
/**
68+
* Verifica si el rol es Usuario regular
69+
*/
70+
export const isRegularUser = (role: number): boolean => role === 2;

src/views/admin/page/gestionuser/UserManagementView.tsx

Lines changed: 113 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react';
2-
import { Table, Button, Input, Space, Popconfirm, Typography, Card, Flex, Grid } from 'antd';
2+
import { Table, Button, Space, Popconfirm, Typography, Card, Flex, Grid, Tag, Tooltip } from 'antd';
33
import type { TableProps } from 'antd';
4-
import { PlusOutlined } from '@ant-design/icons';
4+
import { PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined } from '@ant-design/icons';
55
import { useUserManagement } from './hooks/useUserManagement';
66
import { UserFormModal } from './formulariomodal/UserFormModal';
7-
import { UserTableType, ROLES } from '../../../../types/admin/gestionuser/index';
7+
import { UserTableType, ROLES, isAdmin } from '../../../../types/admin/gestionuser/index';
88

9-
const { Search } = Input;
109
const { Title } = Typography;
1110

1211
export const UserManagementView: React.FC = () => {
@@ -17,7 +16,10 @@ export const UserManagementView: React.FC = () => {
1716
isModalVisible,
1817
editingUser,
1918
isSubmitting,
20-
handleSearch,
19+
currentUser,
20+
canCreateUsers,
21+
canDeleteUsers,
22+
canEditAllUsers,
2123
handleDelete,
2224
showAddModal,
2325
showEditModal,
@@ -26,51 +28,127 @@ export const UserManagementView: React.FC = () => {
2628
} = useUserManagement();
2729

2830
const columns: TableProps<UserTableType>['columns'] = [
29-
{ title: 'Nombre', dataIndex: 'name', key: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
30-
{ title: 'Correo Electrónico', dataIndex: 'email', key: 'email', responsive: ['sm'] },
31+
{
32+
title: 'Nombre',
33+
dataIndex: 'name',
34+
key: 'name',
35+
sorter: (a, b) => a.name.localeCompare(b.name),
36+
render: (name: string, record) => (
37+
<Space>
38+
<span>{name}</span>
39+
{currentUser?.id === record.id && (
40+
<Tag icon={<UserOutlined />} color="green"></Tag>
41+
)}
42+
</Space>
43+
)
44+
},
45+
{
46+
title: 'Correo Electrónico',
47+
dataIndex: 'email',
48+
key: 'email',
49+
responsive: ['sm']
50+
},
3151
{
32-
title: 'Rol', dataIndex: 'role', key: 'role', responsive: ['md'],
52+
title: 'Rol',
53+
dataIndex: 'role',
54+
key: 'role',
55+
responsive: ['md'],
56+
render: (role: string, record) => (
57+
<Tag color={record.roleId === 1 ? 'red' : 'blue'}>
58+
{role}
59+
</Tag>
60+
),
3361
filters: Object.values(ROLES).map(role => ({ text: role, value: role })),
3462
onFilter: (value, record) => record.role.indexOf(value as string) === 0,
3563
},
3664
{
37-
title: 'Acciones', key: 'actions', fixed: 'right', width: 150,
38-
render: (_, record) => (
39-
<Space size="middle">
40-
<Button type="link" onClick={() => showEditModal(record)}>Editar</Button>
41-
<Popconfirm
42-
title="¿Eliminar este usuario?"
43-
onConfirm={() => handleDelete(record.id)}
44-
okText="Sí" cancelText="No"
45-
>
46-
<Button type="link" danger>Eliminar</Button>
47-
</Popconfirm>
48-
</Space>
49-
),
65+
title: 'Acciones',
66+
key: 'actions',
67+
fixed: 'right',
68+
width: 150,
69+
render: (_, record) => {
70+
const isCurrentUser = currentUser?.id === record.id;
71+
const canEdit = canEditAllUsers || isCurrentUser;
72+
const canDelete = canDeleteUsers && !isCurrentUser; // REMOVIDO: && record.roleId !== 1
73+
// Ahora permite eliminar admins si no es self
74+
75+
return (
76+
<Space size="small">
77+
<Tooltip title={canEdit ? "Editar usuario" : "Sin permisos para editar"}>
78+
<Button
79+
type="link"
80+
icon={<EditOutlined />}
81+
onClick={() => showEditModal(record)}
82+
disabled={!canEdit}
83+
>
84+
Editar
85+
</Button>
86+
</Tooltip>
87+
88+
{canDelete && (
89+
<Tooltip title="Eliminar usuario">
90+
<Popconfirm
91+
title="¿Eliminar este usuario?"
92+
description="Esta acción no se puede deshacer"
93+
onConfirm={() => handleDelete(record.id)}
94+
okText="Sí"
95+
cancelText="No"
96+
okType="danger"
97+
>
98+
<Button
99+
type="link"
100+
danger
101+
icon={<DeleteOutlined />}
102+
>
103+
Eliminar
104+
</Button>
105+
</Popconfirm>
106+
</Tooltip>
107+
)}
108+
</Space>
109+
);
110+
},
50111
},
51112
];
52113

53114
return (
54115
<Card>
55116
<Space direction="vertical" size="large" style={{ width: '100%' }}>
56-
<Title level={2}>Gestión de Usuarios</Title>
57-
<Flex vertical={!screens.md} justify="space-between" align="center" wrap="wrap" gap="middle">
58-
<Search
59-
placeholder="Buscar por nombre o correo"
60-
onSearch={handleSearch}
61-
style={{ width: screens.sm ? 300 : '100%' }}
62-
allowClear
63-
/>
64-
<Button type="primary" icon={<PlusOutlined />} onClick={showAddModal} style={{ width: !screens.md ? '100%' : 'auto' }}>
65-
Agregar Usuario
66-
</Button>
117+
<Flex justify="space-between" align="center">
118+
<Title level={2} style={{ margin: 0 }}>Gestión de Usuarios</Title>
119+
{currentUser && (
120+
<Tag color={isAdmin(currentUser.role) ? 'red' : 'blue'}>
121+
{ROLES[currentUser.role] || 'Usuario'}
122+
</Tag>
123+
)}
67124
</Flex>
125+
126+
<Flex vertical={!screens.md} justify="flex-end" align="center" wrap="wrap" gap="middle">
127+
{canCreateUsers && (
128+
<Button
129+
type="primary"
130+
icon={<PlusOutlined />}
131+
onClick={showAddModal}
132+
style={{ width: !screens.md ? '100%' : 'auto' }}
133+
>
134+
Agregar Usuario
135+
</Button>
136+
)}
137+
</Flex>
138+
68139
<Table
69140
columns={columns}
70141
dataSource={filteredUsers}
71142
rowKey="key"
72143
loading={loading}
73-
scroll={{ x: true }}
144+
scroll={{ x: 800 }}
145+
pagination={{
146+
pageSize: 10,
147+
showSizeChanger: true,
148+
showQuickJumper: true,
149+
showTotal: (total, range) =>
150+
`${range[0]}-${range[1]} de ${total} usuarios`,
151+
}}
74152
/>
75153
</Space>
76154

@@ -80,6 +158,8 @@ export const UserManagementView: React.FC = () => {
80158
onFinish={handleFormFinish}
81159
editingUser={editingUser}
82160
isSubmitting={isSubmitting}
161+
currentUserRole={currentUser?.role}
162+
isAdminUser={canEditAllUsers}
83163
/>
84164
</Card>
85165
);

0 commit comments

Comments
 (0)