feature/nueva funcionalidad al hacer login mediante HUB y nuevos reportes en segundo plano

This commit is contained in:
2026-06-15 11:22:26 -06:00
parent 45548f9bc8
commit 14db21c671
6 changed files with 220 additions and 188 deletions

View File

@@ -935,21 +935,11 @@ export default function Reports() {
}
setIsExporting(true);
try {
const progressDiv = document.createElement('div');
progressDiv.className = 'fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-xl border border-blue-100';
progressDiv.innerHTML = `
<div class="flex items-center gap-3">
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-500"></div>
<p class="text-sm text-gray-600">Preparando exportación DataStage...</p>
</div>
`;
document.body.appendChild(progressDiv);
try {
// DETECCIÓN AUTOMÁTICA DEL MODO
const modo = modelosConCampos.length > 1 ? 'multiple' : 'simple';
const exportData = {
modo: modo,
format: exportFormat,
@@ -959,11 +949,10 @@ export default function Reports() {
if (modo === 'simple') {
// MODO SIMPLE: solo un modelo con campos
const modeloUnico = modelosConCampos[0];
const modelData = datastageModels.find(m => m.model === modeloUnico);
exportData.model = modeloUnico;
exportData.fields = modelFieldsMap[modeloUnico];
} else {
// MODO MÚLTIPLE: varios modelos con campos
exportData.models = modelosConCampos.map(modelo => {
@@ -976,14 +965,8 @@ export default function Reports() {
});
}
// Resto del código de exportación...
progressDiv.innerHTML = `
<div class="flex items-center gap-3">
<div class="animate-spin rounded-full h-5 w-5 border-b-2 border-green-500"></div>
<p class="text-sm text-gray-600">Generando archivo DataStage...</p>
</div>
`;
// El backend encola la generación en Celery y responde 202 con task_id;
// el progreso llega por SSE al TaskProgressCard y ahí se descarga el archivo.
const response = await fetchWithAuth(`${API_URL}/reports/exportmodel/datastage/`, {
method: 'POST',
headers: {
@@ -997,68 +980,26 @@ export default function Reports() {
throw new Error(errorData.error || errorData.message || 'Error al exportar DataStage');
}
const blob = await response.blob();
const contentDisposition = response.headers.get('Content-Disposition');
let fileName = '';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="(.+?)"/) ||
contentDisposition.match(/filename=([^;]+)/);
if (filenameMatch) {
fileName = filenameMatch[1].replace(/"/g, '');
}
}
if (!fileName) {
const isZip = blob.type === 'application/zip';
const isExcel = blob.type.includes('spreadsheetml');
if (isZip) {
fileName = modo === 'multiple'
? `datastage_reports_${new Date().toISOString().split('T')[0]}.zip`
: `datastage_${modelosConCampos[0]}_particionado_${new Date().toISOString().split('T')[0]}.zip`;
} else if (isExcel) {
fileName = `datastage_${modelosConCampos[0]}_${new Date().toISOString().split('T')[0]}.xlsx`;
} else {
fileName = `datastage_${modelosConCampos[0]}_${new Date().toISOString().split('T')[0]}.csv`;
}
const data = await response.json();
if (data.task_id) {
addTask({
task_id: data.task_id,
label: 'Reporte DataStage',
organizacion_id: globalFilters.organizacion,
taskType: 'report',
report_id: data.report_id,
status: 'submitted',
});
pendingReportTasksRef.current.add(data.task_id);
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
showMessage('Reporte solicitado. Puedes ver el progreso en la barra inferior.', 'success');
progressDiv.innerHTML = `
<div class="flex items-center gap-3 text-green-600">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<p class="text-sm">¡Exportación completada! (Modo ${modo})</p>
</div>
`;
setTimeout(() => {
progressDiv.style.opacity = '0';
progressDiv.style.transform = 'translateY(100%)';
setTimeout(() => progressDiv.remove(), 300);
}, 2000);
showMessage(`¡Archivo ${fileName} descargado exitosamente! (${modo === 'multiple' ? 'Múltiple' : 'Simple'})`, 'success');
} catch (error) {
console.error('❌ ERROR AL EXPORTAR DATASTAGE:', error);
showMessage(error.message || 'Error al exportar DataStage. Por favor intente nuevamente.', 'error');
} finally {
setIsExporting(false);
const progressDiv = document.querySelector('.fixed.bottom-4.right-4');
if (progressDiv) progressDiv.remove();
}
};
@@ -1795,10 +1736,61 @@ export default function Reports() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
)}
<span>{isExporting ? 'Generando archivo...' : `Generar y descargar ${exportFormat.toUpperCase()}`}</span>
<span>{isExporting ? 'Generando archivo...' : `Generar reporte ${exportFormat.toUpperCase()}`}</span>
</span>
</button>
<div className="mt-10">
<h2 className="text-lg font-bold text-slate-700 mb-4">Historial de Reportes</h2>
<div className="overflow-x-auto">
<table className="min-w-full bg-white rounded-lg shadow border border-slate-200">
<thead>
<tr className="bg-slate-100">
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">ID</th>
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Estado</th>
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Creado</th>
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Finalizado</th>
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Error</th>
<th className="px-4 py-2 text-left text-xs font-semibold text-slate-600">Descargar</th>
</tr>
</thead>
<tbody>
{reports.filter(r => r.report_type === 'datastage').length > 0 ? (
reports
.filter(r => r.report_type === 'datastage')
.map((r) => (
<tr key={r.report_id}>
<td className="px-4 py-2 text-xs text-slate-700">{r.report_id}</td>
<td className="px-4 py-2 text-xs text-slate-700">{r.status}</td>
<td className="px-4 py-2 text-xs text-slate-700">{r.created_at}</td>
<td className="px-4 py-2 text-xs text-slate-700">{r.finished_at}</td>
<td className="px-4 py-2 text-xs text-red-500">{r.error_message ? r.error_message : '-'}</td>
<td className="px-4 py-2 text-xs">
{r.status === 'ready' ? (
<button
className="text-blue-600 hover:underline"
onClick={() => handleDownloadReport(r.report_id)}
>
Descargar
</button>
) : (
<span className="text-slate-400">-</span>
)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={6} className="px-4 py-2 text-center text-slate-400">
No hay reportes de datastage
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
<style>{`
.skeleton-animation {
animation: shimmer 2s linear infinite;