Se soluciono autenticacion
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
// src/api/pedimentoDocuments.ts
|
// src/api/pedimentoDocuments.ts
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
export interface PedimentoDocument {
|
export interface PedimentoDocument {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -23,8 +24,6 @@ export interface PedimentoDocumentsResponse {
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
export async function fetchPedimentoDocuments(
|
export async function fetchPedimentoDocuments(
|
||||||
token: string,
|
|
||||||
pedimentoId: string = '',
|
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 10,
|
pageSize: number = 10,
|
||||||
filters: {
|
filters: {
|
||||||
@@ -32,7 +31,8 @@ export async function fetchPedimentoDocuments(
|
|||||||
extension?: string;
|
extension?: string;
|
||||||
document_type?: string | number;
|
document_type?: string | number;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
} = {}
|
} = {},
|
||||||
|
pedimentoId: string = ''
|
||||||
): Promise<PedimentoDocumentsResponse> {
|
): Promise<PedimentoDocumentsResponse> {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('page', String(page));
|
params.append('page', String(page));
|
||||||
@@ -43,18 +43,10 @@ export async function fetchPedimentoDocuments(
|
|||||||
if (filters.document_type) params.append('document_type', String(filters.document_type));
|
if (filters.document_type) params.append('document_type', String(filters.document_type));
|
||||||
if (filters.created_at) params.append('created_at', filters.created_at);
|
if (filters.created_at) params.append('created_at', filters.created_at);
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetchWithAuth(
|
||||||
`${API_URL}/record/documents/?${params.toString()}`,
|
`${API_URL}/record/documents/?${params.toString()}`
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.status === 401) {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
export interface Document {
|
export interface Document {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -19,8 +20,6 @@ export interface DocumentsResponse {
|
|||||||
results: Document[];
|
results: Document[];
|
||||||
}
|
}
|
||||||
|
|
||||||
import { refreshToken } from './auth';
|
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
// Obtiene la lista de documentos (pedimentos)
|
// Obtiene la lista de documentos (pedimentos)
|
||||||
export interface PedimentosFilters {
|
export interface PedimentosFilters {
|
||||||
@@ -38,7 +37,6 @@ export interface PedimentosFilters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchDocuments(
|
export async function fetchDocuments(
|
||||||
token: string,
|
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 10,
|
pageSize: number = 10,
|
||||||
filters: PedimentosFilters = {}
|
filters: PedimentosFilters = {}
|
||||||
@@ -51,65 +49,14 @@ export async function fetchDocuments(
|
|||||||
params.append(key, String(value));
|
params.append(key, String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let res = await fetch(`${API_URL}/customs/pedimentos/?${params.toString()}`, {
|
|
||||||
headers: {
|
const res = await fetchWithAuth(`${API_URL}/customs/pedimentos/?${params.toString()}`);
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
// Intentar refrescar el token
|
|
||||||
const refresh = localStorage.getItem('refresh');
|
|
||||||
if (refresh) {
|
|
||||||
try {
|
|
||||||
const data = await refreshToken(refresh);
|
|
||||||
localStorage.setItem('access', data.access);
|
|
||||||
// Reintenta la petición con el nuevo access token
|
|
||||||
res = await fetch(`${API_URL}/customs/pedimentos/?page=${page}&page_size=${pageSize}`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${data.access}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
// Obtiene los documentos por id de pedimento
|
// Obtiene los documentos por id de pedimento
|
||||||
export async function fetchDocumentById(token: string, id: string): Promise<DocumentsResponse> {
|
export async function fetchDocumentById(id: string): Promise<DocumentsResponse> {
|
||||||
let res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, {
|
const res = await fetchWithAuth(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
// Intentar refrescar el token
|
|
||||||
const refresh = localStorage.getItem('refresh');
|
|
||||||
if (refresh) {
|
|
||||||
try {
|
|
||||||
const data = await refreshToken(refresh);
|
|
||||||
localStorage.setItem('access', data.access);
|
|
||||||
// Reintenta la petición con el nuevo access token
|
|
||||||
res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${data.access}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
|
import { fetchWithAuth, putWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
// PUT para marcar una notificación como vista
|
// PUT para marcar una notificación como vista
|
||||||
export async function marcarNotificacionComoVista(id: number): Promise<Notificacion> {
|
export async function marcarNotificacionComoVista(id: number): Promise<Notificacion> {
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const url = `${API_URL}/notificaciones/notificaciones/${id}/`;
|
const url = `${API_URL}/notificaciones/notificaciones/${id}/`;
|
||||||
const headers = new Headers();
|
const res = await putWithAuth(url, { visto: true });
|
||||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
|
||||||
headers.append('Content-Type', 'application/json');
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ visto: true })
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al actualizar notificación');
|
if (!res.ok) throw new Error('Error al actualizar notificación');
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
@@ -41,28 +35,16 @@ export interface NotificacionesResponse {
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
export async function fetchNotificaciones({ page = 1, pageSize = 10, visto = false } = {}): Promise<NotificacionesResponse> {
|
export async function fetchNotificaciones({ page = 1, pageSize = 10, visto = false } = {}): Promise<NotificacionesResponse> {
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${pageSize}&visto=${visto}`;
|
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${pageSize}&visto=${visto}`;
|
||||||
const headers = new Headers();
|
const res = await fetchWithAuth(url);
|
||||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
|
||||||
headers.append('Content-Type', 'application/json');
|
|
||||||
const res = await fetch(url, {
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function fetchAllNotifications({page = 1, page_size=10}): Promise<NotificacionesResponse>{
|
export async function fetchAllNotifications({page = 1, page_size=10}): Promise<NotificacionesResponse>{
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${page_size}`;
|
const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${page_size}`;
|
||||||
const headers = new Headers();
|
const res = await fetchWithAuth(url);
|
||||||
if (token) headers.append('Authorization', `Bearer ${token}`);
|
|
||||||
headers.append('Content-Type', 'application/json');
|
|
||||||
const res = await fetch(url, {
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
if (!res.ok) throw new Error('Error al obtener notificaciones');
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// organization.ts
|
// organization.ts
|
||||||
// Tipos para la respuesta del endpoint de uso de almacenamiento de organización
|
// Tipos para la respuesta del endpoint de uso de almacenamiento de organización
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
export interface OrganizationUsage {
|
export interface OrganizationUsage {
|
||||||
organizacion: string;
|
organizacion: string;
|
||||||
@@ -16,16 +17,9 @@ export interface OrganizationUsage {
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
// Ejemplo de función para obtener la información tipada
|
// Ejemplo de función para obtener la información tipada
|
||||||
export async function fetchOrganizationUsage(token: string): Promise<OrganizationUsage> {
|
export async function fetchOrganizationUsage(): Promise<OrganizationUsage> {
|
||||||
const res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, {
|
const res = await fetchWithAuth(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('Error al obtener información de la organización');
|
throw new Error('Error al obtener información de la organización');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// src/api/pedimentoDocuments.ts
|
// src/api/pedimentoDocuments.ts
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
export interface PedimentoDocument {
|
export interface PedimentoDocument {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -19,26 +20,25 @@ export interface PedimentoDocumentsResponse {
|
|||||||
results: PedimentoDocument[];
|
results: PedimentoDocument[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
export async function fetchPedimentoDocuments(
|
export async function fetchPedimentoDocuments(
|
||||||
token: string,
|
|
||||||
pedimentoId: string,
|
pedimentoId: string,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 10
|
pageSize: number = 10
|
||||||
): Promise<PedimentoDocumentsResponse> {
|
): Promise<PedimentoDocumentsResponse> {
|
||||||
const res = await fetch(
|
try {
|
||||||
`${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}`,
|
const res = await fetchWithAuth(
|
||||||
{
|
`${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}`
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.status === 401) {
|
|
||||||
throw new Error('SESSION_EXPIRED');
|
if (!res.ok) {
|
||||||
|
throw new Error('No autorizado o error en la petición');
|
||||||
}
|
}
|
||||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
|
||||||
return res.json();
|
return res.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchPedimentoDocuments:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
// Tipos para la respuesta y registros
|
// Tipos para la respuesta y registros
|
||||||
export interface ProcesamientoPedimento {
|
export interface ProcesamientoPedimento {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -20,14 +22,34 @@ export interface ProcesamientoPedimentosResponse {
|
|||||||
|
|
||||||
// API para customs/procesamientopedimentos/
|
// API para customs/procesamientopedimentos/
|
||||||
export async function fetchProcesamientoPedimentos(
|
export async function fetchProcesamientoPedimentos(
|
||||||
token: string | null,
|
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 20
|
pageSize: number = 20,
|
||||||
|
filters: Record<string, any> = {}
|
||||||
): Promise<ProcesamientoPedimentosResponse> {
|
): Promise<ProcesamientoPedimentosResponse> {
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
try {
|
||||||
const headers: Record<string, string> = {};
|
const API_URL = (import.meta as any).env.VITE_EFC_API_URL;
|
||||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
||||||
const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?page=${page}&page_size=${pageSize}`, { headers });
|
// Construir query params
|
||||||
if (!res.ok) throw new Error('Error al obtener procesamiento de pedimentos');
|
const params = new URLSearchParams();
|
||||||
return await res.json();
|
params.append('page', String(page));
|
||||||
|
params.append('page_size', String(pageSize));
|
||||||
|
|
||||||
|
// Agregar filtros
|
||||||
|
Object.entries(filters).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
params.append(key, String(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetchWithAuth(`${API_URL}/customs/procesamientopedimentos/?${params.toString()}`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Error al obtener procesamiento de pedimentos');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchProcesamientoPedimentos:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
||||||
|
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
|
// Función helper para manejar respuestas
|
||||||
|
|
||||||
async function handleResponse(response, operation = 'operación') {
|
async function handleResponse(response, operation = 'operación') {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
@@ -15,57 +18,27 @@ async function handleResponse(response, operation = 'operación') {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUsers(token) {
|
export async function fetchUsers() {
|
||||||
const url = `${API_URL}/user/users/`;
|
const url = `${API_URL}/user/users/`;
|
||||||
const res = await fetch(url, {
|
const res = await fetchWithAuth(url);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return handleResponse(res, 'Fetch Users');
|
return handleResponse(res, 'Fetch Users');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(token, userData) {
|
export async function createUser(userData) {
|
||||||
const url = `${API_URL}/user/users/`;
|
const url = `${API_URL}/user/users/`;
|
||||||
const res = await fetch(url, {
|
const res = await postWithAuth(url, userData);
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData),
|
|
||||||
});
|
|
||||||
return handleResponse(res, 'Create User');
|
return handleResponse(res, 'Create User');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUser(token, id, userData) {
|
export async function updateUser(id, userData) {
|
||||||
const url = `${API_URL}/user/users/${id}/`;
|
const url = `${API_URL}/user/users/${id}/`;
|
||||||
const res = await fetch(url, {
|
const res = await putWithAuth(url, userData);
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData),
|
|
||||||
});
|
|
||||||
return handleResponse(res, 'Update User');
|
return handleResponse(res, 'Update User');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteUser(token, id) {
|
export async function deleteUser(id) {
|
||||||
const url = `${API_URL}/user/users/${id}/`;
|
const url = `${API_URL}/user/users/${id}/`;
|
||||||
const res = await fetch(url, {
|
const res = await deleteWithAuth(url);
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) throw new Error('SESSION_EXPIRED');
|
|
||||||
if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`);
|
if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000';
|
||||||
|
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
// Función helper para manejar respuestas
|
// Función helper para manejar respuestas
|
||||||
async function handleResponse(response, operation = 'operación') {
|
async function handleResponse(response, operation = 'operación') {
|
||||||
@@ -26,18 +27,12 @@ async function handleResponse(response, operation = 'operación') {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUsers(token) {
|
export async function fetchUsers() {
|
||||||
try {
|
try {
|
||||||
const url = `${API_URL}/user/users/`;
|
const url = `${API_URL}/user/users/`;
|
||||||
console.log('👥 Fetching users from:', url);
|
console.log('👥 Fetching users from:', url);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetchWithAuth(url);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await handleResponse(res, 'Fetch Users');
|
const data = await handleResponse(res, 'Fetch Users');
|
||||||
console.log('✅ Users data received');
|
console.log('✅ Users data received');
|
||||||
@@ -52,20 +47,12 @@ export async function fetchUsers(token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(token, userData) {
|
export async function createUser(userData) {
|
||||||
try {
|
try {
|
||||||
const url = `${API_URL}/user/users/`;
|
const url = `${API_URL}/user/users/`;
|
||||||
console.log('➕ Creating user at:', url);
|
console.log('➕ Creating user at:', url);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await postWithAuth(url, userData);
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await handleResponse(res, 'Create User');
|
const data = await handleResponse(res, 'Create User');
|
||||||
console.log('✅ User created successfully');
|
console.log('✅ User created successfully');
|
||||||
@@ -80,20 +67,12 @@ export async function createUser(token, userData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUser(token, id, userData) {
|
export async function updateUser(id, userData) {
|
||||||
try {
|
try {
|
||||||
const url = `${API_URL}/user/users/${id}/`;
|
const url = `${API_URL}/user/users/${id}/`;
|
||||||
console.log('✏️ Updating user at:', url);
|
console.log('✏️ Updating user at:', url);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await putWithAuth(url, userData);
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await handleResponse(res, 'Update User');
|
const data = await handleResponse(res, 'Update User');
|
||||||
console.log('✅ User updated successfully');
|
console.log('✅ User updated successfully');
|
||||||
@@ -108,19 +87,12 @@ export async function updateUser(token, id, userData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteUser(token, id) {
|
export async function deleteUser(id) {
|
||||||
try {
|
try {
|
||||||
const url = `${API_URL}/user/users/${id}/`;
|
const url = `${API_URL}/user/users/${id}/`;
|
||||||
console.log('🗑️ Deleting user at:', url);
|
console.log('🗑️ Deleting user at:', url);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await deleteWithAuth(url);
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
console.error('❌ Unauthorized - session expired');
|
console.error('❌ Unauthorized - session expired');
|
||||||
@@ -145,18 +117,12 @@ export async function deleteUser(token, id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCurrentUser(token) {
|
export async function getCurrentUser() {
|
||||||
try {
|
try {
|
||||||
const url = `${API_URL}/user/users/me/`;
|
const url = `${API_URL}/user/users/me/`;
|
||||||
console.log('👤 Fetching current user from:', url);
|
console.log('👤 Fetching current user from:', url);
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetchWithAuth(url);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await handleResponse(res, 'Get Current User');
|
const data = await handleResponse(res, 'Get Current User');
|
||||||
console.log('✅ Current user data received:', data);
|
console.log('✅ Current user data received:', data);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState, useRef } from 'react';
|
import React, { createContext, useContext, useEffect, useState, useRef } from 'react';
|
||||||
import { getCurrentUser } from '../api/users.ts';
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
import { refreshToken } from '../api/auth.js';
|
|
||||||
|
|
||||||
const UserContext = createContext({
|
const UserContext = createContext({
|
||||||
user: null,
|
user: null,
|
||||||
@@ -19,51 +18,28 @@ export function UserProvider({ children }) {
|
|||||||
if (fetchedOnce.current && loading) return;
|
if (fetchedOnce.current && loading) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
let token = localStorage.getItem('access');
|
|
||||||
let triedRefresh = false;
|
|
||||||
while (true) {
|
|
||||||
try {
|
try {
|
||||||
|
const token = localStorage.getItem('access');
|
||||||
if (token) {
|
if (token) {
|
||||||
const userData = await getCurrentUser(token);
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
const response = await fetchWithAuth(`${API_URL}/user/users/me/`);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const userData = await response.json();
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
} else {
|
} else {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
}
|
}
|
||||||
break;
|
} else {
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Si el token expiró, intenta refrescarlo una vez
|
console.error('Error fetching user:', err);
|
||||||
if (!triedRefresh && (err.message === 'SESSION_EXPIRED' || err.message.includes('401'))) {
|
|
||||||
triedRefresh = true;
|
|
||||||
const refresh = localStorage.getItem('refresh');
|
|
||||||
if (refresh) {
|
|
||||||
try {
|
|
||||||
const data = await refreshToken(refresh);
|
|
||||||
if (data.access) {
|
|
||||||
localStorage.setItem('access', data.access);
|
|
||||||
token = data.access;
|
|
||||||
continue; // Reintenta con el nuevo token
|
|
||||||
} else {
|
|
||||||
throw new Error('No se pudo refrescar el token');
|
|
||||||
}
|
|
||||||
} catch (refreshErr) {
|
|
||||||
setError(refreshErr);
|
|
||||||
setUser(null);
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
window.dispatchEvent(new CustomEvent('authStateChanged'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setUser(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setError(err);
|
setError(err);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
fetchedOnce.current = true;
|
fetchedOnce.current = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
|
// Variable para controlar si ya hay una renovación de token en proceso
|
||||||
|
let isRefreshing = false;
|
||||||
|
let failedQueue = [];
|
||||||
|
|
||||||
|
// Función para procesar la cola de peticiones fallidas después de renovar el token
|
||||||
|
const processQueue = (error, token = null) => {
|
||||||
|
failedQueue.forEach(prom => {
|
||||||
|
if (error) {
|
||||||
|
prom.reject(error);
|
||||||
|
} else {
|
||||||
|
prom.resolve(token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
failedQueue = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función para renovar el token usando el refresh token
|
||||||
|
const refreshToken = async () => {
|
||||||
|
try {
|
||||||
|
const refresh = localStorage.getItem('refresh');
|
||||||
|
if (!refresh) {
|
||||||
|
throw new Error('No refresh token available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_URL}/auth/token/refresh/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
refresh: refresh
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Guardar el nuevo access token
|
||||||
|
localStorage.setItem('access', data.access);
|
||||||
|
|
||||||
|
// Si viene un nuevo refresh token, guardarlo también
|
||||||
|
if (data.refresh) {
|
||||||
|
localStorage.setItem('refresh', data.refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.access;
|
||||||
|
} catch (error) {
|
||||||
|
// Si falla la renovación, limpiar tokens y redirigir al login
|
||||||
|
localStorage.removeItem('access');
|
||||||
|
localStorage.removeItem('refresh');
|
||||||
|
localStorage.removeItem('user_id');
|
||||||
|
localStorage.removeItem('user_is_importador');
|
||||||
|
|
||||||
|
// Redirigir al login después de un pequeño delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función principal para hacer peticiones con manejo automático de tokens
|
||||||
|
export const fetchWithAuth = async (url, options = {}) => {
|
||||||
|
// Obtener el token actual
|
||||||
|
let token = localStorage.getItem('access');
|
||||||
|
|
||||||
|
// Configurar headers por defecto
|
||||||
|
const defaultHeaders = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token && { 'Authorization': `Bearer ${token}` })
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combinar headers
|
||||||
|
const finalOptions = {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...defaultHeaders,
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Hacer la petición inicial
|
||||||
|
let response = await fetch(url, finalOptions);
|
||||||
|
|
||||||
|
// Si la respuesta es 401 (Unauthorized), intentar renovar el token
|
||||||
|
if (response.status === 401 && !isRefreshing) {
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Renovar el token
|
||||||
|
const newToken = await refreshToken();
|
||||||
|
|
||||||
|
// Procesar la cola de peticiones pendientes
|
||||||
|
processQueue(null, newToken);
|
||||||
|
|
||||||
|
// Actualizar el header de autorización y reintentar la petición original
|
||||||
|
finalOptions.headers['Authorization'] = `Bearer ${newToken}`;
|
||||||
|
response = await fetch(url, finalOptions);
|
||||||
|
|
||||||
|
} catch (refreshError) {
|
||||||
|
// Si falla la renovación, procesar la cola con error
|
||||||
|
processQueue(refreshError, null);
|
||||||
|
throw refreshError;
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si todavía hay un 401 después del intento de renovación, redirigir al login
|
||||||
|
if (response.status === 401) {
|
||||||
|
localStorage.removeItem('access');
|
||||||
|
localStorage.removeItem('refresh');
|
||||||
|
localStorage.removeItem('user_id');
|
||||||
|
localStorage.removeItem('user_is_importador');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
throw new Error('Session expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Si hay un error de red o cualquier otro error, propagarlo
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función auxiliar para hacer peticiones GET con manejo de tokens
|
||||||
|
export const getWithAuth = async (url) => {
|
||||||
|
return fetchWithAuth(url, { method: 'GET' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función auxiliar para hacer peticiones POST con manejo de tokens
|
||||||
|
export const postWithAuth = async (url, data) => {
|
||||||
|
return fetchWithAuth(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función auxiliar para hacer peticiones PUT con manejo de tokens
|
||||||
|
export const putWithAuth = async (url, data) => {
|
||||||
|
return fetchWithAuth(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función auxiliar para hacer peticiones PATCH con manejo de tokens
|
||||||
|
export const patchWithAuth = async (url, data) => {
|
||||||
|
return fetchWithAuth(url, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función auxiliar para hacer peticiones DELETE con manejo de tokens
|
||||||
|
export const deleteWithAuth = async (url) => {
|
||||||
|
return fetchWithAuth(url, { method: 'DELETE' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función para hacer peticiones con FormData (para archivos)
|
||||||
|
export const postFormDataWithAuth = async (url, formData) => {
|
||||||
|
let token = localStorage.getItem('access');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
...(token && { 'Authorization': `Bearer ${token}` })
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (response.status === 401 && !isRefreshing) {
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newToken = await refreshToken();
|
||||||
|
processQueue(null, newToken);
|
||||||
|
|
||||||
|
options.headers['Authorization'] = `Bearer ${newToken}`;
|
||||||
|
response = await fetch(url, options);
|
||||||
|
|
||||||
|
} catch (refreshError) {
|
||||||
|
processQueue(refreshError, null);
|
||||||
|
throw refreshError;
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
localStorage.removeItem('access');
|
||||||
|
localStorage.removeItem('refresh');
|
||||||
|
localStorage.removeItem('user_id');
|
||||||
|
localStorage.removeItem('user_is_importador');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
throw new Error('Session expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default fetchWithAuth;
|
||||||
|
|||||||
10
src/main.jsx
10
src/main.jsx
@@ -10,10 +10,18 @@ import '@fontsource/roboto/500.css';
|
|||||||
import '@fontsource/roboto/700.css';
|
import '@fontsource/roboto/700.css';
|
||||||
|
|
||||||
|
|
||||||
|
const isDevelopment = import.meta.env.DEV;
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
|
isDevelopment ? (
|
||||||
|
<NotificationProvider>
|
||||||
|
<App />
|
||||||
|
</NotificationProvider>
|
||||||
|
) : (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<App />
|
<App />
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</StrictMode>,
|
</StrictMode>
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { fetchWithAuth } from '../fetchWithAuth';
|
||||||
// Animación fade-in/slide-up para cards
|
// Animación fade-in/slide-up para cards
|
||||||
const fadeInSlideUp = `@keyframes fadein-slideup {
|
const fadeInSlideUp = `@keyframes fadein-slideup {
|
||||||
0% { opacity: 0; transform: translateY(40px); }
|
0% { opacity: 0; transform: translateY(40px); }
|
||||||
@@ -45,33 +46,31 @@ export default function Admin() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
|
||||||
|
|
||||||
// Servicios
|
// Servicios
|
||||||
const resServices = await fetch(`${API_URL}/cards/services-util-information/`, { headers });
|
const resServices = await fetchWithAuth(`${API_URL}/cards/services-util-information/`);
|
||||||
if (!resServices.ok) throw new Error('Error al obtener estados de servicios');
|
if (!resServices.ok) throw new Error('Error al obtener estados de servicios');
|
||||||
const dataServices = await resServices.json();
|
const dataServices = await resServices.json();
|
||||||
setServices(dataServices);
|
setServices(dataServices);
|
||||||
|
|
||||||
// Descargas
|
// Descargas
|
||||||
const resDownloads = await fetch(`${API_URL}/cards/document-util-information/`, { headers });
|
const resDownloads = await fetchWithAuth(`${API_URL}/cards/document-util-information/`);
|
||||||
if (!resDownloads.ok) throw new Error('Error al obtener información de descargas');
|
if (!resDownloads.ok) throw new Error('Error al obtener información de descargas');
|
||||||
const dataDownloads = await resDownloads.json();
|
const dataDownloads = await resDownloads.json();
|
||||||
setDownloads(dataDownloads);
|
setDownloads(dataDownloads);
|
||||||
|
|
||||||
// Últimos documentos
|
// Últimos documentos
|
||||||
const resDocs = await fetch(`${API_URL}/cards/downloaded-documents/`, { headers });
|
const resDocs = await fetchWithAuth(`${API_URL}/cards/downloaded-documents/`);
|
||||||
if (!resDocs.ok) throw new Error('Error al obtener últimos documentos');
|
if (!resDocs.ok) throw new Error('Error al obtener últimos documentos');
|
||||||
const dataDocs = await resDocs.json();
|
const dataDocs = await resDocs.json();
|
||||||
setLatestDocs(dataDocs.documentos);
|
setLatestDocs(dataDocs.documentos);
|
||||||
|
|
||||||
// Análisis de actividad de usuario
|
// Análisis de actividad de usuario
|
||||||
const resUserActivity = await fetch(`${API_URL}/cards/user-activity-analysis/`, { headers });
|
const resUserActivity = await fetchWithAuth(`${API_URL}/cards/user-activity-analysis/`);
|
||||||
if (!resUserActivity.ok) throw new Error('Error al obtener análisis de actividad de usuario');
|
if (!resUserActivity.ok) throw new Error('Error al obtener análisis de actividad de usuario');
|
||||||
const dataUserActivity = await resUserActivity.json();
|
const dataUserActivity = await resUserActivity.json();
|
||||||
setUserActivity(dataUserActivity);
|
setUserActivity(dataUserActivity);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Error fetching admin data:', err);
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
setError(err instanceof Error ? err.message : String(err));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
||||||
import SuccessModal from '../components/SuccessModal.jsx';
|
import SuccessModal from '../components/SuccessModal.jsx';
|
||||||
|
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||||
// Animación fade-in/slide-up para bloques
|
// Animación fade-in/slide-up para bloques
|
||||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||||
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
||||||
@@ -17,25 +18,14 @@ const API_URL = import.meta.env.VITE_EFC_API_URL;
|
|||||||
|
|
||||||
// Descarga individual
|
// Descarga individual
|
||||||
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
||||||
const token = localStorage.getItem('access');
|
try {
|
||||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
alert('No autorizado o error en la descarga');
|
alert('No autorizado o error en la descarga');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -46,6 +36,10 @@ const downloadFile = async (id, filename = 'archivo', setSuccess, setError, show
|
|||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
if (setSuccess) setSuccess('Descarga exitosa');
|
if (setSuccess) setSuccess('Descarga exitosa');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
showMessage('Error al descargar el archivo', 'error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Descarga masiva (bulk)
|
// Descarga masiva (bulk)
|
||||||
@@ -54,28 +48,18 @@ const downloadBulkZip = async (ids, showMessage, setSuccess, nombreZip = 'docume
|
|||||||
showMessage('Selecciona al menos un documento.', 'error');
|
showMessage('Selecciona al menos un documento.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const res = await fetch(`${API_URL}/record/documents/bulk-download/`, {
|
try {
|
||||||
method: 'POST',
|
const res = await postWithAuth(`${API_URL}/record/documents/bulk-download/`, {
|
||||||
headers: {
|
document_ids: ids,
|
||||||
'Authorization': `Bearer ${token}`,
|
pedimento_nombre: nombreZip
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ document_ids: ids, pedimento_nombre: nombreZip }),
|
|
||||||
});
|
});
|
||||||
if (res.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showMessage('No autorizado o error en la descarga masiva', 'error');
|
showMessage('No autorizado o error en la descarga masiva', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -86,6 +70,10 @@ const downloadBulkZip = async (ids, showMessage, setSuccess, nombreZip = 'docume
|
|||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
if (setSuccess) setSuccess('Descarga(s) completada(s)');
|
if (setSuccess) setSuccess('Descarga(s) completada(s)');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in bulk download:', error);
|
||||||
|
showMessage('Error en la descarga masiva', 'error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Documents() {
|
export default function Documents() {
|
||||||
@@ -128,8 +116,7 @@ export default function Documents() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const data = await fetchPedimentoDocuments(currentPage, itemsPerPage, {
|
||||||
const data = await fetchPedimentoDocuments(token, '', currentPage, itemsPerPage, {
|
|
||||||
pedimento_numero: pedimentoNumeroFilter,
|
pedimento_numero: pedimentoNumeroFilter,
|
||||||
extension: extensionFilter,
|
extension: extensionFilter,
|
||||||
document_type: documentTypeFilter,
|
document_type: documentTypeFilter,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
import React, { useEffect, useState, useLayoutEffect, useRef } from 'react';
|
||||||
|
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||||
// Animación fade-in/slide-up para bloques
|
// Animación fade-in/slide-up para bloques
|
||||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||||
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) {
|
||||||
@@ -15,25 +16,14 @@ import { Link } from 'react-router-dom';
|
|||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
|
||||||
const token = localStorage.getItem('access');
|
try {
|
||||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
alert('No autorizado o error en la descarga');
|
alert('No autorizado o error en la descarga');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -43,7 +33,11 @@ const downloadFile = async (id, filename = 'archivo', setSuccess, setError, show
|
|||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
if (setSuccess) setSuccess('Descarga exitosa');useEffect
|
if (setSuccess) setSuccess('Descarga exitosa');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
showMessage('Error al descargar el archivo', 'error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Documents() {
|
export default function Documents() {
|
||||||
@@ -83,7 +77,6 @@ export default function Documents() {
|
|||||||
|
|
||||||
// Fetching usando la función tipada de TypeScript
|
// Fetching usando la función tipada de TypeScript
|
||||||
const fetchPedimentosData = async (page = currentPage, pageSize = itemsPerPage) => {
|
const fetchPedimentosData = async (page = currentPage, pageSize = itemsPerPage) => {
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
// Construir objeto de filtros
|
// Construir objeto de filtros
|
||||||
const filters = {
|
const filters = {
|
||||||
search: searchFilter || undefined,
|
search: searchFilter || undefined,
|
||||||
@@ -98,7 +91,7 @@ export default function Documents() {
|
|||||||
tipo_operacion: tipoOperacionFilter || undefined,
|
tipo_operacion: tipoOperacionFilter || undefined,
|
||||||
clave_pedimento: clavePedimentoFilter || undefined,
|
clave_pedimento: clavePedimentoFilter || undefined,
|
||||||
};
|
};
|
||||||
return await fetchDocuments(token, page, pageSize, filters);
|
return await fetchDocuments(page, pageSize, filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook de polling que se ejecuta cada 30 segundos
|
// Hook de polling que se ejecuta cada 30 segundos
|
||||||
|
|||||||
@@ -12,28 +12,14 @@ export default function Organization() {
|
|||||||
const [animatedPercent, setAnimatedPercent] = useState(0);
|
const [animatedPercent, setAnimatedPercent] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('access');
|
fetchOrganizationUsage()
|
||||||
if (!token) {
|
|
||||||
setError('No se encontró el token de acceso.');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchOrganizationUsage(token)
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setInfo(data);
|
setInfo(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'SESSION_EXPIRED') {
|
console.error('Error fetching organization data:', err);
|
||||||
localStorage.removeItem('access');
|
setError('Error al cargar la información de la organización.');
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [showMessage]);
|
}, [showMessage]);
|
||||||
|
|||||||
@@ -16,31 +16,21 @@ import 'highlight.js/styles/github.css';
|
|||||||
hljs.registerLanguage('xml', xml);
|
hljs.registerLanguage('xml', xml);
|
||||||
// import type removed for JSX compatibility
|
// import type removed for JSX compatibility
|
||||||
import { fetchPedimentoDocuments } from '../api/pedimentoDocuments';
|
import { fetchPedimentoDocuments } from '../api/pedimentoDocuments';
|
||||||
|
import { fetchWithAuth, postWithAuth } from '../fetchWithAuth';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { useNotification } from '../context/NotificationContext';
|
import { useNotification } from '../context/NotificationContext';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
const downloadFile = async (id, filename = 'archivo', showMessage) => {
|
const downloadFile = async (id, filename = 'archivo', showMessage) => {
|
||||||
const token = localStorage.getItem('access');
|
try {
|
||||||
const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, {
|
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
alert('No autorizado o error en la descarga');
|
showMessage('Error en la descarga del archivo', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -50,6 +40,14 @@ const downloadFile = async (id, filename = 'archivo', showMessage) => {
|
|||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
if (error.message === 'SESSION_EXPIRED') {
|
||||||
|
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||||
|
} else {
|
||||||
|
showMessage('Error al descargar el archivo', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
||||||
@@ -57,28 +55,18 @@ const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
|||||||
showMessage('Selecciona al menos un documento.', 'error');
|
showMessage('Selecciona al menos un documento.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const res = await fetch(`${API_URL}/record/documents/bulk-download/`, {
|
try {
|
||||||
method: 'POST',
|
const res = await postWithAuth(`${API_URL}/record/documents/bulk-download/`, {
|
||||||
headers: {
|
document_ids: ids,
|
||||||
'Authorization': `Bearer ${token}`,
|
pedimento_nombre: pedimentoNombre
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ document_ids: ids, pedimento_nombre: pedimentoNombre }),
|
|
||||||
});
|
});
|
||||||
if (res.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showMessage('No autorizado o error en la descarga masiva', 'error');
|
showMessage('Error en la descarga masiva', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -88,6 +76,14 @@ const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => {
|
|||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in bulk download:', error);
|
||||||
|
if (error.message === 'SESSION_EXPIRED') {
|
||||||
|
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||||
|
} else {
|
||||||
|
showMessage('Error en la descarga masiva', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
import { useRef, useLayoutEffect } from 'react';
|
import { useRef, useLayoutEffect } from 'react';
|
||||||
@@ -176,23 +172,8 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
const { showMessage } = useNotification();
|
const { showMessage } = useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('access');
|
fetchWithAuth(`${API_URL}/customs/pedimentos/${id}/`)
|
||||||
fetch(`${API_URL}/customs/pedimentos/${id}/`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.status === 401) {
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
if (!res.ok) throw new Error('No autorizado o error en la petición');
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
@@ -201,7 +182,12 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
console.error('Error fetching pedimento:', err);
|
||||||
|
if (err.message === 'SESSION_EXPIRED') {
|
||||||
|
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||||
|
} else {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [id, showMessage]);
|
}, [id, showMessage]);
|
||||||
@@ -209,10 +195,9 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
// Fetch paginated documents
|
// Fetch paginated documents
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
setDocsLoading(true);
|
setDocsLoading(true);
|
||||||
setDocsError('');
|
setDocsError('');
|
||||||
fetchPedimentoDocuments(token, id, page, pageSize)
|
fetchPedimentoDocuments(id, page, pageSize)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setDocuments(data.results);
|
setDocuments(data.results);
|
||||||
setDocsCount(data.count);
|
setDocsCount(data.count);
|
||||||
@@ -221,13 +206,9 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
setDocsLoading(false);
|
setDocsLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
console.error('Error fetching documents:', err);
|
||||||
if (err.message === 'SESSION_EXPIRED') {
|
if (err.message === 'SESSION_EXPIRED') {
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
} else {
|
||||||
setDocsError(err.message);
|
setDocsError(err.message);
|
||||||
}
|
}
|
||||||
@@ -288,22 +269,14 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
setPreviewXml('');
|
setPreviewXml('');
|
||||||
setPreviewOpen(true);
|
setPreviewOpen(true);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${doc.id}/`);
|
||||||
const res = await fetch(`${API_URL}/record/documents/descargar/${doc.id}/`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.');
|
|
||||||
setPreviewLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
setPreviewError('No autorizado o error en la descarga');
|
setPreviewError('Error al obtener el archivo');
|
||||||
setPreviewLoading(false);
|
setPreviewLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detectar tipo de archivo
|
// Detectar tipo de archivo
|
||||||
let type = '';
|
let type = '';
|
||||||
if (doc.extension) {
|
if (doc.extension) {
|
||||||
@@ -332,7 +305,12 @@ const [docsPrev, setDocsPrev] = useState(null);
|
|||||||
setPreviewLoading(false);
|
setPreviewLoading(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Error in preview:', err);
|
||||||
|
if (err.message === 'SESSION_EXPIRED') {
|
||||||
|
setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.');
|
||||||
|
} else {
|
||||||
setPreviewError('Error al obtener el archivo');
|
setPreviewError('Error al obtener el archivo');
|
||||||
|
}
|
||||||
setPreviewLoading(false);
|
setPreviewLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { fetchProcesamientoPedimentos } from '../api/procesos.ts';
|
import { fetchProcesamientoPedimentos } from '../api/procesos.ts';
|
||||||
|
import { postWithAuth } from '../fetchWithAuth';
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL;
|
const MICROSERVICE_URL = import.meta.env.VITE_EFC_MICROSERVICE_URL;
|
||||||
|
|
||||||
@@ -62,31 +63,26 @@ export default function Procesos() {
|
|||||||
setExecutingId(null);
|
setExecutingId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const body = {
|
||||||
if (!token) {
|
|
||||||
alert('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
|
|
||||||
setExecutingId(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
};
|
|
||||||
const body = JSON.stringify({
|
|
||||||
pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento,
|
pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento,
|
||||||
organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId,
|
organizacion: proc.organizacion_id || proc.organizacion || proc.organizacionId,
|
||||||
});
|
};
|
||||||
const res = await fetch(`${MICROSERVICE_URL}${endpoint}`, {
|
|
||||||
method: 'POST',
|
const res = await postWithAuth(`${MICROSERVICE_URL}${endpoint}`, body);
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al ejecutar el servicio');
|
if (!res.ok) throw new Error('Error al ejecutar el servicio');
|
||||||
|
|
||||||
alert('Servicio ejecutado correctamente');
|
alert('Servicio ejecutado correctamente');
|
||||||
setOpenDropdownId(null);
|
setOpenDropdownId(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Error executing service:', err);
|
||||||
|
if (err.message === 'SESSION_EXPIRED') {
|
||||||
|
alert('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.');
|
||||||
|
} else {
|
||||||
alert('Error al ejecutar el servicio: ' + (err instanceof Error ? err.message : String(err)));
|
alert('Error al ejecutar el servicio: ' + (err instanceof Error ? err.message : String(err)));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setExecutingId(null);
|
setExecutingId(null);
|
||||||
}
|
}
|
||||||
@@ -115,41 +111,29 @@ export default function Procesos() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
// Construir filtros
|
||||||
if (!token) {
|
const filters = {};
|
||||||
setError('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
|
if (pedimentoPedimentoFilter) filters['pedimento__pedimento'] = pedimentoPedimentoFilter;
|
||||||
return;
|
if (estadoFilter) filters['estado'] = estadoFilter;
|
||||||
}
|
if (servicioFilter) filters['servicio'] = servicioFilter;
|
||||||
// Construir query params
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('page', String(page));
|
|
||||||
params.append('page_size', String(itemsPerPage));
|
|
||||||
if (pedimentoPedimentoFilter) params.append('pedimento__pedimento', pedimentoPedimentoFilter);
|
|
||||||
if (estadoFilter) params.append('estado', estadoFilter);
|
|
||||||
if (servicioFilter) params.append('servicio', servicioFilter);
|
|
||||||
if (sortField) {
|
if (sortField) {
|
||||||
params.append('ordering', (sortOrder === 'desc' ? '-' : '') + sortField);
|
filters['ordering'] = (sortOrder === 'desc' ? '-' : '') + sortField;
|
||||||
}
|
}
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
|
||||||
const headers = {
|
console.log('Fetching procesos with filters:', filters);
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
const data = await fetchProcesamientoPedimentos(page, itemsPerPage, filters);
|
||||||
};
|
|
||||||
console.log('Fetching procesos with token:', token ? 'Token present' : 'No token');
|
|
||||||
console.log('URL:', `${API_URL}/customs/procesamientopedimentos/?${params.toString()}`);
|
|
||||||
const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?${params.toString()}`, { headers });
|
|
||||||
console.log('Response status:', res.status);
|
|
||||||
if (!res.ok) {
|
|
||||||
const errorText = await res.text();
|
|
||||||
console.log('Error response:', errorText);
|
|
||||||
throw new Error(`Error al obtener procesamiento de pedimentos: ${res.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
const data = await res.json();
|
|
||||||
console.log('Data received:', data);
|
console.log('Data received:', data);
|
||||||
setProcesos(data.results || []);
|
setProcesos(data.results || []);
|
||||||
setCount(data.count || 0);
|
setCount(data.count || 0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Error fetching procesos:', err);
|
||||||
|
if (err.message === 'SESSION_EXPIRED') {
|
||||||
|
setError('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.');
|
||||||
|
} else {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
setError(err instanceof Error ? err.message : String(err));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
import { getCurrentUser } from '../api/users.ts';
|
import { getCurrentUser } from '../api/users.ts';
|
||||||
import { useNotification } from '../context/NotificationContext';
|
import { useNotification } from '../context/NotificationContext';
|
||||||
|
import { fetchWithAuth, patchWithAuth, postWithAuth, postFormDataWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
// Animación fade-in/slide-up para el componente
|
// Animación fade-in/slide-up para el componente
|
||||||
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`;
|
||||||
@@ -27,6 +28,16 @@ const Settings = () => {
|
|||||||
rfc: ''
|
rfc: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Estados para cambio de contraseña
|
||||||
|
const [passwordData, setPasswordData] = useState({
|
||||||
|
current_password: '',
|
||||||
|
new_password: '',
|
||||||
|
confirm_password: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [passwordErrors, setPasswordErrors] = useState({});
|
||||||
|
const [changingPassword, setChangingPassword] = useState(false);
|
||||||
|
|
||||||
// Estado para controlar la animación de entrada
|
// Estado para controlar la animación de entrada
|
||||||
const [showAnimation, setShowAnimation] = useState(false);
|
const [showAnimation, setShowAnimation] = useState(false);
|
||||||
const [hasAnimated, setHasAnimated] = useState(false);
|
const [hasAnimated, setHasAnimated] = useState(false);
|
||||||
@@ -79,28 +90,10 @@ const Settings = () => {
|
|||||||
|
|
||||||
// Función para actualizar el usuario
|
// Función para actualizar el usuario
|
||||||
const updateUser = async (userData) => {
|
const updateUser = async (userData) => {
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_URL}/user/users/${currentUser.id}/`, {
|
const response = await patchWithAuth(`${API_URL}/user/users/${currentUser.id}/`, userData);
|
||||||
method: 'PATCH',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
@@ -123,6 +116,105 @@ const Settings = () => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Manejar cambios en el formulario de contraseña
|
||||||
|
const handlePasswordChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setPasswordData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
// Limpiar errores cuando el usuario empiece a escribir
|
||||||
|
if (passwordErrors[name]) {
|
||||||
|
setPasswordErrors(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: ''
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validar contraseña
|
||||||
|
const validatePassword = (password) => {
|
||||||
|
const errors = [];
|
||||||
|
if (password.length < 8) {
|
||||||
|
errors.push('Debe tener al menos 8 caracteres');
|
||||||
|
}
|
||||||
|
if (!/(?=.*[a-z])/.test(password)) {
|
||||||
|
errors.push('Debe contener al menos una letra minúscula');
|
||||||
|
}
|
||||||
|
if (!/(?=.*[A-Z])/.test(password)) {
|
||||||
|
errors.push('Debe contener al menos una letra mayúscula');
|
||||||
|
}
|
||||||
|
if (!/(?=.*\d)/.test(password)) {
|
||||||
|
errors.push('Debe contener al menos un número');
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cambiar contraseña
|
||||||
|
const handleChangePassword = async () => {
|
||||||
|
const errors = {};
|
||||||
|
|
||||||
|
// Validaciones
|
||||||
|
if (!passwordData.current_password.trim()) {
|
||||||
|
errors.current_password = 'La contraseña actual es requerida';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordData.new_password.trim()) {
|
||||||
|
errors.new_password = 'La nueva contraseña es requerida';
|
||||||
|
} else {
|
||||||
|
const passwordValidationErrors = validatePassword(passwordData.new_password);
|
||||||
|
if (passwordValidationErrors.length > 0) {
|
||||||
|
errors.new_password = passwordValidationErrors[0]; // Mostrar el primer error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordData.confirm_password.trim()) {
|
||||||
|
errors.confirm_password = 'Confirma tu nueva contraseña';
|
||||||
|
} else if (passwordData.new_password !== passwordData.confirm_password) {
|
||||||
|
errors.confirm_password = 'Las contraseñas no coinciden';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
setPasswordErrors(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setChangingPassword(true);
|
||||||
|
try {
|
||||||
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
|
|
||||||
|
const response = await postWithAuth(`${API_URL}/user/users/${currentUser.id}/change_password/`, {
|
||||||
|
old_password: passwordData.current_password,
|
||||||
|
new_password: passwordData.new_password
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
if (errorData.old_password) {
|
||||||
|
setPasswordErrors({ current_password: 'La contraseña actual es incorrecta' });
|
||||||
|
} else {
|
||||||
|
throw new Error(errorData.detail || 'Error al cambiar la contraseña');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpiar formulario y mostrar mensaje de éxito
|
||||||
|
setPasswordData({
|
||||||
|
current_password: '',
|
||||||
|
new_password: '',
|
||||||
|
confirm_password: ''
|
||||||
|
});
|
||||||
|
setPasswordErrors({});
|
||||||
|
showMessage('Contraseña cambiada exitosamente', 'success');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error changing password:', error);
|
||||||
|
showMessage(error.message || 'Error al cambiar la contraseña', 'error');
|
||||||
|
} finally {
|
||||||
|
setChangingPassword(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Guardar cambios del perfil
|
// Guardar cambios del perfil
|
||||||
const handleSaveProfile = async () => {
|
const handleSaveProfile = async () => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
@@ -642,9 +734,19 @@ const Settings = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
name="current_password"
|
||||||
|
value={passwordData.current_password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||||
|
passwordErrors.current_password
|
||||||
|
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||||
|
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||||
|
}`}
|
||||||
placeholder="Ingresa tu contraseña actual"
|
placeholder="Ingresa tu contraseña actual"
|
||||||
/>
|
/>
|
||||||
|
{passwordErrors.current_password && (
|
||||||
|
<p className="mt-1 text-sm text-red-600">{passwordErrors.current_password}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -653,9 +755,19 @@ const Settings = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
name="new_password"
|
||||||
|
value={passwordData.new_password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||||
|
passwordErrors.new_password
|
||||||
|
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||||
|
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||||
|
}`}
|
||||||
placeholder="Ingresa una nueva contraseña"
|
placeholder="Ingresa una nueva contraseña"
|
||||||
/>
|
/>
|
||||||
|
{passwordErrors.new_password && (
|
||||||
|
<p className="mt-1 text-sm text-red-600">{passwordErrors.new_password}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -664,28 +776,52 @@ const Settings = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 hover:border-gray-400"
|
name="confirm_password"
|
||||||
|
value={passwordData.confirm_password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
className={`w-full px-4 py-3 border rounded-lg shadow-sm focus:outline-none focus:ring-2 text-sm transition-all duration-200 hover:border-gray-400 ${
|
||||||
|
passwordErrors.confirm_password
|
||||||
|
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||||
|
: 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
|
||||||
|
}`}
|
||||||
placeholder="Confirma tu nueva contraseña"
|
placeholder="Confirma tu nueva contraseña"
|
||||||
/>
|
/>
|
||||||
|
{passwordErrors.confirm_password && (
|
||||||
|
<p className="mt-1 text-sm text-red-600">{passwordErrors.confirm_password}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200"
|
onClick={handleChangePassword}
|
||||||
|
disabled={changingPassword}
|
||||||
|
className="inline-flex items-center px-6 py-3 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
|
{changingPassword ? (
|
||||||
|
<>
|
||||||
|
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" 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>
|
||||||
|
Cambiando contraseña...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
Actualizar contraseña
|
Actualizar contraseña
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Información de seguridad adicional */}
|
{/* Información de seguridad adicional */}
|
||||||
<div className="bg-gray-50 rounded-xl p-6">
|
<div className="bg-gray-50 rounded-xl p-6">
|
||||||
<h4 className="text-lg font-semibold text-gray-900 mb-4">Consejos de seguridad</h4>
|
<h4 className="text-lg font-semibold text-gray-900 mb-4">Requisitos de contraseña</h4>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
@@ -694,7 +830,7 @@ const Settings = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-3 text-sm text-gray-600">
|
<p className="ml-3 text-sm text-gray-600">
|
||||||
Usa al menos 8 caracteres con una combinación de letras, números y símbolos
|
Mínimo 8 caracteres de longitud
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
@@ -704,19 +840,65 @@ const Settings = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-3 text-sm text-gray-600">
|
<p className="ml-3 text-sm text-gray-600">
|
||||||
|
Al menos una letra minúscula (a-z)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg className="w-5 h-5 text-green-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="ml-3 text-sm text-gray-600">
|
||||||
|
Al menos una letra mayúscula (A-Z)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg className="w-5 h-5 text-green-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="ml-3 text-sm text-gray-600">
|
||||||
|
Al menos un número (0-9)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-4 border-t border-gray-200">
|
||||||
|
<h5 className="text-sm font-semibold text-gray-900 mb-2">Consejos adicionales</h5>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="ml-2 text-xs text-gray-600">
|
||||||
Evita usar información personal como nombres o fechas de nacimiento
|
Evita usar información personal como nombres o fechas de nacimiento
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<svg className="w-5 h-5 text-green-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-3 text-sm text-gray-600">
|
<p className="ml-2 text-xs text-gray-600">
|
||||||
No reutilices contraseñas de otras cuentas
|
No reutilices contraseñas de otras cuentas
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg className="w-4 h-4 text-blue-500 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="ml-2 text-xs text-gray-600">
|
||||||
|
Considera usar símbolos especiales para mayor seguridad
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { fetchUsers, createUser, updateUser, deleteUser } from '../api/users.ts';
|
import { fetchUsers, createUser, updateUser, deleteUser, getCurrentUser } from '../api/users.ts';
|
||||||
import { useNotification } from '../context/NotificationContext';
|
import { useNotification } from '../context/NotificationContext';
|
||||||
|
|
||||||
const initialForm = {
|
const initialForm = {
|
||||||
@@ -44,33 +44,24 @@ export default function Users() {
|
|||||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||||
const { showMessage } = useNotification();
|
const { showMessage } = useNotification();
|
||||||
|
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
|
|
||||||
const loadUsers = () => {
|
const loadUsers = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetchUsers(token)
|
fetchUsers()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'SESSION_EXPIRED') {
|
console.error('Error loading users:', err);
|
||||||
localStorage.removeItem('access');
|
setError('Error al cargar usuarios');
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Si no hay token, limpiar y redirigir a login inmediatamente
|
// Si no hay token, limpiar y redirigir a login inmediatamente
|
||||||
if (!token) {
|
const accessToken = localStorage.getItem('access');
|
||||||
|
if (!accessToken) {
|
||||||
localStorage.removeItem('access');
|
localStorage.removeItem('access');
|
||||||
localStorage.removeItem('refresh');
|
localStorage.removeItem('refresh');
|
||||||
localStorage.removeItem('username');
|
localStorage.removeItem('username');
|
||||||
@@ -86,27 +77,7 @@ export default function Users() {
|
|||||||
|
|
||||||
loadUsers();
|
loadUsers();
|
||||||
// Siempre sincroniza la información del usuario autenticado en localStorage
|
// Siempre sincroniza la información del usuario autenticado en localStorage
|
||||||
fetch(`${import.meta.env.VITE_EFC_API_URL}/user/users/me/`, {
|
getCurrentUser()
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
})
|
|
||||||
.then(async res => {
|
|
||||||
if (!res.ok) {
|
|
||||||
// Token inválido o expirado, limpiar localStorage y redirigir a login
|
|
||||||
console.log('Token inválido o expirado');
|
|
||||||
localStorage.removeItem('access');
|
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
localStorage.removeItem('username');
|
|
||||||
localStorage.removeItem('user_email');
|
|
||||||
localStorage.removeItem('user_id');
|
|
||||||
localStorage.removeItem('user_groups');
|
|
||||||
localStorage.removeItem('user_first_name');
|
|
||||||
localStorage.removeItem('user_last_name');
|
|
||||||
localStorage.removeItem('user_is_importador');
|
|
||||||
window.location.href = '/login';
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('Respuesta de /api/users/me/:', data);
|
console.log('Respuesta de /api/users/me/:', data);
|
||||||
if (data && data.username) {
|
if (data && data.username) {
|
||||||
@@ -146,13 +117,13 @@ export default function Users() {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
await updateUser(token, editingId, form);
|
await updateUser(editingId, form);
|
||||||
showMessage('Usuario actualizado exitosamente', 'success');
|
showMessage('Usuario actualizado exitosamente', 'success');
|
||||||
setShowEditModal(false);
|
setShowEditModal(false);
|
||||||
} else {
|
} else {
|
||||||
const groups = createType === 'importador' ? [3, 5] : [4, 3];
|
const groups = createType === 'importador' ? [3, 5] : [4, 3];
|
||||||
const extra = createType === 'importador' ? { is_importador: true } : {};
|
const extra = createType === 'importador' ? { is_importador: true } : {};
|
||||||
await createUser(token, { ...form, groups, ...extra });
|
await createUser({ ...form, groups, ...extra });
|
||||||
showMessage(createType === 'importador' ? 'Importador creado exitosamente' : 'Usuario creado exitosamente', 'success');
|
showMessage(createType === 'importador' ? 'Importador creado exitosamente' : 'Usuario creado exitosamente', 'success');
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
}
|
}
|
||||||
@@ -170,7 +141,7 @@ export default function Users() {
|
|||||||
if (!userToDelete) return;
|
if (!userToDelete) return;
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await deleteUser(token, userToDelete.id);
|
await deleteUser(userToDelete.id);
|
||||||
showMessage('Usuario eliminado exitosamente', 'success');
|
showMessage('Usuario eliminado exitosamente', 'success');
|
||||||
setShowDeleteModal(false);
|
setShowDeleteModal(false);
|
||||||
setUserToDelete(null);
|
setUserToDelete(null);
|
||||||
|
|||||||
@@ -24,26 +24,16 @@ export default function Users() {
|
|||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const { showMessage } = useNotification();
|
const { showMessage } = useNotification();
|
||||||
|
|
||||||
const token = localStorage.getItem('access');
|
|
||||||
|
|
||||||
const loadUsers = () => {
|
const loadUsers = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetchUsers(token)
|
fetchUsers()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setUsers(data);
|
setUsers(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'SESSION_EXPIRED') {
|
console.error('Error loading users:', err);
|
||||||
localStorage.removeItem('access');
|
setError('Error al cargar usuarios');
|
||||||
localStorage.removeItem('refresh');
|
|
||||||
showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -62,11 +52,11 @@ export default function Users() {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
await updateUser(token, editingId, form);
|
await updateUser(editingId, form);
|
||||||
showMessage('Usuario actualizado exitosamente', 'success');
|
showMessage('Usuario actualizado exitosamente', 'success');
|
||||||
setShowEditModal(false);
|
setShowEditModal(false);
|
||||||
} else {
|
} else {
|
||||||
await createUser(token, form);
|
await createUser(form);
|
||||||
showMessage('Usuario creado exitosamente', 'success');
|
showMessage('Usuario creado exitosamente', 'success');
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
}
|
}
|
||||||
@@ -101,7 +91,7 @@ export default function Users() {
|
|||||||
if (!userToDelete) return;
|
if (!userToDelete) return;
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await deleteUser(token, userToDelete.id);
|
await deleteUser(userToDelete.id);
|
||||||
showMessage('Usuario eliminado exitosamente', 'success');
|
showMessage('Usuario eliminado exitosamente', 'success');
|
||||||
setShowDeleteModal(false);
|
setShowDeleteModal(false);
|
||||||
setUserToDelete(null);
|
setUserToDelete(null);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { fetchWithAuth, postWithAuth, putWithAuth, deleteWithAuth } from '../fetchWithAuth';
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
const API_URL = import.meta.env.VITE_EFC_API_URL;
|
||||||
export default function Vucem() {
|
export default function Vucem() {
|
||||||
@@ -118,10 +119,7 @@ export default function Vucem() {
|
|||||||
const fetchVucem = async () => {
|
const fetchVucem = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/`);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/`, {
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al cargar VUCEM');
|
if (!res.ok) throw new Error('Error al cargar VUCEM');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setVucemList(data);
|
setVucemList(data);
|
||||||
@@ -135,10 +133,7 @@ export default function Vucem() {
|
|||||||
// Funciones de descarga
|
// Funciones de descarga
|
||||||
const downloadCertificate = async (id, usuario) => {
|
const downloadCertificate = async (id, usuario) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_cer/`);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_cer/`, {
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
@@ -171,10 +166,7 @@ export default function Vucem() {
|
|||||||
|
|
||||||
const downloadKey = async (id, usuario) => {
|
const downloadKey = async (id, usuario) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await fetchWithAuth(`${API_URL}/vucem/vucem/${id}/download_key/`);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_key/`, {
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 404) {
|
if (res.status === 404) {
|
||||||
@@ -939,12 +931,7 @@ export default function Vucem() {
|
|||||||
formData.append('acuseedocument', form.acuseedocument);
|
formData.append('acuseedocument', form.acuseedocument);
|
||||||
formData.append('is_active', form.is_active);
|
formData.append('is_active', form.is_active);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await postWithAuth(`${API_URL}/vucem/vucem/`, formData);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al crear VUCEM');
|
if (!res.ok) throw new Error('Error al crear VUCEM');
|
||||||
await fetchVucem();
|
await fetchVucem();
|
||||||
closeModals();
|
closeModals();
|
||||||
@@ -1108,12 +1095,7 @@ export default function Vucem() {
|
|||||||
formData.append('acuseedocument', form.acuseedocument);
|
formData.append('acuseedocument', form.acuseedocument);
|
||||||
formData.append('is_active', form.is_active);
|
formData.append('is_active', form.is_active);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await putWithAuth(`${API_URL}/vucem/vucem/${editVucem.id}/`, formData);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/${editVucem.id}/`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al actualizar VUCEM');
|
if (!res.ok) throw new Error('Error al actualizar VUCEM');
|
||||||
await fetchVucem();
|
await fetchVucem();
|
||||||
closeModals();
|
closeModals();
|
||||||
@@ -1273,11 +1255,7 @@ export default function Vucem() {
|
|||||||
<button type="button" onClick={async () => {
|
<button type="button" onClick={async () => {
|
||||||
if (!deleteVucem) return;
|
if (!deleteVucem) return;
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('access');
|
const res = await deleteWithAuth(`${API_URL}/vucem/vucem/${deleteVucem.id}/`);
|
||||||
const res = await fetch(`${API_URL}/vucem/vucem/${deleteVucem.id}/`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error('Error al eliminar VUCEM');
|
if (!res.ok) throw new Error('Error al eliminar VUCEM');
|
||||||
await fetchVucem();
|
await fetchVucem();
|
||||||
closeModals();
|
closeModals();
|
||||||
|
|||||||
Reference in New Issue
Block a user