Subo archivo JSX sin afectar rama principal

This commit is contained in:
2025-11-26 08:48:50 -07:00
parent 33ca76c054
commit 756815983d
2 changed files with 320 additions and 173 deletions

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
.env .env
*.bak

View File

@@ -18,11 +18,13 @@ const API_URL = import.meta.env.VITE_EFC_API_URL;
const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => { const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => {
try { try {
console.log('Descargar: ',id);
const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`); const res = await fetchWithAuth(`${API_URL}/record/documents/descargar/${id}/`);
if (!res.ok) { if (!res.ok) {
alert('No autorizado o error en la descarga'); //alert('No autorizado o error en la descarga');
return; showMessage('No autorizado o error en la descarga.', 'error');
return false;
} }
const blob = await res.blob(); const blob = await res.blob();
@@ -38,7 +40,9 @@ const downloadFile = async (id, filename = 'archivo', setSuccess, setError, show
} catch (error) { } catch (error) {
console.error('Error downloading file:', error); console.error('Error downloading file:', error);
showMessage('Error al descargar el archivo', 'error'); showMessage('Error al descargar el archivo', 'error');
return false;
} }
return true;
}; };
export default function Documents() { export default function Documents() {
@@ -192,6 +196,40 @@ export default function Documents() {
} }
}; };
// // Función para descargar documentos seleccionados
// const handleDownloadSelected = async () => {
// if (selectedDocuments.length === 0) {
// showMessage('No hay documentos seleccionados para descargar', 'warning');
// return;
// }
// try {
// showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
// const resultados = [];
// for (const docId of selectedDocuments) {
// const document = currentDocuments.find(doc => doc.id === docId);
// if (document) {
// // const exito =await downloadFile(docId, `expediente_${document.pedimento_app}`, null, null, showMessage);
// const exito =await downloadExpediente(docId, `expediente_${document.pedimento_app}`, null, showMessage);
// resultados.push(exito);
// // Pequeña pausa entre descargas para no sobrecargar
// await new Promise(resolve => setTimeout(resolve, 500));
// }
// }
// const todosExitosos = resultados.every(Boolean);
// if(todosExitosos){
// showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
// }else{
// showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
// }
// setSelectedDocuments([]);
// setIsSelectAll(false);
// } catch (error) {
// showMessage('Error durante la descarga masiva', 'error');
// }
// // showMessage('Error durante la descarga masiva', 'error');
// };
// Función para descargar documentos seleccionados // Función para descargar documentos seleccionados
const handleDownloadSelected = async () => { const handleDownloadSelected = async () => {
if (selectedDocuments.length === 0) { if (selectedDocuments.length === 0) {
@@ -200,18 +238,61 @@ export default function Documents() {
} }
try { try {
showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
for (const docId of selectedDocuments) { showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s) selecciobado(s)...`, 'info');
const document = currentDocuments.find(doc => doc.id === docId);
const res = await postWithAuth(`${API_URL}/record/documents/multi-pedimento-zip/`, {
pedimento_ids: selectedDocuments
});
if (!res.ok){
showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
}else {
// Leer el nombre que eligió el backend
const zipFileName = res.headers.get('X-Zip-Filename') || 'expedientes.zip';
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = zipFileName;
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
setSelectedDocuments([]);
setIsSelectAll(false);
}
} catch (error) {
showMessage('Error durante la descarga masiva', 'error');
}
// showMessage('Error durante la descarga masiva', 'error');
};
// Función para descargar todo el expediente
const handleDownloadTodoElExpediente = async (pedimentoId, pedimentoName) => {
try {
showMessage(`Iniciando descarga de ${selectedDocuments.length} documento(s)...`, 'info');
const resultados = [];
const document = currentDocuments.find(doc => doc.id === pedimentoId);
if (document) { if (document) {
await downloadFile(docId, `expediente_${document.pedimento_app}`, null, null, showMessage); const exito =await downloadExpediente(pedimentoId, `expediente_${pedimentoName}`, null, showMessage);
resultados.push(exito);
// Pequeña pausa entre descargas para no sobrecargar // Pequeña pausa entre descargas para no sobrecargar
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
} }
const todosExitosos = resultados.every(Boolean);
if(todosExitosos){
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
}else{
// showMessage('Algunas descargas fallaron. Revisa los archivos seleccionados.', 'warning');
} }
showMessage(`Descarga completada de ${selectedDocuments.length} documento(s)`, 'success');
setSelectedDocuments([]); setSelectedDocuments([]);
setIsSelectAll(false); setIsSelectAll(false);
} catch (error) { } catch (error) {
@@ -219,6 +300,38 @@ export default function Documents() {
} }
}; };
const downloadExpediente = async (pedimentoId, pedimentoName, setSuccess, showMessage) => {
try {
const res = await postWithAuth(`${API_URL}/record/documents/expediente-zip/`, {
pedimento_id: pedimentoId,
});
if (!res.ok) {
// alert('No autorizado o error en la descarga');
// showMessage('No autorizado o error en la descarga.', 'error');
const err = await res.json();
showMessage(err.error || 'Error al generar ZIP', 'error');
return false;
}
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${pedimentoName}.zip`;
// document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
if (setSuccess) setSuccess('Descarga exitosa');
} catch (error) {
console.error('Error downloading file:', error);
showMessage('Error al descargar el archivo', 'error');
return false;
}
return true;
};
// Función para eliminar documentos seleccionados // Función para eliminar documentos seleccionados
const handleDeleteSelected = async () => { const handleDeleteSelected = async () => {
if (selectedDocuments.length === 0) { if (selectedDocuments.length === 0) {
@@ -485,10 +598,30 @@ export default function Documents() {
const uploadEndpoint = `${API_URL}/customs/pedimentos/bulk-create/`; const uploadEndpoint = `${API_URL}/customs/pedimentos/bulk-create/`;
const result = await postFormDataWithAuth(uploadEndpoint, formData); const result = await postFormDataWithAuth(uploadEndpoint, formData);
if(!result.ok){
let errorMsg = 'Error al intentar cargar los archivos';
try {
const errorData = await result.json();
errorMsg = errorData.error || errorMsg;
} catch {
// Si no es JSON válido, usar texto plano
const text = await result.text();
errorMsg = text || errorMsg;
}
showMessage(errorMsg, 'error');
}else{
showMessage( showMessage(
`${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`, `${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`,
'success' 'success'
); );
}
// showMessage(
// `${result.uploaded_count || fileCount} archivo(s) subido(s) exitosamente`,
// 'success'
// );
// console.log(result);
// Limpiar archivos seleccionados y cerrar modal // Limpiar archivos seleccionados y cerrar modal
setSelectedFiles([]); setSelectedFiles([]);
@@ -522,47 +655,60 @@ export default function Documents() {
setIsSelectAll(false); setIsSelectAll(false);
}, [currentPage]); }, [currentPage]);
// función que detecta si hay filtros activos
const hasActiveFilters = [
searchFilter,
pedimentoFilter,
expedienteFilter !== 'all',
contribuyenteFilter,
curpApoderadoFilter,
fechaPagoFilter,
patenteFilter,
aduanaFilter,
tipoOperacionFilter,
clavePedimentoFilter,
].some(Boolean);
// El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla // El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla
return ( return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 p-4 sm:p-6 lg:p-8"> <div className="min-h-screen p-4 bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 sm:p-6 lg:p-8">
<div ref={focusKeeperRef} tabIndex={-1} style={{position:'absolute',width:0,height:0,overflow:'hidden',outline:'none'}} aria-hidden="true"></div> <div ref={focusKeeperRef} tabIndex={-1} style={{position:'absolute',width:0,height:0,overflow:'hidden',outline:'none'}} aria-hidden="true"></div>
<div className="max-w-7xl mx-auto"> <div className="mx-auto max-w-7xl">
{/* Header mejorado y decorativo */} {/* Header mejorado y decorativo */}
<div className={ <div className={
"mb-6 sm:mb-8 relative overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8 flex items-center gap-4 sm:gap-6"+ "mb-6 sm:mb-8 relative overflow-hidden rounded-3xl shadow-2xl bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 p-6 sm:p-8 flex items-center gap-4 sm:gap-6"+
(showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '') (showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '')
} }
style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' } : undefined}> style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.05s forwards' } : undefined}>
<div className="flex-shrink-0 bg-white/20 backdrop-blur-sm rounded-full p-3 sm:p-4 shadow-lg animate-bounce-slow"> <div className="flex-shrink-0 p-3 rounded-full shadow-lg bg-white/20 backdrop-blur-sm sm:p-4 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"> <svg className="w-8 h-8 text-white sm:h-10 sm:w-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-white tracking-tight mb-1 flex flex-col sm:flex-row sm:items-center gap-2"> <h1 className="flex flex-col gap-2 mb-1 text-2xl font-extrabold tracking-tight text-white sm:text-3xl lg:text-4xl sm:flex-row sm:items-center">
<span>Expedientes</span> <span>Expedientes</span>
{totalDocuments > 0 && ( {totalDocuments > 0 && (
<span className="inline-block bg-white/20 backdrop-blur-sm text-white text-xs sm:text-sm font-semibold px-3 py-1 rounded-full shadow-lg animate-fade-in"> <span className="inline-block px-3 py-1 text-xs font-semibold text-white rounded-full shadow-lg bg-white/20 backdrop-blur-sm sm:text-sm animate-fade-in">
{totalDocuments} registros {totalDocuments} registros
</span> </span>
)} )}
</h1> </h1>
<p className="text-sm sm:text-lg text-blue-100 font-medium leading-relaxed">Gestiona y descarga los documentos de tus pedimentos</p> <p className="text-sm font-medium leading-relaxed text-blue-100 sm:text-lg">Gestiona y descarga los documentos de tus pedimentos</p>
</div> </div>
{/* Efectos decorativos de fondo modernos */} {/* Efectos decorativos de fondo modernos */}
<div className="absolute -top-10 -right-10 opacity-20 pointer-events-none select-none"> <div className="absolute pointer-events-none select-none -top-10 -right-10 opacity-20">
<div className="w-32 h-32 bg-white/10 rounded-full blur-xl"></div> <div className="w-32 h-32 rounded-full bg-white/10 blur-xl"></div>
</div> </div>
<div className="absolute -bottom-6 -left-6 opacity-15 pointer-events-none select-none"> <div className="absolute pointer-events-none select-none -bottom-6 -left-6 opacity-15">
<div className="w-24 h-24 bg-white/10 rounded-full blur-lg"></div> <div className="w-24 h-24 rounded-full bg-white/10 blur-lg"></div>
</div> </div>
{/* Partículas flotantes */} {/* Partículas flotantes */}
<div className="absolute inset-0 overflow-hidden pointer-events-none"> <div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-2 h-2 bg-white/30 rounded-full animate-ping"></div> <div className="absolute w-2 h-2 rounded-full top-1/4 left-1/4 bg-white/30 animate-ping"></div>
<div className="absolute top-3/4 right-1/3 w-1 h-1 bg-white/40 rounded-full animate-pulse"></div> <div className="absolute w-1 h-1 rounded-full top-3/4 right-1/3 bg-white/40 animate-pulse"></div>
<div className="absolute top-1/2 right-1/4 w-3 h-3 bg-white/20 rounded-full animate-bounce"></div> <div className="absolute w-3 h-3 rounded-full top-1/2 right-1/4 bg-white/20 animate-bounce"></div>
</div> </div>
</div> </div>
{/* Animación personalizada para el icono y contador */} {/* Animación personalizada para el icono y contador */}
@@ -588,16 +734,16 @@ export default function Documents() {
(showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '') (showAnimation && !hasAnimated ? ' animate-fadein-slideup opacity-0' : '')
} }
style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' } : undefined}> style={showAnimation && !hasAnimated ? { animation: 'fadein-slideup 0.7s cubic-bezier(0.22,1,0.36,1) 0.15s forwards' } : undefined}>
<div className="px-4 sm:px-6 py-4 sm:py-6 border-b border-gray-200 bg-gradient-to-r from-gray-50 to-blue-50/30"> <div className="px-4 py-4 border-b border-gray-200 sm:px-6 sm:py-6 bg-gradient-to-r from-gray-50 to-blue-50/30">
{/* Filtros avanzados */} {/* Filtros avanzados */}
<div className="mb-4 sm:mb-6"> <div className="mb-4 sm:mb-6">
<h3 className="text-sm font-semibold text-gray-800 mb-3 flex items-center"> <h3 className="flex items-center mb-3 text-sm font-semibold text-gray-800">
<svg className="w-4 h-4 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
</svg> </svg>
Filtros de búsqueda Filtros de búsqueda
</h3> </h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4"> <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-4">
{/* Search global */} {/* Search global */}
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-xs font-semibold text-gray-700 mb-1.5">Buscar</label> <label className="text-xs font-semibold text-gray-700 mb-1.5">Buscar</label>
@@ -631,7 +777,7 @@ export default function Documents() {
</select> </select>
</div> </div>
{/* Contribuyente combobox */} {/* Contribuyente combobox */}
<div className="flex flex-col relative"> <div className="relative flex flex-col">
<label className="text-xs font-semibold text-gray-700 mb-1.5">Contribuyente</label> <label className="text-xs font-semibold text-gray-700 mb-1.5">Contribuyente</label>
<input <input
type="text" type="text"
@@ -649,7 +795,7 @@ export default function Documents() {
/> />
{/* Dropdown de sugerencias */} {/* Dropdown de sugerencias */}
{contribuyenteInput && ( {contribuyenteInput && (
<div className="absolute top-16 left-0 right-0 bg-white border border-gray-200 rounded-xl shadow-2xl z-50 max-h-40 overflow-auto"> <div className="absolute left-0 right-0 z-50 overflow-auto bg-white border border-gray-200 shadow-2xl top-16 rounded-xl max-h-40">
{contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).length === 0 ? ( {contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).length === 0 ? (
<div className="px-3 py-2 text-sm text-gray-500">Sin coincidencias</div> <div className="px-3 py-2 text-sm text-gray-500">Sin coincidencias</div>
) : ( ) : (
@@ -657,7 +803,7 @@ export default function Documents() {
<button <button
key={c} key={c}
type="button" type="button"
className="w-full text-left px-3 py-2 hover:bg-blue-50 text-sm transition-colors duration-200 first:rounded-t-xl last:rounded-b-xl" className="w-full px-3 py-2 text-sm text-left transition-colors duration-200 hover:bg-blue-50 first:rounded-t-xl last:rounded-b-xl"
onClick={() => { onClick={() => {
setContribuyenteFilter(c); setContribuyenteFilter(c);
setContribuyenteInput(''); setContribuyenteInput('');
@@ -738,11 +884,11 @@ export default function Documents() {
{/* Área de acciones para documentos seleccionados */} {/* Área de acciones para documentos seleccionados */}
{selectedDocuments.length > 0 && ( {selectedDocuments.length > 0 && (
<div className="mb-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-2xl overflow-hidden"> <div className="mb-4 overflow-hidden border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl">
<div className="px-6 py-4 border-b border-blue-200"> <div className="px-6 py-4 border-b border-blue-200">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-2"> <div className="p-2 bg-blue-100 rounded-full">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
@@ -759,7 +905,7 @@ export default function Documents() {
setSelectedDocuments([]); setSelectedDocuments([]);
setIsSelectAll(false); setIsSelectAll(false);
}} }}
className="text-gray-400 hover:text-gray-600 transition-colors" className="text-gray-400 transition-colors hover:text-gray-600"
title="Limpiar selección" title="Limpiar selección"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -772,7 +918,7 @@ export default function Documents() {
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<button <button
onClick={handleDeleteSelected} onClick={handleDeleteSelected}
className="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors duration-200 shadow-sm hover:shadow-md" className="inline-flex items-center px-4 py-2 font-medium text-white transition-colors duration-200 bg-red-600 rounded-lg shadow-sm hover:bg-red-700 hover:shadow-md"
> >
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 mr-2" 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" /> <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" />
@@ -784,16 +930,16 @@ export default function Documents() {
</div> </div>
)} )}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"> <div className="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="inline-flex items-center text-xs text-blue-600 bg-blue-50 px-3 py-2 rounded-full font-medium"> <span className="inline-flex items-center px-3 py-2 text-xs font-medium text-blue-600 rounded-full bg-blue-50">
<svg className="w-4 h-4 mr-2 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg> </svg>
Actualización automática cada 30s Actualización automática cada 30s
</span> </span>
{loading && ( {loading && (
<span className="inline-flex items-center text-xs text-orange-600 bg-orange-50 px-3 py-2 rounded-full font-medium"> <span className="inline-flex items-center px-3 py-2 text-xs font-medium text-orange-600 rounded-full bg-orange-50">
<svg className="w-4 h-4 mr-2 animate-spin" fill="none" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
@@ -826,21 +972,21 @@ export default function Documents() {
Actualizar Ahora Actualizar Ahora
</button> </button>
<button <button
onClick={() => {}} onClick={() => {handleDownloadSelected()}}
className="inline-flex items-center px-4 py-2.5 border border-transparent text-sm font-medium rounded-xl text-white bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200 transform hover:scale-105 shadow-lg" className="inline-flex items-center px-4 py-2.5 border border-transparent text-sm font-medium rounded-xl text-white bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200 transform hover:scale-105 shadow-lg"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg> </svg>
Descargar Todos {hasActiveFilters ? 'Descargar por Filtro' : 'Descargar Todos'}
</button> </button>
</div> </div>
</div> </div>
{success && ( {success && (
<div className="mt-4 bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-xl p-4 shadow-sm"> <div className="p-4 mt-4 border border-green-200 shadow-sm bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<svg className="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
@@ -855,30 +1001,30 @@ export default function Documents() {
<div className="overflow-hidden"> <div className="overflow-hidden">
{/* Vista de tabla para pantallas grandes */} {/* Vista de tabla para pantallas grandes */}
<div className="hidden lg:block"> <div className="hidden lg:block">
<div className="overflow-x-auto shadow ring-1 ring-black ring-opacity-5 rounded-lg"> <div className="overflow-x-auto rounded-lg shadow ring-1 ring-black ring-opacity-5">
<table className="min-w-full divide-y divide-gray-300" style={{ minWidth: '1200px' }}> <table className="min-w-full divide-y divide-gray-300" style={{ minWidth: '1200px' }}>
<thead className="bg-gray-50"> <thead className="bg-gray-50">
<tr> <tr>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
<input <input
type="checkbox" type="checkbox"
checked={isSelectAll} checked={isSelectAll}
onChange={handleSelectAll} onChange={handleSelectAll}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</th> </th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pedimento</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Pedimento</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Pago</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Fecha Pago</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contribuyente</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Contribuyente</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CURP Apod.</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">CURP Apod.</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Partidas</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Partidas</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">F. Carga</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">F. Carga</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tipo Op.</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Tipo Op.</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Clave</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Clave</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Archivos</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Archivos</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Peso Total</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Peso Total</th>
<th scope="col" className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expediente</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Expediente</th>
<th scope="col" className="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th> <th scope="col" className="px-4 py-2 text-xs font-medium tracking-wider text-center text-gray-500 uppercase">Acciones</th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
@@ -886,8 +1032,8 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div> <div className="w-12 h-12 mb-4 border-b-2 border-blue-600 rounded-full animate-spin"></div>
<span className="text-gray-500 text-lg font-medium">Cargando expedientes...</span> <span className="text-lg font-medium text-gray-500">Cargando expedientes...</span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -895,12 +1041,12 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="bg-red-100 rounded-full p-3 mb-4"> <div className="p-3 mb-4 bg-red-100 rounded-full">
<svg className="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 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"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
<span className="text-red-600 text-lg font-medium">Error: {error.message || 'Error al cargar expedientes'}</span> <span className="text-lg font-medium text-red-600">Error: {error.message || 'Error al cargar expedientes'}</span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -912,22 +1058,22 @@ export default function Documents() {
type="checkbox" type="checkbox"
checked={selectedDocuments.includes(ped.id)} checked={selectedDocuments.includes(ped.id)}
onChange={(e) => handleSelectDocument(ped.id, e.target.checked)} onChange={(e) => handleSelectDocument(ped.id, e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</td> </td>
<td className="px-4 py-3 whitespace-nowrap"> <td className="px-4 py-3 whitespace-nowrap">
<Link <Link
to={`/expedientes/pedimento/${ped.id}`} to={`/expedientes/pedimento/${ped.id}`}
className="text-blue-600 hover:text-blue-800 font-semibold transition-colors duration-200 text-xs" className="text-xs font-semibold text-blue-600 transition-colors duration-200 hover:text-blue-800"
> >
{ped.pedimento_app} {ped.pedimento_app}
</Link> </Link>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.fecha_pago}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.fecha_pago}</td>
<td className="px-4 py-3 text-xs text-gray-900 max-w-xs truncate" title={ped.contribuyente}>{ped.contribuyente}</td> <td className="max-w-xs px-4 py-3 text-xs text-gray-900 truncate" title={ped.contribuyente}>{ped.contribuyente}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.curp_apoderado}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.curp_apoderado}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.numero_partidas}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.numero_partidas}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</td>
<td className="px-4 py-3 whitespace-nowrap"> <td className="px-4 py-3 whitespace-nowrap">
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{ped.tipo_operacion === 1 {ped.tipo_operacion === 1
@@ -937,9 +1083,9 @@ export default function Documents() {
: ped.tipo_operacion} : ped.tipo_operacion}
</span> </span>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.clave_pedimento}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.clave_pedimento}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900">{ped.documentos_count || 0}</td> <td className="px-4 py-3 text-xs text-gray-900 whitespace-nowrap">{ped.documentos_count || 0}</td>
<td className="px-4 py-3 whitespace-nowrap text-xs text-gray-900 min-w-0"> <td className="min-w-0 px-4 py-3 text-xs text-gray-900 whitespace-nowrap">
<div className="truncate" title={typeof ped.documentos_peso_total === 'number' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' : ped.documentos_peso_total}> <div className="truncate" title={typeof ped.documentos_peso_total === 'number' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' : ped.documentos_peso_total}>
{typeof ped.documentos_peso_total === 'number' {typeof ped.documentos_peso_total === 'number'
? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB' ? (ped.documentos_peso_total / (1024 * 1024)).toFixed(2) + ' MB'
@@ -969,13 +1115,13 @@ export default function Documents() {
)} )}
</span> </span>
</td> </td>
<td className="px-4 py-3 whitespace-nowrap text-center"> <td className="px-4 py-3 text-center whitespace-nowrap">
<button <button
className="p-2 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200" className="p-2 text-blue-600 transition-colors duration-200 rounded-full hover:text-blue-800 hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
onClick={() => {}} onClick={() => handleDownloadTodoElExpediente(ped.id, ped.pedimento_app)}
title="Descargar" title="Descargar"
> >
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg> </svg>
</button> </button>
@@ -986,12 +1132,12 @@ export default function Documents() {
<tr> <tr>
<td colSpan={12} className="px-6 py-12 text-center"> <td colSpan={12} className="px-6 py-12 text-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="bg-gray-100 rounded-full p-4 mb-4"> <div className="p-4 mb-4 bg-gray-100 rounded-full">
<svg className="h-8 w-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
</div> </div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">No hay expedientes</h3> <h3 className="mb-2 text-lg font-semibold text-gray-900">No hay expedientes</h3>
<p className="text-gray-500">No se encontraron expedientes con los filtros aplicados.</p> <p className="text-gray-500">No se encontraron expedientes con los filtros aplicados.</p>
</div> </div>
</td> </td>
@@ -1003,37 +1149,37 @@ export default function Documents() {
</div> </div>
{/* Vista de tarjetas para pantallas pequeñas y medianas */} {/* Vista de tarjetas para pantallas pequeñas y medianas */}
<div className="lg:hidden space-y-4 p-4"> <div className="p-4 space-y-4 lg:hidden">
{loading ? ( {loading ? (
<div className="flex flex-col items-center py-12"> <div className="flex flex-col items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div> <div className="w-12 h-12 mb-4 border-b-2 border-blue-600 rounded-full animate-spin"></div>
<span className="text-gray-500 text-lg font-medium">Cargando expedientes...</span> <span className="text-lg font-medium text-gray-500">Cargando expedientes...</span>
</div> </div>
) : error ? ( ) : error ? (
<div className="flex flex-col items-center py-12"> <div className="flex flex-col items-center py-12">
<div className="bg-red-100 rounded-full p-3 mb-4"> <div className="p-3 mb-4 bg-red-100 rounded-full">
<svg className="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 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"></path> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
</div> </div>
<span className="text-red-600 text-lg font-medium">Error: {error.message || 'Error al cargar expedientes'}</span> <span className="text-lg font-medium text-red-600">Error: {error.message || 'Error al cargar expedientes'}</span>
</div> </div>
) : currentDocuments.length > 0 ? ( ) : currentDocuments.length > 0 ? (
currentDocuments.map(ped => ( currentDocuments.map(ped => (
<div key={ped.id} className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 hover:shadow-xl transition-all duration-300 relative"> <div key={ped.id} className="relative p-4 transition-all duration-300 bg-white border border-gray-200 shadow-lg rounded-2xl hover:shadow-xl">
{/* Checkbox en la esquina superior derecha */} {/* Checkbox en la esquina superior derecha */}
<div className="absolute top-4 right-4"> <div className="absolute top-4 right-4">
<input <input
type="checkbox" type="checkbox"
checked={selectedDocuments.includes(ped.id)} checked={selectedDocuments.includes(ped.id)}
onChange={(e) => handleSelectDocument(ped.id, e.target.checked)} onChange={(e) => handleSelectDocument(ped.id, e.target.checked)}
className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="w-5 h-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/> />
</div> </div>
<div className="flex items-start justify-between mb-4 pr-8">{/* Agregamos pr-8 para dar espacio al checkbox */} <div className="flex items-start justify-between pr-8 mb-4">{/* Agregamos pr-8 para dar espacio al checkbox */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-xl p-2 flex-shrink-0"> <div className="flex-shrink-0 p-2 bg-blue-100 rounded-xl">
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
@@ -1041,7 +1187,7 @@ export default function Documents() {
<div className="flex-1"> <div className="flex-1">
<Link <Link
to={`/expedientes/pedimento/${ped.id}`} to={`/expedientes/pedimento/${ped.id}`}
className="text-lg font-semibold text-blue-600 hover:text-blue-800 transition-colors duration-200" className="text-lg font-semibold text-blue-600 transition-colors duration-200 hover:text-blue-800"
> >
{ped.pedimento_app} {ped.pedimento_app}
</Link> </Link>
@@ -1057,7 +1203,7 @@ export default function Documents() {
</div> </div>
</div> </div>
<div className="space-y-3 mb-4"> <div className="mb-4 space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-600">Contribuyente:</span> <span className="text-sm font-medium text-gray-600">Contribuyente:</span>
<span className="text-sm text-gray-900 text-right max-w-[60%] truncate" title={ped.contribuyente}> <span className="text-sm text-gray-900 text-right max-w-[60%] truncate" title={ped.contribuyente}>
@@ -1071,27 +1217,27 @@ export default function Documents() {
</div> </div>
)} )}
<div className="grid grid-cols-1 gap-2"> <div className="grid grid-cols-1 gap-2">
<div className="flex items-center justify-between bg-green-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-green-50">
<span className="text-sm font-medium text-green-700">Tipo Operacion</span> <span className="text-sm font-medium text-green-700">Tipo Operacion</span>
<span className="text-sm font-bold text-green-800">{ped.tipo_operacion}</span> <span className="text-sm font-bold text-green-800">{ped.tipo_operacion}</span>
</div> </div>
<div className="flex items-center justify-between bg-green-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-green-50">
<span className="text-sm font-medium text-green-700">Partidas</span> <span className="text-sm font-medium text-green-700">Partidas</span>
<span className="text-sm font-bold text-green-800">{ped.numero_partidas}</span> <span className="text-sm font-bold text-green-800">{ped.numero_partidas}</span>
</div> </div>
<div className="flex items-center justify-between bg-blue-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-blue-50">
<span className="text-sm font-medium text-blue-700">Fecha de Carga</span> <span className="text-sm font-medium text-blue-700">Fecha de Carga</span>
<span className="text-sm font-bold text-blue-800">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</span> <span className="text-sm font-bold text-blue-800">{ped.created_at ? ped.created_at.slice(0, 10) : ''}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">Clave</span> <span className="text-sm font-medium text-gray-700">Clave</span>
<span className="text-sm font-bold text-gray-800">{ped.clave_pedimento}</span> <span className="text-sm font-bold text-gray-800">{ped.clave_pedimento}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">No. Archivos</span> <span className="text-sm font-medium text-gray-700">No. Archivos</span>
<span className="text-sm font-bold text-gray-800">{ped.documentos_count || 0}</span> <span className="text-sm font-bold text-gray-800">{ped.documentos_count || 0}</span>
</div> </div>
<div className="flex items-center justify-between bg-gray-50 rounded-lg p-2"> <div className="flex items-center justify-between p-2 rounded-lg bg-gray-50">
<span className="text-sm font-medium text-gray-700">Peso Total</span> <span className="text-sm font-medium text-gray-700">Peso Total</span>
<span className="text-sm font-bold text-gray-800"> <span className="text-sm font-bold text-gray-800">
{typeof ped.documentos_peso_total === 'number' {typeof ped.documentos_peso_total === 'number'
@@ -1104,20 +1250,20 @@ export default function Documents() {
</div> </div>
)) ))
) : ( ) : (
<div className="bg-gray-50 rounded-2xl p-8 text-center"> <div className="p-8 text-center bg-gray-50 rounded-2xl">
<div className="bg-gray-100 rounded-full p-4 w-16 h-16 mx-auto mb-4 flex items-center justify-center"> <div className="flex items-center justify-center w-16 h-16 p-4 mx-auto mb-4 bg-gray-100 rounded-full">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
</div> </div>
<p className="text-gray-500 font-medium">No hay expedientes disponibles</p> <p className="font-medium text-gray-500">No hay expedientes disponibles</p>
<p className="text-gray-400 text-sm mt-1">Intenta ajustar los filtros de búsqueda</p> <p className="mt-1 text-sm text-gray-400">Intenta ajustar los filtros de búsqueda</p>
</div> </div>
)} )}
</div> </div>
{/* Paginación moderna y responsiva */} {/* Paginación moderna y responsiva */}
{totalDocuments > 0 && ( {totalDocuments > 0 && (
<div className="bg-gradient-to-r from-gray-50 to-blue-50/30 px-4 sm:px-6 py-4 flex flex-col sm:flex-row items-center justify-between border-t border-gray-200"> <div className="flex flex-col items-center justify-between px-4 py-4 border-t border-gray-200 bg-gradient-to-r from-gray-50 to-blue-50/30 sm:px-6 sm:flex-row">
{(() => { {(() => {
const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage)); const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage));
const maxPagesToShow = 5; const maxPagesToShow = 5;
@@ -1132,21 +1278,21 @@ export default function Documents() {
pageNumbers.push(i); pageNumbers.push(i);
} }
return ( return (
<div className="flex flex-col sm:flex-row sm:items-center w-full gap-4"> <div className="flex flex-col w-full gap-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<label htmlFor="itemsPerPage" className="text-xs font-semibold text-gray-700">Registros por página:</label> <label htmlFor="itemsPerPage" className="text-xs font-semibold text-gray-700">Registros por página:</label>
<select <select
id="itemsPerPage" id="itemsPerPage"
value={itemsPerPage} value={itemsPerPage}
onChange={e => handleItemsPerPageChange(Number(e.target.value))} onChange={e => handleItemsPerPageChange(Number(e.target.value))}
className="border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white shadow-sm" className="px-3 py-2 text-sm bg-white border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
> >
{[5, 10, 20, 50, 100, 200, 400, 800, 1200, 2400, 10000].map(size => ( {[5, 10, 20, 50, 100, 200, 400, 800, 1200, 2400, 10000].map(size => (
<option key={size} value={size}>{size}</option> <option key={size} value={size}>{size}</option>
))} ))}
</select> </select>
</div> </div>
<div className="flex items-center justify-center sm:justify-end flex-1 gap-1"> <div className="flex items-center justify-center flex-1 gap-1 sm:justify-end">
<button <button
type="button" type="button"
onClick={e => handlePageChange(1, e)} onClick={e => handlePageChange(1, e)}
@@ -1163,7 +1309,7 @@ export default function Documents() {
> >
</button> </button>
<div className="hidden sm:flex items-center gap-1"> <div className="items-center hidden gap-1 sm:flex">
{pageNumbers.map(num => ( {pageNumbers.map(num => (
<button <button
type="button" type="button"
@@ -1176,7 +1322,7 @@ export default function Documents() {
</button> </button>
))} ))}
</div> </div>
<div className="sm:hidden flex items-center px-3 py-2 bg-white border border-gray-200 rounded-lg shadow-sm"> <div className="flex items-center px-3 py-2 bg-white border border-gray-200 rounded-lg shadow-sm sm:hidden">
<span className="text-sm font-semibold text-gray-700"> <span className="text-sm font-semibold text-gray-700">
{currentPage} / {totalPages} {currentPage} / {totalPages}
</span> </span>
@@ -1199,7 +1345,7 @@ export default function Documents() {
</button> </button>
</div> </div>
<div className="text-center sm:text-right"> <div className="text-center sm:text-right">
<span className="text-xs text-gray-600 bg-white px-3 py-2 rounded-lg border border-gray-200 shadow-sm"> <span className="px-3 py-2 text-xs text-gray-600 bg-white border border-gray-200 rounded-lg shadow-sm">
Mostrando <span className="font-bold text-blue-600">{((currentPage - 1) * itemsPerPage) + 1}</span> a <span className="font-bold text-blue-600">{Math.min(currentPage * itemsPerPage, totalDocuments)}</span> de <span className="font-bold text-blue-600">{totalDocuments}</span> registros Mostrando <span className="font-bold text-blue-600">{((currentPage - 1) * itemsPerPage) + 1}</span> a <span className="font-bold text-blue-600">{Math.min(currentPage * itemsPerPage, totalDocuments)}</span> de <span className="font-bold text-blue-600">{totalDocuments}</span> registros
</span> </span>
</div> </div>
@@ -1213,13 +1359,13 @@ export default function Documents() {
{/* Modal de subida de expedientes */} {/* Modal de subida de expedientes */}
{showUploadModal && ( {showUploadModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
<div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full mx-4 transform transition-all duration-300 scale-100 max-h-[90vh] flex flex-col"> <div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full mx-4 transform transition-all duration-300 scale-100 max-h-[90vh] flex flex-col">
{/* Header del modal */} {/* Header del modal */}
<div className="px-6 py-4 border-b border-gray-200 flex-shrink-0"> <div className="flex-shrink-0 px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-3"> <div className="p-3 bg-blue-100 rounded-full">
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
@@ -1237,7 +1383,7 @@ export default function Documents() {
setIsUploading(false); setIsUploading(false);
setValidationErrors([]); setValidationErrors([]);
}} }}
className="text-gray-400 hover:text-gray-600 transition-colors duration-200" className="text-gray-400 transition-colors duration-200 hover:text-gray-600"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
@@ -1247,10 +1393,10 @@ export default function Documents() {
</div> </div>
{/* Contenido del modal - con scroll */} {/* Contenido del modal - con scroll */}
<div className="flex-1 overflow-y-auto px-6 py-4 min-h-0"> <div className="flex-1 min-h-0 px-6 py-4 overflow-y-auto">
{/* Selector de tipo de subida */} {/* Selector de tipo de subida */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
Tipo de subida Tipo de subida
</label> </label>
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
@@ -1298,7 +1444,7 @@ export default function Documents() {
{/* Área de selección de archivos */} {/* Área de selección de archivos */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
{uploadType === 'folders' && 'Seleccionar carpetas'} {uploadType === 'folders' && 'Seleccionar carpetas'}
{uploadType === 'zip' && 'Seleccionar archivos ZIP'} {uploadType === 'zip' && 'Seleccionar archivos ZIP'}
{uploadType === 'rar' && 'Seleccionar archivos RAR'} {uploadType === 'rar' && 'Seleccionar archivos RAR'}
@@ -1306,11 +1452,11 @@ export default function Documents() {
{uploadType === 'folders' ? ( {uploadType === 'folders' ? (
<div className="space-y-3"> <div className="space-y-3">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors duration-200"> <div className="p-6 text-center transition-colors duration-200 border-2 border-gray-300 border-dashed rounded-lg hover:border-gray-400">
<svg className="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg> </svg>
<p className="text-sm text-gray-600 mb-3"> <p className="mb-3 text-sm text-gray-600">
Selecciona una carpeta. Después puedes hacer clic nuevamente para agregar más carpetas. Selecciona una carpeta. Después puedes hacer clic nuevamente para agregar más carpetas.
</p> </p>
<input <input
@@ -1323,14 +1469,14 @@ export default function Documents() {
/> />
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" className="inline-flex items-center px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg> </svg>
Agregar Carpeta Agregar Carpeta
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<span className="ml-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-full"> <span className="px-2 py-1 ml-2 text-xs text-white bg-blue-500 rounded-full">
{[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].length} {[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].length}
</span> </span>
)} )}
@@ -1339,13 +1485,13 @@ export default function Documents() {
{/* Lista de carpetas seleccionadas - con altura máxima y scroll */} {/* Lista de carpetas seleccionadas - con altura máxima y scroll */}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4"> <div className="p-4 border border-gray-200 rounded-lg bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-2">Carpetas seleccionadas:</h4> <h4 className="mb-2 text-sm font-medium text-gray-700">Carpetas seleccionadas:</h4>
<div className="max-h-40 overflow-y-auto space-y-2"> <div className="space-y-2 overflow-y-auto max-h-40">
{[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].map((folderName, index) => ( {[...new Set(selectedFiles.map(file => file.webkitRelativePath.split('/')[0]))].map((folderName, index) => (
<div key={index} className="flex items-center justify-between bg-white p-3 rounded border"> <div key={index} className="flex items-center justify-between p-3 bg-white border rounded">
<div className="flex items-center"> <div className="flex items-center">
<svg className="w-4 h-4 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg> </svg>
<span className="text-sm font-medium">{folderName}</span> <span className="text-sm font-medium">{folderName}</span>
@@ -1356,7 +1502,7 @@ export default function Documents() {
</span> </span>
<button <button
onClick={() => removeFolderFromSelection(folderName)} onClick={() => removeFolderFromSelection(folderName)}
className="text-red-500 hover:text-red-700 p-1" className="p-1 text-red-500 hover:text-red-700"
title="Eliminar carpeta" title="Eliminar carpeta"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1378,11 +1524,11 @@ export default function Documents() {
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors duration-200"> <div className="p-6 text-center transition-colors duration-200 border-2 border-gray-300 border-dashed rounded-lg hover:border-gray-400">
<svg className="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
<p className="text-sm text-gray-600 mb-3"> <p className="mb-3 text-sm text-gray-600">
{uploadType === 'zip' && 'Selecciona archivos ZIP. Puedes hacer clic nuevamente para agregar más archivos.'} {uploadType === 'zip' && 'Selecciona archivos ZIP. Puedes hacer clic nuevamente para agregar más archivos.'}
{uploadType === 'rar' && 'Selecciona archivos RAR. Puedes hacer clic nuevamente para agregar más archivos.'} {uploadType === 'rar' && 'Selecciona archivos RAR. Puedes hacer clic nuevamente para agregar más archivos.'}
</p> </p>
@@ -1396,14 +1542,14 @@ export default function Documents() {
/> />
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors duration-200" className="inline-flex items-center px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
> >
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg> </svg>
Agregar {uploadType === 'zip' ? 'ZIP' : 'RAR'} Agregar {uploadType === 'zip' ? 'ZIP' : 'RAR'}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<span className="ml-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-full"> <span className="px-2 py-1 ml-2 text-xs text-white bg-blue-500 rounded-full">
{selectedFiles.length} {selectedFiles.length}
</span> </span>
)} )}
@@ -1412,13 +1558,13 @@ export default function Documents() {
{/* Lista de archivos seleccionados */} {/* Lista de archivos seleccionados */}
{selectedFiles.length > 0 && ( {selectedFiles.length > 0 && (
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4"> <div className="p-4 border border-gray-200 rounded-lg bg-gray-50">
<h4 className="text-sm font-medium text-gray-700 mb-2">Archivos seleccionados:</h4> <h4 className="mb-2 text-sm font-medium text-gray-700">Archivos seleccionados:</h4>
<div className="max-h-40 overflow-y-auto space-y-2"> <div className="space-y-2 overflow-y-auto max-h-40">
{selectedFiles.map((file, index) => ( {selectedFiles.map((file, index) => (
<div key={index} className="flex items-center justify-between bg-white p-3 rounded border"> <div key={index} className="flex items-center justify-between p-3 bg-white border rounded">
<div className="flex items-center"> <div className="flex items-center">
<svg className="w-4 h-4 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
<span className="text-sm font-medium">{file.name}</span> <span className="text-sm font-medium">{file.name}</span>
@@ -1429,7 +1575,7 @@ export default function Documents() {
</span> </span>
<button <button
onClick={() => removeFileFromSelection(index)} onClick={() => removeFileFromSelection(index)}
className="text-red-500 hover:text-red-700 p-1" className="p-1 text-red-500 hover:text-red-700"
title="Eliminar archivo" title="Eliminar archivo"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1454,7 +1600,7 @@ export default function Documents() {
{/* Selector de contribuyente */} {/* Selector de contribuyente */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block mb-2 text-sm font-medium text-gray-700">
Contribuyente Contribuyente
</label> </label>
<select <select
@@ -1471,20 +1617,20 @@ export default function Documents() {
))} ))}
</select> </select>
{loadingContributors && ( {loadingContributors && (
<p className="text-sm text-gray-500 mt-1">Cargando contribuyentes...</p> <p className="mt-1 text-sm text-gray-500">Cargando contribuyentes...</p>
)} )}
</div> </div>
{/* Errores de validación */} {/* Errores de validación */}
{validationErrors.length > 0 && ( {validationErrors.length > 0 && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-md"> <div className="p-3 mb-4 border border-red-200 rounded-md bg-red-50">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-red-500" 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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<span className="text-sm font-medium text-red-800">Errores de validación:</span> <span className="text-sm font-medium text-red-800">Errores de validación:</span>
</div> </div>
<ul className="text-sm text-red-700 space-y-1"> <ul className="space-y-1 text-sm text-red-700">
{validationErrors.map((error, index) => ( {validationErrors.map((error, index) => (
<li key={index}> {error}</li> <li key={index}> {error}</li>
))} ))}
@@ -1494,7 +1640,7 @@ export default function Documents() {
{/* Información de nomenclatura */} {/* Información de nomenclatura */}
{uploadType === 'folders' && ( {uploadType === 'folders' && (
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md"> <div className="p-3 mb-4 border border-blue-200 rounded-md bg-blue-50">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<svg className="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@@ -1524,9 +1670,9 @@ export default function Documents() {
<span className="text-sm font-medium text-gray-700">Comprimiendo carpetas...</span> <span className="text-sm font-medium text-gray-700">Comprimiendo carpetas...</span>
<span className="text-sm text-gray-500">{compressionProgress}%</span> <span className="text-sm text-gray-500">{compressionProgress}%</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full h-2 bg-gray-200 rounded-full">
<div <div
className="bg-green-600 h-2 rounded-full transition-all duration-300" className="h-2 transition-all duration-300 bg-green-600 rounded-full"
style={{ width: `${compressionProgress}%` }} style={{ width: `${compressionProgress}%` }}
></div> ></div>
</div> </div>
@@ -1539,9 +1685,9 @@ export default function Documents() {
<span className="text-sm font-medium text-gray-700">Subiendo...</span> <span className="text-sm font-medium text-gray-700">Subiendo...</span>
<span className="text-sm text-gray-500">{uploadProgress}%</span> <span className="text-sm text-gray-500">{uploadProgress}%</span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2"> <div className="w-full h-2 bg-gray-200 rounded-full">
<div <div
className="bg-blue-600 h-2 rounded-full transition-all duration-300" className="h-2 transition-all duration-300 bg-blue-600 rounded-full"
style={{ width: `${uploadProgress}%` }} style={{ width: `${uploadProgress}%` }}
></div> ></div>
</div> </div>
@@ -1550,7 +1696,7 @@ export default function Documents() {
</div> </div>
{/* Footer del modal - fijo */} {/* Footer del modal - fijo */}
<div className="flex-shrink-0 px-6 py-4 border-t border-gray-200 flex gap-3 justify-end"> <div className="flex justify-end flex-shrink-0 gap-3 px-6 py-4 border-t border-gray-200">
<button <button
onClick={() => { onClick={() => {
setShowUploadModal(false); setShowUploadModal(false);
@@ -1562,14 +1708,14 @@ export default function Documents() {
setValidationErrors([]); setValidationErrors([]);
}} }}
disabled={isUploading || isCompressing} disabled={isUploading || isCompressing}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-4 py-2 text-sm font-medium text-gray-700 transition-colors duration-200 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
> >
Cancelar Cancelar
</button> </button>
<button <button
onClick={handleUploadFiles} onClick={handleUploadFiles}
disabled={isUploading || isCompressing || selectedFiles.length === 0 || !selectedContributor || validationErrors.length > 0} disabled={isUploading || isCompressing || selectedFiles.length === 0 || !selectedContributor || validationErrors.length > 0}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isCompressing ? 'Comprimiendo...' : isUploading ? 'Subiendo...' : 'Subir expedientes'} {isCompressing ? 'Comprimiendo...' : isUploading ? 'Subiendo...' : 'Subir expedientes'}
</button> </button>
@@ -1580,12 +1726,12 @@ export default function Documents() {
{/* Modal de confirmación para eliminación */} {/* Modal de confirmación para eliminación */}
{showDeleteModal && ( {showDeleteModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
<div className="bg-white rounded-2xl shadow-2xl max-w-md w-full mx-4 transform transition-all duration-300 scale-100"> <div className="w-full max-w-md mx-4 transition-all duration-300 transform scale-100 bg-white shadow-2xl rounded-2xl">
{/* Header del modal */} {/* Header del modal */}
<div className="px-6 py-4 border-b border-gray-200"> <div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="bg-red-100 rounded-full p-3"> <div className="p-3 bg-red-100 rounded-full">
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="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" /> <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> </svg>
@@ -1602,7 +1748,7 @@ export default function Documents() {
{/* Contenido del modal */} {/* Contenido del modal */}
<div className="px-6 py-4"> <div className="px-6 py-4">
<div className="mb-4"> <div className="mb-4">
<p className="text-gray-700 mb-3"> <p className="mb-3 text-gray-700">
¿Estás seguro de que deseas eliminar{' '} ¿Estás seguro de que deseas eliminar{' '}
<span className="font-semibold text-red-600"> <span className="font-semibold text-red-600">
{selectedDocuments.length} documento{selectedDocuments.length !== 1 ? 's' : ''} {selectedDocuments.length} documento{selectedDocuments.length !== 1 ? 's' : ''}
@@ -1610,14 +1756,14 @@ export default function Documents() {
? ?
</p> </p>
<div className="bg-red-50 border border-red-200 rounded-lg p-3"> <div className="p-3 border border-red-200 rounded-lg bg-red-50">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<svg className="w-5 h-5 text-red-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-red-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg> </svg>
<div> <div>
<p className="text-sm font-medium text-red-800">Advertencia importante</p> <p className="text-sm font-medium text-red-800">Advertencia importante</p>
<p className="text-sm text-red-700 mt-1"> <p className="mt-1 text-sm text-red-700">
Los documentos eliminados no podrán ser recuperados. Asegúrate de que realmente deseas proceder con esta acción. Los documentos eliminados no podrán ser recuperados. Asegúrate de que realmente deseas proceder con esta acción.
</p> </p>
</div> </div>
@@ -1627,16 +1773,16 @@ export default function Documents() {
</div> </div>
{/* Botones del modal */} {/* Botones del modal */}
<div className="px-6 py-4 border-t border-gray-200 flex justify-end gap-3"> <div className="flex justify-end gap-3 px-6 py-4 border-t border-gray-200">
<button <button
onClick={() => setShowDeleteModal(false)} onClick={() => setShowDeleteModal(false)}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200" className="px-4 py-2 text-sm font-medium text-gray-700 transition-colors duration-200 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
> >
Cancelar Cancelar
</button> </button>
<button <button
onClick={confirmDelete} onClick={confirmDelete}
className="px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200 flex items-center gap-2" className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white transition-colors duration-200 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
> >
<svg className="w-4 h-4" 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" /> <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" />