Se mejoro estetica y estandarizaron estilos
This commit is contained in:
@@ -16,6 +16,7 @@ export default function Vucem() {
|
||||
usuario: '',
|
||||
password: '',
|
||||
patente: '',
|
||||
efirma: '',
|
||||
key: null,
|
||||
cer: null,
|
||||
is_importador: false,
|
||||
@@ -84,36 +85,63 @@ export default function Vucem() {
|
||||
// Reset page si cambia el filtro
|
||||
useEffect(() => { setPage(1); }, [filterUsuario, filterPatente]);
|
||||
|
||||
// Cuando se selecciona un registro para editar, poblar el formulario con sus datos
|
||||
useEffect(() => {
|
||||
if (editVucem) {
|
||||
setForm({
|
||||
usuario: editVucem.usuario || '',
|
||||
password: '', // No se rellena por seguridad
|
||||
patente: editVucem.patente || '',
|
||||
efirma: editVucem.efirma || '',
|
||||
key: null, // No se rellena, solo se sube si el usuario selecciona
|
||||
cer: null, // No se rellena, solo se sube si el usuario selecciona
|
||||
is_importador: !!editVucem.is_importador,
|
||||
acusecove: !!editVucem.acusecove,
|
||||
acuseedocument: !!editVucem.acuseedocument,
|
||||
is_active: !!editVucem.is_active,
|
||||
});
|
||||
}
|
||||
}, [editVucem]);
|
||||
|
||||
// Table y header estilo Users.jsx
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Header Mejorado igual que Users.jsx */}
|
||||
<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">
|
||||
<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">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-4xl font-extrabold text-blue-900 tracking-tight mb-1 flex items-center gap-2">
|
||||
VUCEM
|
||||
<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">{vucemList.length}</span>
|
||||
<span className="inline-block bg-lime-200 text-lime-800 text-xs font-semibold px-2 py-0.5 rounded-full ml-2 animate-fade-in" title="Activos">
|
||||
Activos: {vucemList.filter(v => v.is_active).length}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-lg text-blue-700/80 font-medium">Gestiona y supervisa los accesos y certificados VUCEM registrados en el sistema.</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>
|
||||
{/* Header modernizado con gradientes azules */}
|
||||
<div className="mb-8 relative overflow-hidden rounded-2xl shadow-xl bg-gradient-to-br from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/20 via-transparent to-blue-900/30"></div>
|
||||
<div className="absolute -top-24 -right-24 w-48 h-48 bg-white/10 rounded-full blur-xl"></div>
|
||||
<div className="absolute -bottom-12 -left-12 w-32 h-32 bg-white/5 rounded-full blur-lg"></div>
|
||||
|
||||
<div className="relative flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
|
||||
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-2xl 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 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-2">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-white tracking-tight">
|
||||
Credenciales VU
|
||||
</h1>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-white/20 text-white backdrop-blur-sm animate-fade-in">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1V8zm8 0a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1h-6a1 1 0 01-1-1V8z" clipRule="evenodd" />
|
||||
</svg>
|
||||
{vucemList.length} Total
|
||||
</span>
|
||||
<span className="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-green-500/20 text-green-100 backdrop-blur-sm animate-fade-in" style={{ animationDelay: '0.1s' }}>
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
{vucemList.filter(v => v.is_active).length} Activos
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-blue-100 text-sm sm:text-base font-medium">
|
||||
Gestiona certificados digitales, credenciales y configuraciones VUCEM del sistema aduanero
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>{`
|
||||
@@ -131,190 +159,453 @@ export default function Vucem() {
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.7s ease;
|
||||
}
|
||||
@keyframes fade-in-up {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.fade-in-up-users {
|
||||
animation: fade-in-up 0.4s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
`}</style>
|
||||
{/* Filtros y botón Crear VUCEM igual que Users.jsx */}
|
||||
<div className="bg-white shadow rounded-lg mb-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div className="flex-1 min-w-0 max-w-md">
|
||||
<div className="relative bg-gray-50 rounded-lg border border-gray-200 shadow-sm">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-4.35-4.35M17 11A6 6 0 105 11a6 6 0 0012 0z" />
|
||||
</svg>
|
||||
{/* Controles de búsqueda y filtros mejorados */}
|
||||
<div className="bg-white shadow-lg rounded-xl border border-gray-100 mb-6 overflow-hidden">
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 px-4 sm:px-6 py-4 border-b border-gray-200">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||
{/* Búsqueda principal */}
|
||||
<div className="flex-1 max-w-md">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="block w-full pl-10 pr-10 py-3 border border-gray-300 rounded-lg bg-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-all duration-200 shadow-sm"
|
||||
placeholder="Buscar por usuario o patente..."
|
||||
value={filterUsuario}
|
||||
onChange={e => setFilterUsuario(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{filterUsuario && (
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||
<button
|
||||
onClick={() => setFilterUsuario('')}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-full hover:bg-gray-100"
|
||||
title="Limpiar búsqueda"
|
||||
>
|
||||
<svg className="h-4 w-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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:bg-white block w-full pl-10 pr-10 py-3 sm:text-sm border-0 bg-transparent rounded-lg placeholder-gray-500 transition-all duration-200"
|
||||
placeholder="Buscar por usuario o patente..."
|
||||
value={filterUsuario}
|
||||
onChange={e => setFilterUsuario(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{filterUsuario && (
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||
<button
|
||||
onClick={() => setFilterUsuario('')}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-full hover:bg-gray-200"
|
||||
title="Limpiar búsqueda"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</div>
|
||||
|
||||
{/* Información y botón crear */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3">
|
||||
{filteredList.length !== vucemList.length && (
|
||||
<div className="text-sm text-blue-700">
|
||||
<span className="inline-flex items-center">
|
||||
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 4a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1V8zm8 0a1 1 0 011-1h6a1 1 0 011 1v2a1 1 0 01-1 1h-6a1 1 0 01-1-1V8z" />
|
||||
</svg>
|
||||
</button>
|
||||
Mostrando {filteredList.length} de {vucemList.length} registros
|
||||
{filterUsuario && <span className="ml-1">para "{filterUsuario}"</span>}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2.5 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
<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 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Agregar Credencial
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 sm:mt-0 sm:ml-4 flex gap-2">
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-700 hover:to-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
<svg className="-ml-1 mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Crear VUCEM
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Tabla igual que Users.jsx */}
|
||||
<div className="overflow-hidden">
|
||||
{/* Vista responsiva: tabla para desktop, cards para mobile */}
|
||||
|
||||
{/* Tabla para pantallas grandes */}
|
||||
<div className="hidden lg:block bg-white shadow-lg rounded-xl overflow-hidden border border-gray-100">
|
||||
<div style={{ minHeight: 'calc(8 * 56px)', maxHeight: 'calc(8 * 56px)', overflowY: 'auto', position: 'relative' }}>
|
||||
<table className="min-w-full divide-y divide-gray-200 rounded-lg overflow-hidden">
|
||||
<thead className="bg-gray-50 sticky top-0 z-20">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gradient-to-r from-gray-50 to-blue-50 sticky top-0 z-20">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Usuario</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Patente</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Key</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cer</th>
|
||||
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Activo</th>
|
||||
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-blue-700 uppercase tracking-wider">Usuario</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-blue-700 uppercase tracking-wider">Patente</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-blue-700 uppercase tracking-wider">Archivos</th>
|
||||
<th scope="col" className="px-6 py-3 text-center text-xs font-bold text-blue-700 uppercase tracking-wider">Estado</th>
|
||||
<th scope="col" className="px-6 py-3 text-center text-xs font-bold text-blue-700 uppercase tracking-wider">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200" style={{ position: 'relative', minHeight: 'calc(8 * 56px)' }}>
|
||||
{loading ? (
|
||||
<tr>
|
||||
<td colSpan={6} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<div className="flex items-center justify-center h-full w-full absolute left-0 top-0" style={{ minHeight: 'calc(8 * 56px)', background: 'rgba(255,255,255,0.7)', zIndex: 10 }}>
|
||||
<span className="text-gray-500 text-lg">Cargando VUCEM...</span>
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<span className="text-gray-500 text-lg">Cargando registros VUCEM...</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : error ? (
|
||||
<tr>
|
||||
<td colSpan={6} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<div className="flex items-center justify-center h-full w-full absolute left-0 top-0" style={{ minHeight: 'calc(8 * 56px)', background: 'rgba(255,255,255,0.7)', zIndex: 10 }}>
|
||||
<span className="text-danger-600 text-lg">Error: {error}</span>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto h-12 w-12 bg-red-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg className="h-6 w-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>
|
||||
<span className="text-red-600 text-lg">Error: {error}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : paginatedList.length > 0 ? (
|
||||
paginatedList.map((vucem, idx) => (
|
||||
<tr
|
||||
key={vucem.id}
|
||||
className={
|
||||
`transition-all duration-300 hover:scale-[1.025] hover:shadow-lg hover:bg-indigo-50 fade-in-up-users` +
|
||||
(idx % 2 === 0 ? ' bg-white' : ' bg-gray-50')
|
||||
}
|
||||
style={{ animationDelay: `${0.05 * idx}s` }}
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{vucem.usuario}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{vucem.patente}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
{vucem.key ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Archivo cargado
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-400">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin archivo
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
{vucem.cer ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Archivo cargado
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-400">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin archivo
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-center">
|
||||
{vucem.is_active ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Activo
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Inactivo
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-center">
|
||||
<button onClick={() => { setEditVucem(vucem); setShowEditModal(true); }} className="text-indigo-600 hover:underline font-semibold mr-3">Editar</button>
|
||||
<button onClick={() => { setDeleteVucem(vucem); setShowDeleteModal(true); }} className="text-red-600 hover:underline font-semibold">Eliminar</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
<>
|
||||
{paginatedList.map((vucem, idx) => (
|
||||
<tr
|
||||
key={vucem.id}
|
||||
className={
|
||||
`transition-all duration-300 hover:scale-[1.015] hover:shadow-md hover:bg-blue-50 fade-in-up-users` +
|
||||
(idx % 2 === 0 ? ' bg-white' : ' bg-gray-50')
|
||||
}
|
||||
style={{ animationDelay: `${0.05 * idx}s` }}
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 h-10 w-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<svg className="h-5 w-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<div className="text-sm font-medium text-gray-900">{vucem.usuario}</div>
|
||||
<div className="text-sm text-gray-500">ID: {vucem.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">{vucem.patente}</div>
|
||||
<div className="text-sm text-gray-500">e.firma: {vucem.efirma}</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex flex-col gap-1">
|
||||
{vucem.key ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Key
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin Key
|
||||
</span>
|
||||
)}
|
||||
{vucem.cer ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Cer
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin Cer
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-center">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{vucem.is_active ? (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Activo
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Inactivo
|
||||
</span>
|
||||
)}
|
||||
{vucem.is_importador && (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
||||
<svg className="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M9.504 1.132a1 1 0 01.992 0l1.75 1a1 1 0 11-.992 1.736L10 3.152l-1.254.716a1 1 0 11-.992-1.736l1.75-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Importador
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-center">
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
onClick={() => { setEditVucem(vucem); setShowEditModal(true); }}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-blue-300 shadow-sm text-xs font-medium rounded-lg text-blue-700 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1.5" 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>
|
||||
<button
|
||||
onClick={() => { setDeleteVucem(vucem); setShowDeleteModal(true); }}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-red-300 shadow-sm text-xs font-medium rounded-lg text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{/* Rellenar con filas vacías si hay menos de 8 */}
|
||||
{paginatedList.length < 8 && !loading && !error && Array.from({ length: 8 - paginatedList.length }).map((_, idx) => (
|
||||
<tr key={`empty-${idx}`}>
|
||||
<td className="px-6 py-4 whitespace-nowrap" colSpan={5}> </td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className="text-center py-8 text-gray-500">No hay registros</td>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<div className="flex flex-col items-center justify-center h-full w-full absolute left-0 top-0" style={{ minHeight: 'calc(8 * 56px)', background: 'rgba(255,255,255,0.7)', zIndex: 10 }}>
|
||||
<div className="mx-auto h-16 w-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg className="h-8 w-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No hay registros VUCEM</h3>
|
||||
<p className="text-gray-500">Comienza creando tu primer registro VUCEM.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/* Paginación igual que Users.jsx */}
|
||||
{filteredList.length > 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="flex items-center mb-4 sm:mb-0">
|
||||
<span className="text-sm text-gray-700">
|
||||
Mostrando <span className="font-semibold">{filteredList.length}</span> de <span className="font-semibold">{vucemList.length}</span> registros
|
||||
</span>
|
||||
{/* Cards para pantallas pequeñas */}
|
||||
<div className="lg:hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<span className="text-gray-500 text-lg">Cargando registros VUCEM...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
className={`px-3 py-1 rounded-lg border text-base font-semibold transition-colors ${page === 1 ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-gray-700 border-gray-300 hover:bg-indigo-50 hover:text-indigo-700'}`}
|
||||
>
|
||||
Anterior
|
||||
</button>
|
||||
<span className="mx-2 text-base font-medium text-gray-700">Página {page} de {totalPages}</span>
|
||||
<button
|
||||
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={page === totalPages}
|
||||
className={`px-3 py-1 rounded-lg border text-base font-semibold transition-colors ${page === totalPages ? 'bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed' : 'bg-white text-gray-700 border-gray-300 hover:bg-indigo-50 hover:text-indigo-700'}`}
|
||||
>
|
||||
Siguiente
|
||||
</button>
|
||||
) : error ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto h-12 w-12 bg-red-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg className="h-6 w-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>
|
||||
<span className="text-red-600 text-lg">Error: {error}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : paginatedList.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{paginatedList.map((vucem, idx) => (
|
||||
<div key={vucem.id} className="bg-white border border-gray-200 rounded-xl p-4 shadow-sm hover:shadow-md transition-all duration-300 hover:scale-[1.02] fade-in-up-users" style={{ animationDelay: `${0.05 * idx}s` }}>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center space-x-3 flex-1">
|
||||
<div className="flex-shrink-0 bg-blue-100 rounded-full p-2">
|
||||
<svg className="h-5 w-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-gray-900 truncate">{vucem.usuario}</h3>
|
||||
<p className="text-xs text-gray-500 truncate">Patente: {vucem.patente}</p>
|
||||
<div className="text-xs text-gray-500 mt-1">ID: {vucem.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => { setEditVucem(vucem); setShowEditModal(true); }}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-blue-300 shadow-sm text-xs font-medium rounded-lg text-blue-700 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
>
|
||||
<svg className="w-3 h-3 mr-1" 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>
|
||||
<button
|
||||
onClick={() => { setDeleteVucem(vucem); setShowDeleteModal(true); }}
|
||||
className="inline-flex items-center px-3 py-1.5 border border-red-300 shadow-sm text-xs font-medium rounded-lg text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-all duration-200"
|
||||
>
|
||||
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-xs">
|
||||
<div>
|
||||
<span className="font-medium text-gray-500">e.firma:</span>
|
||||
<p className="text-gray-900 mt-1 truncate">{vucem.efirma}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-500">Estado:</span>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{vucem.is_active ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Activo
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Inactivo
|
||||
</span>
|
||||
)}
|
||||
{vucem.is_importador && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M9.504 1.132a1 1 0 01.992 0l1.75 1a1 1 0 11-.992 1.736L10 3.152l-1.254.716a1 1 0 11-.992-1.736l1.75-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Importador
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span className="font-medium text-gray-500">Archivos:</span>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{vucem.key ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Key
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin Key
|
||||
</span>
|
||||
)}
|
||||
{vucem.cer ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 7.293a1 1 0 00-1.414 0L9 13.586l-2.293-2.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l7-7a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Cer
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500">
|
||||
<svg className="w-2 h-2 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Sin Cer
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<div className="mx-auto h-16 w-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<svg className="h-8 w-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No hay registros VUCEM</h3>
|
||||
<p className="text-gray-500 text-center">Comienza creando tu primer registro VUCEM.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Paginación mejorada */}
|
||||
{totalPages > 1 && (
|
||||
<div className="mt-8 border-t border-gray-200 pt-6">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0">
|
||||
<div className="text-sm text-gray-700">
|
||||
Mostrando <span className="font-medium">{((page - 1) * pageSize) + 1}</span> a{' '}
|
||||
<span className="font-medium">
|
||||
{Math.min(page * pageSize, filteredList.length)}
|
||||
</span> de{' '}
|
||||
<span className="font-medium">{filteredList.length}</span> registros
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => setPage(p => Math.max(1, p - 1))}
|
||||
disabled={page === 1}
|
||||
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
Anterior
|
||||
</button>
|
||||
<div className="flex space-x-1">
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map(pageNum => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setPage(pageNum)}
|
||||
className={`px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
||||
page === pageNum
|
||||
? 'bg-blue-600 text-white shadow-lg transform scale-105'
|
||||
: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 hover:transform hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={page === totalPages}
|
||||
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
|
||||
>
|
||||
Siguiente
|
||||
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal de creación */}
|
||||
{showCreateModal && (
|
||||
@@ -332,6 +623,7 @@ export default function Vucem() {
|
||||
formData.append('usuario', form.usuario);
|
||||
formData.append('password', form.password);
|
||||
formData.append('patente', form.patente);
|
||||
formData.append('efirma', form.efirma);
|
||||
if (form.key) formData.append('key', form.key);
|
||||
if (form.cer) formData.append('cer', form.cer);
|
||||
formData.append('is_importador', form.is_importador);
|
||||
@@ -365,6 +657,10 @@ export default function Vucem() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Patente *</label>
|
||||
<input name="patente" value={form.patente} onChange={handleInputChange} required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="patente" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">e.firma *</label>
|
||||
<input name="efirma" value={form.efirma} onChange={handleInputChange} required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="e.firma" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Key (.key)</label>
|
||||
<input name="key" type="file" accept=".key" onChange={handleInputChange} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm" />
|
||||
@@ -393,8 +689,8 @@ export default function Vucem() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="submit" className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors disabled:opacity-50 flex items-center">Crear</button>
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="submit" className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium 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 disabled:opacity-50 flex items-center">Crear</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -418,6 +714,7 @@ export default function Vucem() {
|
||||
formData.append('usuario', form.usuario);
|
||||
if (form.password) formData.append('password', form.password);
|
||||
formData.append('patente', form.patente);
|
||||
formData.append('efirma', form.efirma);
|
||||
if (form.key) formData.append('key', form.key);
|
||||
if (form.cer) formData.append('cer', form.cer);
|
||||
formData.append('is_importador', form.is_importador);
|
||||
@@ -451,6 +748,10 @@ export default function Vucem() {
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Patente *</label>
|
||||
<input name="patente" value={form.patente} onChange={handleInputChange} required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="patente" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">e.firma *</label>
|
||||
<input name="efirma" value={form.efirma} onChange={handleInputChange} required className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm" placeholder="e.firma" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Key (.key)</label>
|
||||
<input name="key" type="file" accept=".key" onChange={handleInputChange} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm" />
|
||||
@@ -479,8 +780,8 @@ export default function Vucem() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="submit" className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors disabled:opacity-50 flex items-center">Actualizar</button>
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="submit" className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium 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 disabled:opacity-50 flex items-center">Actualizar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -505,7 +806,7 @@ export default function Vucem() {
|
||||
<p className="text-sm text-red-600">Esta acción no se puede deshacer.</p>
|
||||
</div>
|
||||
<div className="flex justify-center space-x-3 pt-4">
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="button" onClick={closeModals} className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors disabled:opacity-50">Cancelar</button>
|
||||
<button type="button" onClick={async () => {
|
||||
if (!deleteVucem) return;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user