Compare commits
3 Commits
feature/do
...
admin
| Author | SHA1 | Date | |
|---|---|---|---|
| e69fca99c0 | |||
| e371af3706 | |||
| 3f640307f8 |
@@ -312,72 +312,6 @@ export default function Admin() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Análisis de actividad de usuario */}
|
|
||||||
{!(typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true') && !isGroup35 && (
|
|
||||||
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
|
||||||
style={{
|
|
||||||
animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.65s forwards',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/3 to-purple-500/3"></div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<div className="flex items-center gap-3 mb-4 sm:mb-6">
|
|
||||||
<div className="bg-gradient-to-br from-indigo-500 to-purple-600 rounded-full p-3 shadow-lg">
|
|
||||||
<svg className="h-6 w-6 sm:h-7 sm:w-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-xl sm:text-2xl font-bold text-gray-900">Actividad de Usuarios</h3>
|
|
||||||
</div>
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center py-8">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600"></div>
|
|
||||||
<span className="ml-3 text-gray-500">Cargando...</span>
|
|
||||||
</div>
|
|
||||||
) : error ? (
|
|
||||||
<div className="text-red-600 bg-red-50 p-4 rounded-xl border border-red-200">{error}</div>
|
|
||||||
) : userActivity ? (
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
|
|
||||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-2xl p-4 sm:p-6 border border-blue-100">
|
|
||||||
<h4 className="font-bold text-gray-800 mb-4 flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
||||||
Resumen de acciones
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(userActivity.actions_count).map(([action, count]) => (
|
|
||||||
<div key={action} className="flex justify-between items-center bg-white rounded-xl p-3 shadow-sm border border-blue-100">
|
|
||||||
<span className="capitalize text-gray-700 font-medium">{action}</span>
|
|
||||||
<span className="font-mono text-blue-700 bg-blue-100 px-2 py-1 rounded-lg text-sm font-bold">{count}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="flex justify-between items-center bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-xl p-3 shadow-lg font-semibold">
|
|
||||||
<span>Total actividades</span>
|
|
||||||
<span className="font-mono bg-white/20 px-2 py-1 rounded-lg">{userActivity.actividades_filtradas}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-2xl p-4 sm:p-6 border border-green-100">
|
|
||||||
<h4 className="font-bold text-gray-800 mb-4 flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
Top usuarios
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{userActivity.top_users.map((user, idx) => (
|
|
||||||
<div key={user.username} className="flex justify-between items-center bg-white rounded-xl p-3 shadow-sm border border-green-100">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="bg-green-100 text-green-800 rounded-full w-6 h-6 flex items-center justify-center text-xs font-bold">{idx + 1}</span>
|
|
||||||
<span className="text-gray-700 font-medium">{user.username}</span>
|
|
||||||
</div>
|
|
||||||
<span className="font-mono text-green-700 bg-green-100 px-2 py-1 rounded-lg text-sm font-bold">{user.activity_count}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tabla de últimos documentos */}
|
{/* Tabla de últimos documentos */}
|
||||||
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 mb-6 sm:mb-8 animate-fadein-slideup opacity-0 relative overflow-hidden"
|
||||||
|
|||||||
@@ -1524,6 +1524,65 @@ export default function PedimentoDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Funciones para procesar peticiones
|
// Funciones para procesar peticiones
|
||||||
|
// Descargar todos los AcuseCoves
|
||||||
|
// Ejecutar la acción de procesar AcuseCove para todos los COVEs visibles
|
||||||
|
// Ejecutar la acción de procesar AcuseCove solo para los que no están descargados
|
||||||
|
const handleDownloadAllAcuseCoves = async () => {
|
||||||
|
if (!coves || coves.length === 0) {
|
||||||
|
showMessage('No hay AcuseCoves por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = coves.filter(cove => !cove.acuse_cove_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay AcuseCoves pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const cove of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleAcuseCoveProcess(cove);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) {
|
||||||
|
showMessage(`${successCount} AcuseCove(s) procesados exitosamente`, 'success');
|
||||||
|
}
|
||||||
|
if (errorCount > 0) {
|
||||||
|
showMessage(`${errorCount} AcuseCove(s) no se pudieron procesar`, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ejecutar la acción de procesar COVE solo para los que no están descargados
|
||||||
|
const handleDownloadAllCoves = async () => {
|
||||||
|
if (!coves || coves.length === 0) {
|
||||||
|
showMessage('No hay COVEs por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = coves.filter(cove => !cove.cove_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay COVEs pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const cove of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleCoveProcess(cove);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) {
|
||||||
|
showMessage(`${successCount} COVE(s) procesados exitosamente`, 'success');
|
||||||
|
}
|
||||||
|
if (errorCount > 0) {
|
||||||
|
showMessage(`${errorCount} COVE(s) no se pudieron procesar`, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
const handleCoveRequest = async (cove) => {
|
const handleCoveRequest = async (cove) => {
|
||||||
console.log('Request cove:', cove);
|
console.log('Request cove:', cove);
|
||||||
showMessage(`Procesando petición para COVE #${cove.numero_cove}...`, 'info');
|
showMessage(`Procesando petición para COVE #${cove.numero_cove}...`, 'info');
|
||||||
@@ -2762,7 +2821,37 @@ export default function PedimentoDetail() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Tabla de partidas */}
|
{/* Botón Descargar partidas y tabla de partidas */}
|
||||||
|
<div className="flex justify-end mb-2">
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm 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"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!partidas || partidas.length === 0) {
|
||||||
|
showMessage('No hay partidas para procesar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = partidas.filter(p => !p.descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay partidas pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const partida of pendientes) {
|
||||||
|
try {
|
||||||
|
await handlePartidaRequest(partida);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) showMessage(`${successCount} partida(s) procesadas exitosamente`, 'success');
|
||||||
|
if (errorCount > 0) showMessage(`${errorCount} partida(s) no se pudieron procesar`, 'error');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Descargar partidas
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-300">
|
<table className="min-w-full divide-y divide-gray-300">
|
||||||
@@ -2957,6 +3046,24 @@ export default function PedimentoDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Botones para descargar todos los AcuseCoves y COVEs */}
|
||||||
|
{coves.length > 0 && (
|
||||||
|
<div className="mb-2 flex flex-col sm:flex-row gap-2 justify-end">
|
||||||
|
<button
|
||||||
|
onClick={handleDownloadAllAcuseCoves}
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
Descargar todos los AcuseCoves
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleDownloadAllCoves}
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
Descargar todos los COVEs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Filtros */}
|
{/* Filtros */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 p-3 sm:p-4 bg-gray-50 rounded-lg border">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 p-3 sm:p-4 bg-gray-50 rounded-lg border">
|
||||||
<div>
|
<div>
|
||||||
@@ -3230,6 +3337,64 @@ export default function PedimentoDetail() {
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
{/* Header de la sección */}
|
{/* Header de la sección */}
|
||||||
<div className="mb-6 space-y-4">
|
<div className="mb-6 space-y-4">
|
||||||
|
<div className="flex flex-col gap-2 mb-2 sm:flex-row sm:justify-end">
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm 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"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!edocs || edocs.length === 0) {
|
||||||
|
showMessage('No hay EDocs para procesar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = edocs.filter(e => !e.edocument_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay EDocs pendientes por procesar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const edoc of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleEdocProcess(edoc);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) showMessage(`${successCount} EDoc(s) procesados exitosamente`, 'success');
|
||||||
|
if (errorCount > 0) showMessage(`${errorCount} EDoc(s) no se pudieron procesar`, 'error');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Descargar todos los EDocs
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!edocs || edocs.length === 0) {
|
||||||
|
showMessage('No hay acuses de EDocs para descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = edocs.filter(e => !e.acuse_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay acuses de EDocs pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const edoc of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleAcuseEdocDownload(edoc);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) showMessage(`${successCount} acuse(s) de EDoc descargados exitosamente`, 'success');
|
||||||
|
if (errorCount > 0) showMessage(`${errorCount} acuse(s) de EDoc no se pudieron descargar`, 'error');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Descargar Todos los acuses
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
@@ -3686,6 +3851,64 @@ export default function PedimentoDetail() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Tabla de Procesos */}
|
{/* Tabla de Procesos */}
|
||||||
|
<div className="flex flex-col gap-2 mb-2 sm:flex-row sm:justify-end">
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm 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"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!edocs || edocs.length === 0) {
|
||||||
|
showMessage('No hay EDocs para procesar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = edocs.filter(e => !e.edocument_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay EDocs pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const edoc of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleEdocProcess(edoc);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) showMessage(`${successCount} EDoc(s) procesados exitosamente`, 'success');
|
||||||
|
if (errorCount > 0) showMessage(`${errorCount} EDoc(s) no se pudieron procesar`, 'error');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Descargar todos los EDocs
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!edocs || edocs.length === 0) {
|
||||||
|
showMessage('No hay Acuses de EDocs para procesar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pendientes = edocs.filter(e => !e.acuse_descargado);
|
||||||
|
if (pendientes.length === 0) {
|
||||||
|
showMessage('No hay Acuses de EDocs pendientes por descargar', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
for (const edoc of pendientes) {
|
||||||
|
try {
|
||||||
|
await handleAcuseEdocProcess(edoc);
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (successCount > 0) showMessage(`${successCount} Acuse(s) de EDoc procesados exitosamente`, 'success');
|
||||||
|
if (errorCount > 0) showMessage(`${errorCount} Acuse(s) de EDoc no se pudieron procesar`, 'error');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Descargar todos los Acuses de EDocs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-300">
|
<table className="min-w-full divide-y divide-gray-300">
|
||||||
|
|||||||
Reference in New Issue
Block a user