Se mejoro estetica y estandarizaron estilos

This commit is contained in:
2025-08-05 10:30:25 -06:00
parent c3d800ba48
commit aa515c1d01
12 changed files with 3530 additions and 1611 deletions

View File

@@ -64,9 +64,14 @@ export default function Procesos() {
}
try {
const token = localStorage.getItem('access');
if (!token) {
alert('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
setExecutingId(null);
return;
}
const headers = {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
'Authorization': `Bearer ${token}`,
};
const body = JSON.stringify({
pedimento: typeof proc.pedimento === 'object' && proc.pedimento !== null ? proc.pedimento.id : proc.pedimento,
@@ -90,14 +95,19 @@ export default function Procesos() {
// Cierra el dropdown si se hace click fuera
useEffect(() => {
if (openDropdownId === null) return;
function handleClick(e) {
function handleClickOutside(e) {
const el = document.getElementById(`dropdown-acciones-${openDropdownId}`);
if (el && !el.contains(e.target)) {
setOpenDropdownId(null);
}
}
document.addEventListener('mousedown', handleClick);
return () => document.removeEventListener('mousedown', handleClick);
// Usar setTimeout para evitar que el click que abre el dropdown lo cierre inmediatamente
setTimeout(() => {
document.addEventListener('click', handleClickOutside);
}, 100);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, [openDropdownId]);
useEffect(() => {
@@ -106,6 +116,10 @@ export default function Procesos() {
setError('');
try {
const token = localStorage.getItem('access');
if (!token) {
setError('No hay token de autenticación. Por favor, inicia sesión nuevamente.');
return;
}
// Construir query params
const params = new URLSearchParams();
params.append('page', String(page));
@@ -117,10 +131,21 @@ export default function Procesos() {
params.append('ordering', (sortOrder === 'desc' ? '-' : '') + sortField);
}
const API_URL = import.meta.env.VITE_EFC_API_URL;
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
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 });
if (!res.ok) throw new Error('Error al obtener procesamiento de pedimentos');
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);
setProcesos(data.results || []);
setCount(data.count || 0);
} catch (err) {
@@ -133,41 +158,48 @@ export default function Procesos() {
}, [page, itemsPerPage, pedimentoPedimentoFilter, estadoFilter, servicioFilter, sortField, sortOrder]);
return (
<div className="p-6 bg-gray-50 min-h-screen">
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 p-4 sm:p-6 lg:p-8">
<div className="max-w-7xl mx-auto">
<div className="mb-8 relative overflow-hidden rounded-2xl shadow bg-gradient-to-r from-blue-50 via-white to-indigo-50 border border-blue-100 p-8 flex items-center gap-6 animate-fadein-slideup opacity-0" style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' }}>
<div className="flex-shrink-0 bg-blue-100 rounded-full p-4 shadow-md animate-bounce-slow">
<svg className="h-10 w-10 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{/* Header mejorado y responsivo */}
<div className="mb-6 sm:mb-8 relative overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8 flex items-center gap-4 sm:gap-6 animate-fadein-slideup opacity-0"
style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' }}>
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-3 sm:p-4 shadow-lg animate-bounce-slow">
<svg className="h-8 w-8 sm:h-10 sm:w-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 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>
</div>
<div>
<h1 className="text-3xl font-extrabold text-blue-900 tracking-tight mb-1 flex items-center gap-2">
Procesos del Sistema
<span className="inline-block bg-blue-200 text-blue-800 text-xs font-semibold px-2 py-0.5 rounded-full ml-2 animate-fade-in" title="Total de procesos">
{count}
</span>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-white tracking-tight mb-1 flex flex-col sm:flex-row sm:items-center gap-2">
<span>Procesos del Sistema</span>
{count > 0 && (
<span className="inline-block bg-white/20 backdrop-blur-sm text-white text-xs sm:text-sm font-semibold px-3 py-1 rounded-full shadow-lg animate-fade-in">
{count} procesos
</span>
)}
</h1>
<p className="text-lg text-blue-700/80 font-medium">Estado actual de los procesos de la agencia aduanal</p>
<p className="text-sm sm:text-lg text-blue-100 font-medium leading-relaxed">Estado actual de los procesos de la agencia aduanal</p>
</div>
<div className="absolute -top-10 -right-10 opacity-30 pointer-events-none select-none">
<svg width="120" height="120" viewBox="0 0 120 120" fill="none">
<circle cx="60" cy="60" r="50" fill="url(#grad1)" />
<defs>
<linearGradient id="grad1" x1="0" y1="0" x2="120" y2="120" gradientUnits="userSpaceOnUse">
<stop stopColor="#3b82f6" stopOpacity="0.15" />
<stop offset="1" stopColor="#6366f1" stopOpacity="0.10" />
</linearGradient>
</defs>
</svg>
{/* Efectos decorativos de fondo modernos */}
<div className="absolute -top-10 -right-10 opacity-20 pointer-events-none select-none">
<div className="w-32 h-32 bg-white/10 rounded-full blur-xl"></div>
</div>
<div className="absolute -bottom-6 -left-6 opacity-15 pointer-events-none select-none">
<div className="w-24 h-24 bg-white/10 rounded-full blur-lg"></div>
</div>
{/* Partículas flotantes */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-2 h-2 bg-white/30 rounded-full animate-ping"></div>
<div className="absolute top-3/4 right-1/3 w-1 h-1 bg-white/40 rounded-full animate-pulse"></div>
<div className="absolute top-1/2 right-1/4 w-3 h-3 bg-white/20 rounded-full animate-bounce"></div>
</div>
{/* Animaciones CSS */}
<style>{`
@keyframes bounce-slow {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-8px) scale(1.05); }
}
.animate-bounce-slow {
animation: bounce-slow 2.2s infinite;
animation: bounce-slow 3s infinite;
}
@keyframes fadein-slideup {
0% { opacity: 0; transform: translateY(40px); }
@@ -177,200 +209,440 @@ export default function Procesos() {
animation: fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards;
}
@keyframes fade-in {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
from { opacity: 0; transform: scale(0.9) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.animate-fade-in {
animation: fade-in 0.7s ease;
animation: fade-in 0.8s ease-out;
}
`}</style>
</div>
<div className="bg-white rounded-xl shadow-lg border border-gray-200 p-8 animate-fadein-slideup opacity-0" style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' }}>
<div className="flex items-center justify-between mb-2">
<h2 className="text-2xl font-bold text-blue-800">Procesamiento de Pedimentos</h2>
{/* Contenido principal */}
<div className="bg-white rounded-3xl shadow-2xl border border-gray-100 p-4 sm:p-6 lg:p-8 animate-fadein-slideup opacity-0"
style={{ animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' }}>
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-6 gap-4">
<h2 className="text-xl sm:text-2xl font-bold text-gray-900 flex items-center gap-3">
<div className="bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl p-2 shadow-lg">
<svg className="w-5 h-5 sm:w-6 sm:h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
Procesamiento de Pedimentos
</h2>
{count > 0 && (
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl px-4 py-2 border border-blue-100">
<span className="text-sm font-medium text-blue-700">Total de registros: </span>
<span className="text-lg font-bold text-blue-800">{count}</span>
</div>
)}
</div>
{/* Filtros */}
<div className="mb-4 flex flex-wrap gap-4 items-end justify-between">
<div className="flex flex-col flex-1 min-w-[150px]">
<label className="text-xs font-semibold text-gray-700 mb-1">Pedimento</label>
<input
type="text"
value={pedimentoPedimentoFilter}
onChange={e => setPedimentoPedimentoFilter(e.target.value)}
placeholder="Buscar por pedimento..."
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50"
/>
</div>
<div className="flex flex-col flex-1 min-w-[150px]">
<label className="text-xs font-semibold text-gray-700 mb-1">Estado</label>
<select
value={estadoFilter}
onChange={e => setEstadoFilter(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50"
>
<option value="">Todos</option>
<option value="1">En Espera</option>
<option value="2">Procesando</option>
<option value="3">Finalizado</option>
<option value="4">Error</option>
</select>
</div>
<div className="flex flex-col flex-1 min-w-[150px]">
<label className="text-xs font-semibold text-gray-700 mb-1">Servicio</label>
<select
value={servicioFilter}
onChange={e => setServicioFilter(e.target.value)}
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50"
>
<option value="">Todos</option>
<option value="1">Estado de pedimento</option>
<option value="2">Listado de pedimentos</option>
<option value="3">Pedimento Completo</option>
<option value="4">Pedimento Partidas</option>
<option value="5">Pedimento Remesas</option>
<option value="6">Acuse</option>
<option value="7">EDocument</option>
<option value="8">Cove</option>
<option value="9">Acuse Cove</option>
</select>
{/* Filtros responsivos mejorados */}
<div className="mb-6 bg-gradient-to-r from-gray-50 to-slate-50 rounded-2xl p-4 sm:p-6 border border-gray-100">
<h3 className="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
<svg className="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
</svg>
Filtros de búsqueda
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
Pedimento
</label>
<input
type="text"
value={pedimentoPedimentoFilter}
onChange={e => setPedimentoPedimentoFilter(e.target.value)}
placeholder="Buscar por pedimento..."
className="w-full border border-gray-300 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm transition-all duration-200 hover:shadow-md"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
Estado
</label>
<select
value={estadoFilter}
onChange={e => setEstadoFilter(e.target.value)}
className="w-full border border-gray-300 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm transition-all duration-200 hover:shadow-md"
>
<option value="">Todos los estados</option>
<option value="1">En Espera</option>
<option value="2">Procesando</option>
<option value="3">Finalizado</option>
<option value="4">Error</option>
</select>
</div>
<div className="space-y-2 sm:col-span-2 lg:col-span-1">
<label className="text-sm font-semibold text-gray-700 flex items-center gap-2">
<div className="w-2 h-2 bg-orange-500 rounded-full"></div>
Servicio
</label>
<select
value={servicioFilter}
onChange={e => setServicioFilter(e.target.value)}
className="w-full border border-gray-300 rounded-xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm transition-all duration-200 hover:shadow-md"
>
<option value="">Todos los servicios</option>
<option value="1">Estado de pedimento</option>
<option value="2">Listado de pedimentos</option>
<option value="3">Pedimento Completo</option>
<option value="4">Pedimento Partidas</option>
<option value="5">Pedimento Remesas</option>
<option value="6">Acuse</option>
<option value="7">EDocument</option>
<option value="8">Cove</option>
<option value="9">Acuse Cove</option>
</select>
</div>
</div>
</div>
{/* Estados de carga y error mejorados */}
{loading ? (
<div className="text-center text-gray-500 py-8">Cargando procesos...</div>
<div className="flex flex-col items-center justify-center py-12">
<div className="relative">
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600"></div>
<div className="absolute inset-0 bg-blue-500/10 rounded-full blur-xl animate-pulse"></div>
</div>
<p className="mt-4 text-gray-600 font-medium">Cargando procesos...</p>
</div>
) : error ? (
<div className="text-center text-danger-600 py-8">{error}</div>
<div className="bg-red-50 border border-red-200 rounded-2xl p-6 text-center">
<div className="bg-red-100 rounded-full p-3 w-12 h-12 mx-auto mb-4 flex items-center justify-center">
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-red-800 mb-2">Error al cargar</h3>
<p className="text-red-600">{error}</p>
</div>
) : (
<div className="overflow-x-auto">
<div style={{ minHeight: 'calc(6 * 56px)', maxHeight: 'calc(6 * 56px)', overflowY: 'auto', position: 'relative' }}>
<table className="min-w-full divide-y divide-gray-200 rounded-lg overflow-hidden sticky text-xs">
<thead className="bg-gradient-to-r from-gray-50 sticky top-0 z-20">
<>
{/* Vista de tabla para pantallas grandes */}
<div className="hidden lg:block overflow-x-auto bg-white rounded-2xl border border-gray-200 shadow-sm">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gradient-to-r from-gray-50 to-slate-50">
<tr>
<th className="px-2 py-2 text-center font-bold text-blue-700 uppercase tracking-wider border-b border-gray-200 whitespace-nowrap cursor-pointer select-none"
<th className="px-4 py-4 text-center text-xs font-bold text-gray-600 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 transition-colors duration-200 rounded-tl-2xl"
onClick={() => {
setSortField('id');
setSortOrder(sortField === 'id' && sortOrder === 'asc' ? 'desc' : 'asc');
}}
>
ID {sortField === 'id' && (sortOrder === 'asc' ? '▲' : '▼')}
<div className="flex items-center justify-center gap-1">
ID {sortField === 'id' && (sortOrder === 'asc' ? '▲' : '▼')}
</div>
</th>
<th className="px-2 py-2 text-left font-bold uppercase tracking-wider border-b border-gray-200 whitespace-nowrap cursor-pointer select-none"
<th className="px-4 py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 transition-colors duration-200"
onClick={() => {
setSortField('organizacion_name');
setSortOrder(sortField === 'organizacion_name' && sortOrder === 'asc' ? 'desc' : 'asc');
}}
>
Organización {sortField === 'organizacion_name' && (sortOrder === 'asc' ? '▲' : '▼')}
<div className="flex items-center gap-1">
Organización {sortField === 'organizacion_name' && (sortOrder === 'asc' ? '▲' : '▼')}
</div>
</th>
<th className="px-2 py-2 text-left font-bold uppercase tracking-wider border-b border-gray-200 whitespace-nowrap cursor-pointer select-none"
<th className="px-4 py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 transition-colors duration-200"
onClick={() => {
setSortField('estado');
setSortOrder(sortField === 'estado' && sortOrder === 'asc' ? 'desc' : 'asc');
}}
>
Estado {sortField === 'estado' && (sortOrder === 'asc' ? '▲' : '▼')}
<div className="flex items-center gap-1">
Estado {sortField === 'estado' && (sortOrder === 'asc' ? '▲' : '▼')}
</div>
</th>
<th className="px-2 py-2 text-left font-bold uppercase tracking-wider border-b border-gray-200 whitespace-nowrap cursor-pointer select-none"
<th className="px-4 py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 transition-colors duration-200"
onClick={() => {
setSortField('pedimento');
setSortOrder(sortField === 'pedimento' && sortOrder === 'asc' ? 'desc' : 'asc');
}}
>
Pedimento {sortField === 'pedimento' && (sortOrder === 'asc' ? '▲' : '▼')}
<div className="flex items-center gap-1">
Pedimento {sortField === 'pedimento' && (sortOrder === 'asc' ? '▲' : '▼')}
</div>
</th>
<th className="px-2 py-2 text-left font-bold uppercase tracking-wider border-b border-gray-200 whitespace-nowrap cursor-pointer select-none"
<th className="px-4 py-4 text-left text-xs font-bold text-gray-600 uppercase tracking-wider cursor-pointer select-none hover:bg-gray-100 transition-colors duration-200"
onClick={() => {
setSortField('servicio');
setSortOrder(sortField === 'servicio' && sortOrder === 'asc' ? 'desc' : 'asc');
}}
>
Servicio {sortField === 'servicio' && (sortOrder === 'asc' ? '▲' : '▼')}
<div className="flex items-center gap-1">
Servicio {sortField === 'servicio' && (sortOrder === 'asc' ? '▲' : '▼')}
</div>
</th>
<th className="px-2 py-2 text-center font-bold text-blue-700 uppercase tracking-wider border-b border-gray-200 whitespace-nowrap">
<th className="px-4 py-4 text-center text-xs font-bold text-gray-600 uppercase tracking-wider rounded-tr-2xl">
Acciones
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100" style={{ position: 'relative', minHeight: 'calc(12 * 40px)' }}>
{procesos.length === 0 ? (
<tr>
<td colSpan={8} className="text-center py-8 text-gray-500">No hay registros</td>
</tr>
) : (
procesos.map((proc) => (
<tr key={proc.id} className="transition-all duration-200 hover:bg-blue-100 hover:shadow-lg">
<td className="px-2 py-2 text-center align-middle whitespace-nowrap">{proc.id}</td>
<td className="px-2 py-2 whitespace-nowrap align-middle">{proc.organizacion_name || '-'}</td>
<td className="px-2 py-2 whitespace-nowrap align-middle">{
proc.estado === 1 ? 'En Espera'
: proc.estado === 2 ? 'Procesando'
: proc.estado === 3 ? 'Finalizado'
: proc.estado === 4 ? 'Error'
: String(proc.estado)
}</td>
<td className="px-2 py-2 whitespace-nowrap align-middle">{
typeof proc.pedimento === 'object' && proc.pedimento !== null
? proc.pedimento.pedimento || JSON.stringify(proc.pedimento)
: proc.pedimento
}</td>
<td className="px-2 py-2 whitespace-nowrap align-middle">{
proc.servicio === 1 ? 'Estado de pedimento'
: proc.servicio === 2 ? 'Listado de pedimentos'
: proc.servicio === 3 ? 'Pedimento Completo'
: proc.servicio === 4 ? 'Pedimento Partidas'
: proc.servicio === 5 ? 'Pedimento Remesas'
: proc.servicio === 6 ? 'Acuse'
: proc.servicio === 7 ? 'EDocument'
: proc.servicio === 8 ? 'Cove'
: proc.servicio === 9 ? 'Acuse Cove'
: String(proc.servicio)
}</td>
<td className="px-2 py-2 text-center align-middle whitespace-nowrap">
<div className="relative inline-block text-left" id={`dropdown-acciones-${proc.id}`}>
<button
className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-3 py-1 bg-white text-xs font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200 transform hover:scale-105"
type="button"
onClick={() => setOpenDropdownId(openDropdownId === proc.id ? null : proc.id)}
>
Acciones
<svg className="ml-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" /></svg>
</button>
{openDropdownId === proc.id && (
<div className="absolute right-0 mt-2 w-32 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-20">
<div className="py-1">
<button
className="block w-full text-left px-4 py-2 text-xs text-blue-700 hover:bg-blue-100 disabled:opacity-60"
onClick={() => handleEjecutarServicio(proc)}
disabled={
executingId === proc.id ||
proc.estado === 2 || // Procesando
proc.estado === 3 || // Finalizado
proc.estado === 4 // Error
}
>
{executingId === proc.id ? 'Ejecutando...' : 'Ejecutar Servicio'}
</button>
<button
className={`block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-blue-100${(proc.estado === 2 || proc.estado === 4) ? '' : ' opacity-50 cursor-not-allowed'}`}
disabled={!(proc.estado === 2 || proc.estado === 4)}
>
Pasar a espera
</button>
<button className="block w-full text-left px-4 py-2 text-xs text-gray-700 hover:bg-blue-100">Editar</button>
</div>
</div>
)}
<tbody className="bg-white divide-y divide-gray-100">
{procesos.length === 0 ? (
<tr>
<td colSpan={6} className="text-center py-12">
<div className="flex flex-col items-center">
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 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>
</div>
<p className="text-gray-500 font-medium">No hay procesos disponibles</p>
<p className="text-gray-400 text-sm mt-1">Intenta ajustar los filtros de búsqueda</p>
</div>
</td>
</tr>
))
)}
</tbody>
) : (
procesos.map((proc) => (
<tr key={proc.id} className="transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50 hover:to-indigo-50 hover:shadow-lg">
<td className="px-4 py-4 text-center align-middle whitespace-nowrap">
<span className="bg-gray-100 text-gray-800 px-2 py-1 rounded-lg text-sm font-semibold">{proc.id}</span>
</td>
<td className="px-4 py-4 whitespace-nowrap align-middle text-sm font-medium text-gray-900">{proc.organizacion_name || '-'}</td>
<td className="px-4 py-4 whitespace-nowrap align-middle">
{(() => {
const estado = proc.estado === 1 ? { text: 'En Espera', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' }
: proc.estado === 2 ? { text: 'Procesando', color: 'bg-blue-100 text-blue-800 border-blue-200' }
: proc.estado === 3 ? { text: 'Finalizado', color: 'bg-green-100 text-green-800 border-green-200' }
: proc.estado === 4 ? { text: 'Error', color: 'bg-red-100 text-red-800 border-red-200' }
: { text: String(proc.estado), color: 'bg-gray-100 text-gray-800 border-gray-200' };
return (
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold border ${estado.color}`}>
{estado.text}
</span>
);
})()}
</td>
<td className="px-4 py-4 whitespace-nowrap align-middle text-sm text-gray-900 font-mono">
{typeof proc.pedimento === 'object' && proc.pedimento !== null
? proc.pedimento.pedimento || JSON.stringify(proc.pedimento)
: proc.pedimento}
</td>
<td className="px-4 py-4 whitespace-nowrap align-middle text-sm text-gray-700">
{proc.servicio === 1 ? 'Estado de pedimento'
: proc.servicio === 2 ? 'Listado de pedimentos'
: proc.servicio === 3 ? 'Pedimento Completo'
: proc.servicio === 4 ? 'Pedimento Partidas'
: proc.servicio === 5 ? 'Pedimento Remesas'
: proc.servicio === 6 ? 'Acuse'
: proc.servicio === 7 ? 'EDocument'
: proc.servicio === 8 ? 'Cove'
: proc.servicio === 9 ? 'Acuse Cove'
: String(proc.servicio)}
</td>
<td className="px-4 py-4 text-center align-middle whitespace-nowrap">
<div className="relative inline-block text-left z-30" id={`dropdown-acciones-${proc.id}`}>
<button
className="inline-flex justify-center items-center rounded-xl border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200 transform hover:scale-105 active:bg-gray-100"
type="button"
onClick={() => setOpenDropdownId(openDropdownId === proc.id ? null : proc.id)}
>
Acciones
<svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
{openDropdownId === proc.id && (
<div className="absolute right-0 mt-2 w-48 rounded-xl shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-[9999] border border-gray-200">
<div className="py-2">
<button
className="flex items-center w-full text-left px-4 py-3 text-sm text-blue-700 hover:bg-blue-50 disabled:opacity-60 disabled:cursor-not-allowed transition-colors duration-200"
onClick={() => {
handleEjecutarServicio(proc);
setOpenDropdownId(null); // Cerrar dropdown después de ejecutar
}}
disabled={
executingId === proc.id ||
proc.estado === 2 || // Procesando
proc.estado === 3 || // Finalizado
proc.estado === 4 // Error
}
>
<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="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2 5H7a2 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>
{executingId === proc.id ? 'Ejecutando...' : 'Ejecutar Servicio'}
</button>
<button
className={`flex items-center w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors duration-200 ${(proc.estado === 2 || proc.estado === 4) ? '' : ' opacity-50 cursor-not-allowed'}`}
disabled={!(proc.estado === 2 || proc.estado === 4)}
onClick={() => {
setOpenDropdownId(null); // Cerrar dropdown
// Aquí iría la lógica para pasar a espera
}}
>
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Pasar a espera
</button>
<button
className="flex items-center w-full text-left px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors duration-200"
onClick={() => {
setOpenDropdownId(null); // Cerrar dropdown
// Aquí iría la lógica para editar
}}
>
<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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Editar
</button>
</div>
</div>
)}
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
{/* Paginación igual a Documents.jsx */}
{/* Vista de tarjetas para pantallas pequeñas y medianas */}
<div className="lg:hidden space-y-4">
{procesos.length === 0 ? (
<div className="bg-gray-50 rounded-2xl p-8 text-center">
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 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>
</div>
<p className="text-gray-500 font-medium">No hay procesos disponibles</p>
<p className="text-gray-400 text-sm mt-1">Intenta ajustar los filtros de búsqueda</p>
</div>
) : (
procesos.map((proc) => (
<div key={proc.id} className={`bg-white rounded-2xl shadow-lg border border-gray-200 p-4 hover:shadow-xl transition-all duration-300 relative ${openDropdownId === proc.id ? 'z-[100]' : ''}`}>
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-xl p-2 flex-shrink-0">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 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>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-lg font-semibold text-gray-900">Proceso #{proc.id}</h3>
<p className="text-sm text-gray-500">{proc.organizacion_name || 'Sin organización'}</p>
</div>
</div>
{(() => {
const estado = proc.estado === 1 ? { text: 'En Espera', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' }
: proc.estado === 2 ? { text: 'Procesando', color: 'bg-blue-100 text-blue-800 border-blue-200' }
: proc.estado === 3 ? { text: 'Finalizado', color: 'bg-green-100 text-green-800 border-green-200' }
: proc.estado === 4 ? { text: 'Error', color: 'bg-red-100 text-red-800 border-red-200' }
: { text: String(proc.estado), color: 'bg-gray-100 text-gray-800 border-gray-200' };
return (
<span className={`inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold border ${estado.color}`}>
{estado.text}
</span>
);
})()}
</div>
<div className="space-y-3 mb-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600">Pedimento:</span>
<span className="text-sm font-mono text-gray-900 bg-gray-100 px-2 py-1 rounded">
{typeof proc.pedimento === 'object' && proc.pedimento !== null
? proc.pedimento.pedimento || JSON.stringify(proc.pedimento)
: proc.pedimento}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600">Servicio:</span>
<span className="text-sm text-gray-900 text-right max-w-[60%]">
{proc.servicio === 1 ? 'Estado de pedimento'
: proc.servicio === 2 ? 'Listado de pedimentos'
: proc.servicio === 3 ? 'Pedimento Completo'
: proc.servicio === 4 ? 'Pedimento Partidas'
: proc.servicio === 5 ? 'Pedimento Remesas'
: proc.servicio === 6 ? 'Acuse'
: proc.servicio === 7 ? 'EDocument'
: proc.servicio === 8 ? 'Cove'
: proc.servicio === 9 ? 'Acuse Cove'
: String(proc.servicio)}
</span>
</div>
</div>
<div className="relative z-30" id={`dropdown-acciones-${proc.id}`}>
<button
className="w-full inline-flex justify-center items-center rounded-xl border border-gray-300 shadow-sm px-4 py-3 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all duration-200 active:bg-gray-100"
type="button"
onClick={() => setOpenDropdownId(openDropdownId === proc.id ? null : proc.id)}
>
Acciones
<svg className="ml-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
{openDropdownId === proc.id && (
<div className="absolute left-0 right-0 mt-2 rounded-xl shadow-2xl bg-white ring-2 ring-gray-300 z-[9999] border border-gray-200 overflow-hidden"
style={{ boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)' }}>
<div className="py-1">
<button
className="flex items-center w-full text-left px-4 py-4 text-sm text-blue-700 hover:bg-blue-50 disabled:opacity-60 disabled:cursor-not-allowed transition-colors duration-200 border-b border-gray-100"
onClick={() => {
handleEjecutarServicio(proc);
setOpenDropdownId(null); // Cerrar dropdown después de ejecutar
}}
disabled={
executingId === proc.id ||
proc.estado === 2 || // Procesando
proc.estado === 3 || // Finalizado
proc.estado === 4 // Error
}
>
<svg className="w-4 h-4 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2 5H7a2 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 className="font-medium">
{executingId === proc.id ? 'Ejecutando...' : 'Ejecutar Servicio'}
</span>
</button>
<button
className={`flex items-center w-full text-left px-4 py-4 text-sm text-gray-700 hover:bg-gray-50 transition-colors duration-200 border-b border-gray-100 ${(proc.estado === 2 || proc.estado === 4) ? '' : ' opacity-50 cursor-not-allowed'}`}
disabled={!(proc.estado === 2 || proc.estado === 4)}
onClick={() => {
setOpenDropdownId(null); // Cerrar dropdown
// Aquí iría la lógica para pasar a espera
}}
>
<svg className="w-4 h-4 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="font-medium">Pasar a espera</span>
</button>
<button
className="flex items-center w-full text-left px-4 py-4 text-sm text-gray-700 hover:bg-gray-50 transition-colors duration-200"
onClick={() => {
setOpenDropdownId(null); // Cerrar dropdown
// Aquí iría la lógica para editar
}}
>
<svg className="w-4 h-4 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
<span className="font-medium">Editar</span>
</button>
</div>
</div>
)}
</div>
</div>
))
)}
</div>
{/* Paginación compartida mejorada */}
{count > 0 && (
<div className="bg-white px-4 py-3 flex flex-col sm:flex-row items-center justify-between border-t border-gray-200">
<div className="bg-gradient-to-r from-gray-50 to-slate-50 px-4 sm:px-6 py-4 mt-6 rounded-2xl border border-gray-200 flex flex-col sm:flex-row items-center justify-between gap-4">
{(() => {
const totalPages = Math.max(1, Math.ceil(count / itemsPerPage));
const maxPagesToShow = 5;
@@ -385,26 +657,26 @@ export default function Procesos() {
pageNumbers.push(i);
}
return (
<div className="flex flex-col sm:flex-row sm:items-center w-full gap-2 sm:gap-4 mt-2 sm:mt-0">
<div className="flex items-center gap-2">
<label htmlFor="itemsPerPage" className="text-xs text-gray-600 font-medium">Registros por página:</label>
<>
<div className="flex items-center gap-3">
<label htmlFor="itemsPerPage" className="text-sm text-gray-600 font-medium">Registros por página:</label>
<select
id="itemsPerPage"
value={itemsPerPage}
onChange={e => { setItemsPerPage(Number(e.target.value)); setPage(1); }}
className="border border-gray-300 rounded px-2 py-1 text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white"
className="border border-gray-300 rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm"
>
{[5, 8, 12, 20, 50, 100].map(size => (
<option key={size} value={size}>{size}</option>
))}
</select>
</div>
<div className="flex items-center gap-1 flex-wrap">
<div className="flex items-center gap-2">
<button
type="button"
onClick={e => { e.preventDefault(); setPage(1); }}
disabled={page === 1}
className={`px-2 py-1 rounded border text-xs font-semibold transition-colors duration-150 ${page === 1 ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900'}`}
className={`px-3 py-2 rounded-xl border text-sm font-semibold transition-all duration-200 ${page === 1 ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900 hover:shadow-md'}`}
>
«
</button>
@@ -412,7 +684,7 @@ export default function Procesos() {
type="button"
onClick={e => { e.preventDefault(); setPage(p => Math.max(1, p - 1)); }}
disabled={page === 1}
className={`px-2 py-1 rounded border text-xs font-semibold transition-colors duration-150 ${page === 1 ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900'}`}
className={`px-3 py-2 rounded-xl border text-sm font-semibold transition-all duration-200 ${page === 1 ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900 hover:shadow-md'}`}
>
</button>
@@ -421,7 +693,7 @@ export default function Procesos() {
type="button"
key={num}
onClick={e => { e.preventDefault(); setPage(num); }}
className={`px-2 py-1 rounded border text-xs font-semibold transition-colors duration-150 ${num === page ? 'bg-blue-600 text-white border-blue-700 cursor-default' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900'}`}
className={`px-3 py-2 rounded-xl border text-sm font-semibold transition-all duration-200 ${num === page ? 'bg-blue-600 text-white border-blue-700 cursor-default shadow-lg' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900 hover:shadow-md'}`}
disabled={num === page}
>
{num}
@@ -431,7 +703,7 @@ export default function Procesos() {
type="button"
onClick={e => { e.preventDefault(); setPage(p => p + 1); }}
disabled={page >= totalPages}
className={`px-2 py-1 rounded border text-xs font-semibold transition-colors duration-150 ${(page >= totalPages) ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900'}`}
className={`px-3 py-2 rounded-xl border text-sm font-semibold transition-all duration-200 ${(page >= totalPages) ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900 hover:shadow-md'}`}
>
</button>
@@ -439,18 +711,20 @@ export default function Procesos() {
type="button"
onClick={e => { e.preventDefault(); setPage(totalPages); }}
disabled={page >= totalPages}
className={`px-2 py-1 rounded border text-xs font-semibold transition-colors duration-150 ${(page >= totalPages) ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900'}`}
className={`px-3 py-2 rounded-xl border text-sm font-semibold transition-all duration-200 ${(page >= totalPages) ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-blue-700 border-blue-200 hover:bg-blue-50 hover:text-blue-900 hover:shadow-md'}`}
>
»
</button>
<span className="ml-3 text-xs text-gray-500">Página <span className="font-bold">{page}</span> de <span className="font-bold">{totalPages}</span></span>
</div>
</div>
<span className="text-sm text-gray-600">
Página <span className="font-bold text-gray-800">{page}</span> de <span className="font-bold text-gray-800">{totalPages}</span>
</span>
</>
);
})()}
</div>
)}
</div>
</>
)}
</div>
</div>