Merge pull request 'feat: Implementar subida avanzada de expedientes con compresión automática' (#4) from feature/document-upload-updated into main
Reviewed-on: #4
This commit is contained in:
@@ -16,7 +16,7 @@ import xml from 'highlight.js/lib/languages/xml';
|
||||
import 'highlight.js/styles/github.css';
|
||||
hljs.registerLanguage('xml', xml);
|
||||
import { fetchPedimentoDocuments } from '../api/pedimentoDocuments';
|
||||
import { fetchWithAuth, postWithAuth, putWithAuth } from '../fetchWithAuth';
|
||||
import { fetchWithAuth, postWithAuth, putWithAuth, postFormDataWithAuth } from '../fetchWithAuth';
|
||||
import { fetchTasks } from '../api/procesos.ts';
|
||||
import { fetchPedimentoCoves, downloadCove, downloadAcuseCove } from '../api/coves';
|
||||
import { fetchPedimentoEdocuments, downloadEdocument, downloadAcuseEdocument } from '../api/edocuments';
|
||||
@@ -131,6 +131,11 @@ export default function PedimentoDetail() {
|
||||
const [selectedDocuments, setSelectedDocuments] = useState([]);
|
||||
const [isSelectAllDocs, setIsSelectAllDocs] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showUploadModal, setShowUploadModal] = useState(false);
|
||||
|
||||
// Estados para subir documentos
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
const [uploadingDocuments, setUploadingDocuments] = useState(false);
|
||||
|
||||
const [dashboardSummary, setDashboardSummary] = useState(null);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
@@ -673,6 +678,56 @@ export default function PedimentoDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
// Funciones para subir documentos
|
||||
const handleFileSelect = (event) => {
|
||||
const files = Array.from(event.target.files);
|
||||
setSelectedFiles(files);
|
||||
};
|
||||
|
||||
const handleUploadDocuments = async () => {
|
||||
if (selectedFiles.length === 0) {
|
||||
showMessage('Por favor selecciona al menos un archivo', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadingDocuments(true);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
// Agregar el ID del pedimento
|
||||
formData.append('pedimento_id', id);
|
||||
|
||||
// Agregar archivos al FormData
|
||||
selectedFiles.forEach((file) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
|
||||
showMessage(`Subiendo ${selectedFiles.length} archivo(s)...`, 'info');
|
||||
|
||||
const result = await postFormDataWithAuth(`${API_URL}/record/documents/bulk-upload/`, formData);
|
||||
|
||||
showMessage(
|
||||
`${result.uploaded_count || selectedFiles.length} archivo(s) subido(s) exitosamente`,
|
||||
'success'
|
||||
);
|
||||
|
||||
// Limpiar archivos seleccionados y cerrar modal
|
||||
setSelectedFiles([]);
|
||||
setShowUploadModal(false);
|
||||
|
||||
// Forzar recarga de documentos
|
||||
const currentPage = page;
|
||||
setPage(0);
|
||||
setTimeout(() => setPage(currentPage), 100);
|
||||
} catch (error) {
|
||||
console.error('Error durante la subida:', error);
|
||||
showMessage(`Error durante la subida: ${error.message}`, 'error');
|
||||
} finally {
|
||||
setUploadingDocuments(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Efecto para actualizar isSelectAllDocs cuando cambia la selección
|
||||
useEffect(() => {
|
||||
if (documents.length > 0) {
|
||||
@@ -2192,45 +2247,58 @@ export default function PedimentoDetail() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{documents.length > 0 && (
|
||||
<div className="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
||||
<button
|
||||
onClick={downloadAll}
|
||||
disabled={downloadingAll}
|
||||
className="inline-flex items-center justify-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
|
||||
>
|
||||
{downloadingAll ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Descargando...</span>
|
||||
<span className="sm:hidden">Descargando...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-1 sm: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" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Descargar Todos</span>
|
||||
<span className="sm:hidden">Descargar</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className="inline-flex items-center justify-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1 sm:mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">{showFilters ? 'Ocultar Filtros' : 'Mostrar Filtros'}</span>
|
||||
<span className="sm:hidden">Filtros</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col sm:flex-row gap-2 sm:gap-3">
|
||||
<button
|
||||
onClick={() => setShowUploadModal(true)}
|
||||
className="inline-flex items-center justify-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1 sm:mr-2" 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" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Subir Documentos</span>
|
||||
<span className="sm:hidden">Subir</span>
|
||||
</button>
|
||||
|
||||
{documents.length > 0 && (
|
||||
<>
|
||||
<button
|
||||
onClick={downloadAll}
|
||||
disabled={downloadingAll}
|
||||
className="inline-flex items-center justify-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
|
||||
>
|
||||
{downloadingAll ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Descargando...</span>
|
||||
<span className="sm:hidden">Descargando...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-1 sm: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" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Descargar Todos</span>
|
||||
<span className="sm:hidden">Descargar</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowFilters(!showFilters)}
|
||||
className="inline-flex items-center justify-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg className="w-4 h-4 mr-1 sm:mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">{showFilters ? 'Ocultar Filtros' : 'Mostrar Filtros'}</span>
|
||||
<span className="sm:hidden">Filtros</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filtros expandibles */}
|
||||
@@ -4950,6 +5018,109 @@ export default function PedimentoDetail() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal para subir documentos */}
|
||||
{showUploadModal && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-2xl shadow-2xl max-w-lg w-full mx-4 transform transition-all duration-300 scale-100">
|
||||
{/* Header del modal */}
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-green-100 rounded-full p-3">
|
||||
<svg className="w-6 h-6 text-green-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" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Subir Documentos
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">Selecciona los archivos a subir</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contenido del modal */}
|
||||
<div className="px-6 py-4">
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Seleccionar archivos
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={handleFileSelect}
|
||||
className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
|
||||
{selectedFiles.length > 0 && (
|
||||
<div className="mt-3 p-3 bg-gray-50 rounded-lg">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Archivos seleccionados ({selectedFiles.length}):
|
||||
</p>
|
||||
<div className="max-h-32 overflow-y-auto">
|
||||
{selectedFiles.map((file, index) => (
|
||||
<div key={index} className="text-xs text-gray-600 py-1 border-b border-gray-200 last:border-b-0">
|
||||
{file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB)
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<svg className="w-5 h-5 text-blue-500 mt-0.5 flex-shrink-0" 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" />
|
||||
</svg>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-800">Información</p>
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
Los archivos se subirán al pedimento actual. Se aceptan múltiples formatos de archivo.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botones del modal */}
|
||||
<div className="px-6 py-4 border-t border-gray-200 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowUploadModal(false);
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
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"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
onClick={handleUploadDocuments}
|
||||
disabled={selectedFiles.length === 0 || uploadingDocuments}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-green-600 border border-transparent rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition-colors duration-200 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{uploadingDocuments ? (
|
||||
<>
|
||||
<svg className="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</svg>
|
||||
Subiendo...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4" 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" />
|
||||
</svg>
|
||||
Subir {selectedFiles.length} archivo{selectedFiles.length !== 1 ? 's' : ''}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal de confirmación para eliminación */}
|
||||
{showDeleteModal && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
|
||||
Reference in New Issue
Block a user