Se Agrego funcionalidad a vucem
This commit is contained in:
6
.env
6
.env
@@ -1,4 +1,4 @@
|
||||
VITE_DEBUG_MODE=false
|
||||
DEBUG_MODE=true
|
||||
|
||||
VITE_EFC_API_URL=http://192.168.1.195:8000/api/v1
|
||||
VITE_EFC_MICROSERVICE_URL=http://192.168.1.195:8001/api/v1
|
||||
VITE_EFC_API_URL=https://api.efc-aduanasoft.com/api/v1
|
||||
VITE_EFC_MICROSERVICE_URL=https://api.efc-aduanasoft.com/microservice/api/v1
|
||||
|
||||
@@ -26,6 +26,74 @@ export default function Vucem() {
|
||||
};
|
||||
const [form, setForm] = useState(initialForm);
|
||||
|
||||
// Estados para controlar visibilidad de contraseñas y copiar
|
||||
const [showPassword, setShowPassword] = useState({});
|
||||
const [showEfirma, setShowEfirma] = useState({});
|
||||
const [copySuccess, setCopySuccess] = useState('');
|
||||
|
||||
// Funciones para alternar visibilidad
|
||||
const togglePasswordVisibility = (id) => {
|
||||
setShowPassword(prev => ({
|
||||
...prev,
|
||||
[id]: !prev[id]
|
||||
}));
|
||||
};
|
||||
|
||||
const toggleEfirmaVisibility = (id) => {
|
||||
setShowEfirma(prev => ({
|
||||
...prev,
|
||||
[id]: !prev[id]
|
||||
}));
|
||||
};
|
||||
|
||||
// Función para copiar al portapapeles
|
||||
const copyToClipboard = async (text, fieldName) => {
|
||||
try {
|
||||
// Verificar si el texto existe
|
||||
if (!text || text.trim() === '') {
|
||||
setCopySuccess(`${fieldName} está vacío`);
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Intentar usar la Clipboard API moderna
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopySuccess(`${fieldName} copiado`);
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
} else {
|
||||
// Fallback para navegadores más antiguos o contextos no seguros
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.opacity = '0';
|
||||
textArea.style.pointerEvents = 'none';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
setCopySuccess(`${fieldName} copiado`);
|
||||
} else {
|
||||
setCopySuccess(`Error al copiar ${fieldName}`);
|
||||
}
|
||||
} catch (fallbackErr) {
|
||||
console.error('Error en fallback:', fallbackErr);
|
||||
setCopySuccess(`Error al copiar ${fieldName}`);
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error al copiar:', err);
|
||||
setCopySuccess(`Error al copiar ${fieldName}`);
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// Handlers básicos para inputs
|
||||
const handleInputChange = e => {
|
||||
const { name, value, type, checked, files } = e.target;
|
||||
@@ -64,6 +132,79 @@ export default function Vucem() {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// Funciones de descarga
|
||||
const downloadCertificate = async (id, usuario) => {
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_cer/`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
setCopySuccess('Certificado no encontrado');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
return;
|
||||
}
|
||||
throw new Error('Error al descargar certificado');
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = `${usuario}_certificado.cer`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
|
||||
setCopySuccess('Certificado descargado');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
} catch (err) {
|
||||
console.error('Error al descargar certificado:', err);
|
||||
setCopySuccess('Error al descargar certificado');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadKey = async (id, usuario) => {
|
||||
try {
|
||||
const token = localStorage.getItem('access');
|
||||
const res = await fetch(`${API_URL}/vucem/vucem/${id}/download_key/`, {
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
setCopySuccess('Clave no encontrada');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
return;
|
||||
}
|
||||
throw new Error('Error al descargar clave');
|
||||
}
|
||||
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = `${usuario}_clave.key`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
|
||||
setCopySuccess('Clave descargada');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
} catch (err) {
|
||||
console.error('Error al descargar clave:', err);
|
||||
setCopySuccess('Error al descargar clave');
|
||||
setTimeout(() => setCopySuccess(''), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVucem();
|
||||
}, []);
|
||||
@@ -90,9 +231,9 @@ export default function Vucem() {
|
||||
if (editVucem) {
|
||||
setForm({
|
||||
usuario: editVucem.usuario || '',
|
||||
password: '', // No se rellena por seguridad
|
||||
password: editVucem.password || '', // Mostrar el password actual para edición
|
||||
patente: editVucem.patente || '',
|
||||
efirma: editVucem.efirma || '',
|
||||
efirma: editVucem.efirma || '', // Ya estaba incluido
|
||||
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,
|
||||
@@ -168,6 +309,17 @@ export default function Vucem() {
|
||||
opacity: 0;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* Mensaje de éxito para copiar */}
|
||||
{copySuccess && (
|
||||
<div className="fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50 flex items-center space-x-2 animate-fade-in">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>{copySuccess}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
@@ -239,7 +391,8 @@ export default function Vucem() {
|
||||
<thead className="bg-gradient-to-r from-gray-50 to-blue-50 sticky top-0 z-20">
|
||||
<tr>
|
||||
<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">Password</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-bold text-blue-700 uppercase tracking-wider">e.firma</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>
|
||||
@@ -248,7 +401,7 @@ export default function Vucem() {
|
||||
<tbody className="bg-white divide-y divide-gray-200" style={{ position: 'relative', minHeight: 'calc(8 * 56px)' }}>
|
||||
{loading ? (
|
||||
<tr>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<td colSpan={6} 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 }}>
|
||||
<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>
|
||||
@@ -259,7 +412,7 @@ export default function Vucem() {
|
||||
</tr>
|
||||
) : error ? (
|
||||
<tr>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<td colSpan={6} 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 }}>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto h-12 w-12 bg-red-100 rounded-full flex items-center justify-center mb-4">
|
||||
@@ -285,30 +438,96 @@ export default function Vucem() {
|
||||
>
|
||||
<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 className="text-sm text-gray-500">Patente: {vucem.patente}</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>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 font-mono">
|
||||
{showPassword[vucem.id] ? vucem.password || '(vacío)' : '••••••••'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => togglePasswordVisibility(vucem.id)}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title={showPassword[vucem.id] ? "Ocultar password" : "Mostrar password"}
|
||||
>
|
||||
{showPassword[vucem.id] ? (
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-3 h-3" 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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copyToClipboard(vucem.password || '', 'Password')}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title="Copiar password"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600 font-mono">
|
||||
{showEfirma[vucem.id] ? vucem.efirma || '(vacío)' : '••••••••'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleEfirmaVisibility(vucem.id)}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title={showEfirma[vucem.id] ? "Ocultar e.firma" : "Mostrar e.firma"}
|
||||
>
|
||||
{showEfirma[vucem.id] ? (
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-3 h-3" 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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copyToClipboard(vucem.efirma || '', 'e.firma')}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title="Copiar e.firma"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</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>
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
<button
|
||||
onClick={() => downloadKey(vucem.id, vucem.usuario)}
|
||||
className="inline-flex items-center p-1 border border-blue-300 shadow-sm text-xs font-medium rounded 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"
|
||||
title="Descargar Key"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
@@ -318,12 +537,23 @@ export default function Vucem() {
|
||||
</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>
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
<button
|
||||
onClick={() => downloadCertificate(vucem.id, vucem.usuario)}
|
||||
className="inline-flex items-center p-1 border border-green-300 shadow-sm text-xs font-medium rounded text-green-700 bg-white hover:bg-green-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200"
|
||||
title="Descargar Certificado"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
@@ -365,21 +595,21 @@ export default function Vucem() {
|
||||
<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"
|
||||
className="inline-flex items-center p-2 border border-blue-300 shadow-sm 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"
|
||||
title="Editar"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4" 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"
|
||||
className="inline-flex items-center p-2 border border-red-300 shadow-sm 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"
|
||||
title="Eliminar"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4" 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>
|
||||
@@ -388,13 +618,13 @@ export default function Vucem() {
|
||||
{/* 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>
|
||||
<td className="px-6 py-4 whitespace-nowrap" colSpan={6}> </td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={5} style={{ height: 'calc(8 * 56px)', padding: 0 }}>
|
||||
<td colSpan={6} 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">
|
||||
@@ -437,42 +667,98 @@ export default function Vucem() {
|
||||
<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"
|
||||
className="inline-flex items-center justify-center p-2 border border-blue-300 shadow-sm 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"
|
||||
title="Editar"
|
||||
>
|
||||
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4" 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"
|
||||
className="inline-flex items-center justify-center p-2 border border-red-300 shadow-sm 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"
|
||||
title="Eliminar"
|
||||
>
|
||||
<svg className="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4" 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">Password:</span>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<span className="text-gray-600 font-mono">
|
||||
{showPassword[vucem.id] ? vucem.password || '(vacío)' : '••••••••'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => togglePasswordVisibility(vucem.id)}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title={showPassword[vucem.id] ? "Ocultar password" : "Mostrar password"}
|
||||
>
|
||||
{showPassword[vucem.id] ? (
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-3 h-3" 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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copyToClipboard(vucem.password || '', 'Password')}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title="Copiar password"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-500">e.firma:</span>
|
||||
<p className="text-gray-900 mt-1 truncate">{vucem.efirma}</p>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<span className="text-gray-600 font-mono">
|
||||
{showEfirma[vucem.id] ? vucem.efirma || '(vacío)' : '••••••••'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleEfirmaVisibility(vucem.id)}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title={showEfirma[vucem.id] ? "Ocultar e.firma" : "Mostrar e.firma"}
|
||||
>
|
||||
{showEfirma[vucem.id] ? (
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-3 h-3" 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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copyToClipboard(vucem.efirma || '', 'e.firma')}
|
||||
className="inline-flex items-center p-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
||||
title="Copiar e.firma"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-gray-500">Estado:</span>
|
||||
@@ -504,14 +790,25 @@ export default function Vucem() {
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<span className="font-medium text-gray-500">Archivos:</span>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
<div className="mt-1 flex flex-wrap gap-2">
|
||||
{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>
|
||||
<div className="flex items-center gap-1">
|
||||
<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>
|
||||
<button
|
||||
onClick={() => downloadKey(vucem.id, vucem.usuario)}
|
||||
className="inline-flex items-center p-1 border border-blue-300 shadow-sm text-xs font-medium rounded 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"
|
||||
title="Descargar Key"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
@@ -521,12 +818,23 @@ export default function Vucem() {
|
||||
</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>
|
||||
<div className="flex items-center gap-1">
|
||||
<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>
|
||||
<button
|
||||
onClick={() => downloadCertificate(vucem.id, vucem.usuario)}
|
||||
className="inline-flex items-center p-1 border border-green-300 shadow-sm text-xs font-medium rounded text-green-700 bg-white hover:bg-green-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-all duration-200"
|
||||
title="Descargar Certificado"
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
@@ -651,7 +959,46 @@ export default function Vucem() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Password *</label>
|
||||
<input name="password" type="password" value={form.password} 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="password" />
|
||||
<div className="relative">
|
||||
<input
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={form.password}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
className="w-full px-3 py-2 pr-20 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
||||
placeholder="password"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="px-2 py-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title={showPassword ? "Ocultar password" : "Mostrar password"}
|
||||
>
|
||||
{showPassword ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
|
||||
</svg>
|
||||
) : (
|
||||
<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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(form.password, 'Password')}
|
||||
className="px-2 py-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title="Copiar password"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Patente *</label>
|
||||
@@ -659,7 +1006,46 @@ export default function Vucem() {
|
||||
</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 className="relative">
|
||||
<input
|
||||
name="efirma"
|
||||
type={showEfirma ? "text" : "password"}
|
||||
value={form.efirma}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
className="w-full px-3 py-2 pr-20 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 className="absolute inset-y-0 right-0 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowEfirma(!showEfirma)}
|
||||
className="px-2 py-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title={showEfirma ? "Ocultar e.firma" : "Mostrar e.firma"}
|
||||
>
|
||||
{showEfirma ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
|
||||
</svg>
|
||||
) : (
|
||||
<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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(form.efirma, 'e.firma')}
|
||||
className="px-2 py-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title="Copiar e.firma"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Key (.key)</label>
|
||||
@@ -741,8 +1127,46 @@ export default function Vucem() {
|
||||
<input name="usuario" value={form.usuario} 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="usuario" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Password (dejar vacío para no cambiar)</label>
|
||||
<input name="password" type="password" value={form.password} onChange={handleInputChange} 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="password" />
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Password *</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={form.password}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-3 py-2 pr-20 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
||||
placeholder="password"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="px-2 py-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title={showPassword ? "Ocultar password" : "Mostrar password"}
|
||||
>
|
||||
{showPassword ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
|
||||
</svg>
|
||||
) : (
|
||||
<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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(form.password, 'Password')}
|
||||
className="px-2 py-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title="Copiar password"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Patente *</label>
|
||||
@@ -750,7 +1174,46 @@ export default function Vucem() {
|
||||
</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 className="relative">
|
||||
<input
|
||||
name="efirma"
|
||||
type={showEfirma ? "text" : "password"}
|
||||
value={form.efirma}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
className="w-full px-3 py-2 pr-20 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 className="absolute inset-y-0 right-0 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowEfirma(!showEfirma)}
|
||||
className="px-2 py-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title={showEfirma ? "Ocultar e.firma" : "Mostrar e.firma"}
|
||||
>
|
||||
{showEfirma ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L8.464 8.464m1.414 1.414L21.536 21.536" />
|
||||
</svg>
|
||||
) : (
|
||||
<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.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(form.efirma, 'e.firma')}
|
||||
className="px-2 py-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
title="Copiar e.firma"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Key (.key)</label>
|
||||
|
||||
Reference in New Issue
Block a user