cambios en datastage

This commit is contained in:
2025-08-21 12:11:50 -06:00
parent 6eea8c8ac1
commit da454447a9
3 changed files with 130 additions and 10 deletions

View File

@@ -1,3 +1,10 @@
// Consultar el estado de un task por task_id
export async function fetchTaskStatus(task_id) {
const url = `${import.meta.env.VITE_EFC_API_URL}/datastage/datastages/task-status/?task_id=${encodeURIComponent(task_id)}`;
const res = await getWithAuth(url);
if (!res.ok) throw new Error('Error al consultar el estado del task');
return res.json();
}
import.meta.env; import.meta.env;
import { getWithAuth, postWithAuth, patchWithAuth, deleteWithAuth } from '../fetchWithAuth'; import { getWithAuth, postWithAuth, patchWithAuth, deleteWithAuth } from '../fetchWithAuth';

View File

@@ -1,17 +1,124 @@
import React from 'react'; import React, { useState } from 'react';
import { fetchTaskStatus } from '../api/datastage.js';
export default function SuccessModal({ open, onClose, message = 'Descarga exitosa' }) { export default function SuccessModal({ open, onClose, message = 'Descarga exitosa' }) {
if (!open) return null; if (!open) return null;
// Detectar si es mensaje de procesamiento iniciado
let isProcessing = false;
let taskId = '';
if (typeof message === 'string' && message.includes('Procesamiento iniciado.') && message.includes('Task ID:')) {
isProcessing = true;
// Extraer taskId
const match = message.match(/Task ID: ([\w-]+)/);
if (match) taskId = match[1];
}
// Estado para mostrar status del task
const [taskStatus, setTaskStatus] = useState(null);
const [loadingStatus, setLoadingStatus] = useState(false);
const [errorStatus, setErrorStatus] = useState('');
// Estado para subtasks
const [subtaskStatus, setSubtaskStatus] = useState({}); // { subtaskId: { loading, error, data } }
const handleCheckStatus = async () => {
setLoadingStatus(true);
setErrorStatus('');
try {
const res = await fetchTaskStatus(taskId);
setTaskStatus(res);
} catch (e) {
setErrorStatus('Error al consultar el estado');
}
setLoadingStatus(false);
};
const handleSubtaskClick = async (subId) => {
setSubtaskStatus(prev => ({ ...prev, [subId]: { loading: true, error: '', data: null } }));
try {
const res = await fetchTaskStatus(subId);
setSubtaskStatus(prev => ({ ...prev, [subId]: { loading: false, error: '', data: res } }));
} catch (e) {
setSubtaskStatus(prev => ({ ...prev, [subId]: { loading: false, error: 'Error al consultar el estado', data: null } }));
}
};
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-40"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-40">
<div className="bg-white rounded-xl shadow-2xl border border-green-200 p-8 max-w-sm w-full flex flex-col items-center animate-fade-in"> <div className={`bg-white rounded-xl shadow-2xl border ${isProcessing ? 'border-blue-300' : 'border-green-200'} p-8 max-w-md w-full flex flex-col items-center animate-fade-in`}>
{isProcessing ? (
<>
{/* Header decorativo */}
<div className="w-full flex items-center gap-3 mb-4 bg-gradient-to-r from-blue-600 to-blue-800 rounded-t-xl p-4 relative">
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-2 shadow-lg">
<svg className="h-7 w-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<ellipse cx="12" cy="7" rx="8" ry="3" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M4 7v10c0 1.657 3.582 3 8 3s8-1.343 8-3V7" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M4 17c0 1.657 3.582 3 8 3s8-1.343 8-3" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="flex-1">
<h2 className="text-lg sm:text-xl font-extrabold text-white tracking-tight">Procesamiento iniciado</h2>
</div>
</div>
<div className="flex flex-col items-center mb-2 mt-2 w-full">
<div
className={`bg-blue-50 border border-blue-200 rounded-lg px-4 py-2 w-full text-center mb-2 cursor-pointer transition hover:bg-blue-100 ${loadingStatus ? 'opacity-60 pointer-events-none' : ''}`}
title="Consultar estado"
onClick={handleCheckStatus}
>
<span className="block text-xs text-blue-800 font-semibold tracking-wide">Task ID:</span>
<span className="block text-sm font-mono text-blue-900 break-all underline hover:text-blue-700">{taskId}</span>
{loadingStatus && <div className="text-xs text-blue-700 mt-1">Consultando estado...</div>}
{errorStatus && <div className="text-red-600 text-xs mt-1">{errorStatus}</div>}
{taskStatus && (
<>
<div className="mt-2 text-xs text-blue-800 font-semibold">Estado: <span className="font-mono text-blue-900">{taskStatus.status}</span></div>
{taskStatus.result && (
<div className="text-xs text-blue-800 mt-2 text-left">
<div className="mb-1"><span className="font-semibold">Group ID:</span> <span className="font-mono text-blue-900 break-all">{taskStatus.result.group_id}</span></div>
<div className="mb-1"><span className="font-semibold">Subtask IDs:</span>
<ul className="list-disc list-inside ml-4 max-h-32 overflow-y-auto">
{Array.isArray(taskStatus.result.subtask_ids) && taskStatus.result.subtask_ids.map(id => (
<li key={id} className="font-mono text-blue-900 break-all text-xs cursor-pointer underline hover:text-blue-700"
title="Consultar estado de subtask"
onClick={() => handleSubtaskClick(id)}>
{id}
{subtaskStatus[id]?.loading && <span className="ml-2 text-blue-600">Consultando...</span>}
{subtaskStatus[id]?.error && <div className="text-red-600 text-xs mt-1">{subtaskStatus[id].error}</div>}
{subtaskStatus[id]?.data && (
<div className="mt-1 text-xs text-blue-800 bg-blue-50 border border-blue-200 rounded p-2 text-left">
<div><span className="font-semibold">Estado:</span> <span className="font-mono text-blue-900">{subtaskStatus[id].data.status}</span></div>
{subtaskStatus[id].data.result && (
<div className="mt-1"><span className="font-semibold">Resultado:</span> <span className="font-mono text-blue-900 break-all">{typeof subtaskStatus[id].data.result === 'object' ? JSON.stringify(subtaskStatus[id].data.result) : subtaskStatus[id].data.result}</span></div>
)}
</div>
)}
</li>
))}
</ul>
</div>
{taskStatus.result.detail && (
<div className="mb-1"><span className="font-semibold">Detalle:</span> <span className="text-blue-900">{taskStatus.result.detail}</span></div>
)}
</div>
)}
</>
)}
</div>
</div>
</>
) : (
<>
<svg className="h-12 w-12 text-green-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="h-12 w-12 text-green-500 mb-4" 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="M5 13l4 4L19 7" />
</svg> </svg>
<h2 className="text-2xl font-bold text-green-700 mb-2">{message}</h2> <h2 className="text-2xl font-bold text-green-700 mb-2">{message}</h2>
</>
)}
<button <button
onClick={onClose} onClick={onClose}
className="mt-4 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold shadow hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-400" className={`mt-4 px-6 py-2 ${isProcessing ? 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-400' : 'bg-green-600 hover:bg-green-700 focus:ring-green-400'} text-white rounded-lg font-semibold shadow focus:outline-none focus:ring-2`}
> >
Cerrar Cerrar
</button> </button>

View File

@@ -84,6 +84,8 @@ async function procesarDatastage(item, setDatastages, setSuccess, setError, setR
} else { } else {
setSuccess('Procesado correctamente'); setSuccess('Procesado correctamente');
} }
// El modal de éxito se debe mostrar en el componente principal después de setSuccess
// No se llama aquí
if (data && data.registros_cargados) { if (data && data.registros_cargados) {
setRegistrosCargados(data.registros_cargados); setRegistrosCargados(data.registros_cargados);
setShowRegistrosModal(true); setShowRegistrosModal(true);
@@ -136,6 +138,10 @@ export default function Datastage() {
useLayoutEffect(() => { setShowAnimation(true); }, []); useLayoutEffect(() => { setShowAnimation(true); }, []);
useEffect(() => { if (showAnimation && !hasAnimated) setTimeout(() => setHasAnimated(true), 800); }, [showAnimation, hasAnimated]); useEffect(() => { if (showAnimation && !hasAnimated) setTimeout(() => setHasAnimated(true), 800); }, [showAnimation, hasAnimated]);
// Mostrar modal de éxito cuando cambia el mensaje de success
useEffect(() => {
if (success) setShowSuccessModal(true);
}, [success]);
// Cargar lista // Cargar lista
const load = async () => { const load = async () => {
setLoading(true); setLoading(true);