Se agregaron cambios en datastage y landingpage

This commit is contained in:
2025-08-15 16:04:05 -06:00
parent 1e88f138ee
commit 45b004c1ba
3 changed files with 144 additions and 118 deletions

View File

@@ -315,12 +315,14 @@ export default function Datastage() {
</div>
{/* Lista */}
{/* Responsive: tabla en desktop, tarjetas en móvil/tablet */}
<div className="bg-white shadow-lg rounded-xl border border-gray-200">
<div className="px-4 sm:px-6 lg:px-8 pt-6 sm:pt-8 pb-2 border-b border-gray-200">
<h2 className="text-xl sm:text-2xl font-extrabold text-blue-800 tracking-tight mb-1">Lista de Datastages</h2>
<div className="h-1 w-10 bg-blue-400 rounded mb-2"></div>
</div>
<div className="overflow-x-auto">
{/* Tabla para pantallas grandes */}
<div className="hidden lg:block overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 rounded-lg overflow-hidden text-xs bg-white">
<thead className="bg-slate-100">
<tr>
@@ -339,20 +341,19 @@ export default function Datastage() {
<td className="border px-2 py-2 text-center">{item.id}</td>
<td className="border px-2 py-2 max-w-xs truncate">
{item.archivo ? (
<>
<span className="block text-xs text-gray-700 truncate font-mono">
{(() => {
try {
const url = new URL(item.archivo);
return decodeURIComponent(url.pathname.split('/').pop() || '');
} catch {
return '';
}
})()}
</span>
<span className="flex items-center gap-1 text-xs text-gray-700 truncate font-mono">
{(() => {
try {
const url = new URL(item.archivo);
return decodeURIComponent(url.pathname.split('/').pop() || '');
} catch {
return '';
}
})()}
<button
type="button"
className="text-blue-600 underline break-all hover:text-blue-800"
className="inline-flex items-center justify-center w-6 h-6 rounded bg-blue-100 border border-blue-200 text-blue-700 hover:bg-blue-200 hover:border-blue-300 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-400 ml-1"
title="Descargar archivo"
onClick={() => downloadDatastageFile(
item.id,
(() => {
@@ -365,9 +366,11 @@ export default function Datastage() {
})()
)}
>
Descargar
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5 5-5M12 15V3" />
</svg>
</button>
</>
</span>
) : (
<span className="text-gray-400">Sin archivo</span>
)}
@@ -381,22 +384,33 @@ export default function Datastage() {
<td className="border px-2 py-2 whitespace-nowrap">{item.created_at ? new Date(item.created_at).toLocaleString() : ''}</td>
<td className="border px-2 py-2 whitespace-nowrap">{item.updated_at ? new Date(item.updated_at).toLocaleString() : ''}</td>
<td className="border px-2 py-2 space-x-2 text-center">
<button onClick={() => handleSelect(item.id)} className="inline-flex items-center gap-1 text-blue-600 underline hover:text-blue-800">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
<button onClick={() => handleSelect(item.id)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-blue-50 border border-blue-200 text-blue-700 hover:bg-blue-100 hover:border-blue-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
title="Ver detalle"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
</button>
<button onClick={() => openEditModal(item)} className="inline-flex items-center gap-1 text-yellow-600 underline hover:text-yellow-800">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.232 5.232l3.536 3.536M9 13l6-6 3.536 3.536a2 2 0 010 2.828l-7.072 7.072a2 2 0 01-2.828 0l-3.536-3.536a2 2 0 010-2.828l7.072-7.072z" /></svg>
<button onClick={() => openEditModal(item)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-yellow-50 border border-yellow-200 text-yellow-700 hover:bg-yellow-100 hover:border-yellow-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-400"
title="Editar"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.232 5.232l3.536 3.536M9 13l6-6 3.536 3.536a2 2 0 010 2.828l-7.072 7.072a2 2 0 01-2.828 0l-3.536-3.536a2 2 0 010-2.828l7.072-7.072z" /></svg>
</button>
<button onClick={() => openDeleteModal(item.id)} className="inline-flex items-center gap-1 text-red-600 underline hover:text-red-800">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /></svg>
<button onClick={() => openDeleteModal(item.id)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-red-50 border border-red-200 text-red-700 hover:bg-red-100 hover:border-red-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-red-400"
title="Eliminar"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
<button
onClick={() => procesarDatastage(item, setDatastages, setSuccess, setError, setRegistrosCargados, setShowRegistrosModal)}
className="inline-flex items-center gap-1 text-green-700 underline hover:text-green-900"
title="Procesar"
className={`inline-flex items-center justify-center w-9 h-9 rounded-lg border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-1 ${item.procesado ? 'bg-gray-100 border-gray-200 cursor-not-allowed opacity-50' : 'bg-green-50 border-green-200 hover:bg-green-100 hover:border-green-300 focus:ring-green-500 cursor-pointer'}`}
title={item.procesado ? 'Ya procesado' : 'Procesar'}
disabled={item.procesado}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" /></svg>
Procesar
<svg className="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</td>
</tr>
@@ -404,6 +418,82 @@ export default function Datastage() {
</tbody>
</table>
</div>
{/* Tarjetas para móvil/tablet */}
<div className="lg:hidden space-y-4 p-2">
{datastages.length === 0 ? (
<div className="text-center text-gray-400 py-8">No hay datastages disponibles</div>
) : (
datastages.map(item => (
<div key={item.id} className="bg-white rounded-xl shadow border border-gray-200 p-4 flex flex-col gap-2">
<div className="flex items-center justify-between mb-2">
<span className="font-bold text-blue-800">#{item.id}</span>
<span className={item.procesado ? 'bg-green-100 text-green-700 px-2 py-0.5 rounded-full text-xs' : 'bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded-full text-xs'}>
{item.procesado ? 'Procesado' : 'Pendiente'}
</span>
</div>
<div className="flex items-center gap-1 text-xs text-gray-700 break-all font-mono mb-1">
{item.archivo ? (
<span className="flex items-center gap-1">
{(() => { try { const url = new URL(item.archivo); return decodeURIComponent(url.pathname.split('/').pop() || ''); } catch { return ''; } })()}
<button
type="button"
className="inline-flex items-center justify-center w-6 h-6 rounded bg-blue-100 border border-blue-200 text-blue-700 hover:bg-blue-200 hover:border-blue-300 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-400 ml-1"
title="Descargar archivo"
onClick={() => downloadDatastageFile(
item.id,
(() => { try { const url = new URL(item.archivo); return decodeURIComponent(url.pathname.split('/').pop() || ''); } catch { return ''; } })()
)}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5 5-5M12 15V3" />
</svg>
</button>
</span>
) : <span className="text-gray-400">Sin archivo</span>}
</div>
<div className="flex flex-wrap gap-2 text-xs">
<span className="font-semibold">Contribuyente:</span> {item.contribuyente}
</div>
<div className="flex flex-wrap gap-2 text-xs">
<span className="font-semibold">Creado:</span> {item.created_at ? new Date(item.created_at).toLocaleString() : ''}
</div>
<div className="flex flex-wrap gap-2 text-xs">
<span className="font-semibold">Actualizado:</span> {item.updated_at ? new Date(item.updated_at).toLocaleString() : ''}
</div>
<div className="flex gap-2 mt-2">
<button onClick={() => handleSelect(item.id)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-blue-50 border border-blue-200 text-blue-700 hover:bg-blue-100 hover:border-blue-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
title="Ver detalle"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
</button>
<button onClick={() => openEditModal(item)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-yellow-50 border border-yellow-200 text-yellow-700 hover:bg-yellow-100 hover:border-yellow-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-400"
title="Editar"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.232 5.232l3.536 3.536M9 13l6-6 3.536 3.536a2 2 0 010 2.828l-7.072 7.072a2 2 0 01-2.828 0l-3.536-3.536a2 2 0 010-2.828l7.072-7.072z" /></svg>
</button>
<button onClick={() => openDeleteModal(item.id)}
className="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-red-50 border border-red-200 text-red-700 hover:bg-red-100 hover:border-red-300 transition-all duration-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-red-400"
title="Eliminar"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
<button
onClick={() => procesarDatastage(item, setDatastages, setSuccess, setError, setRegistrosCargados, setShowRegistrosModal)}
className={`inline-flex items-center justify-center w-9 h-9 rounded-lg border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-1 ${item.procesado ? 'bg-gray-100 border-gray-200 cursor-not-allowed opacity-50' : 'bg-green-50 border-green-200 hover:bg-green-100 hover:border-green-300 focus:ring-green-500 cursor-pointer'}`}
title={item.procesado ? 'Ya procesado' : 'Procesar'}
disabled={item.procesado}
>
<svg className="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
</div>
))
)}
</div>
</div>
{/* Modales */}