FIX: Simplificar input de archivos para asegurar subida cross-browser
This commit is contained in:
@@ -129,23 +129,23 @@
|
|||||||
<div class="form-group" style="margin-bottom: 1.5rem; background: #f8fafc; padding: 1.5rem; border-radius: 6px; border: 1px solid var(--efintel-border);">
|
<div class="form-group" style="margin-bottom: 1.5rem; background: #f8fafc; padding: 1.5rem; border-radius: 6px; border: 1px solid var(--efintel-border);">
|
||||||
<label style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-petroleum); display:flex; align-items:center; gap:0.3rem; margin-bottom: 0.75rem;"><i data-lucide="paperclip" style="width:14px;"></i> Evidencias Adjuntas (Boletas / Facturas)</label>
|
<label style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-petroleum); display:flex; align-items:center; gap:0.3rem; margin-bottom: 0.75rem;"><i data-lucide="paperclip" style="width:14px;"></i> Evidencias Adjuntas (Boletas / Facturas)</label>
|
||||||
|
|
||||||
<!-- Drag and Drop Zone Container -->
|
|
||||||
<div id="drop-zone" style="border: 2px dashed var(--efintel-blue); border-radius: 6px; padding: 2rem; text-align: center; background: white; cursor: pointer; transition: all 0.3s; margin-bottom: 0.75rem; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
|
||||||
<i data-lucide="upload-cloud" style="width: 36px; height: 36px; color: var(--efintel-blue); margin-bottom: 0.5rem; transition: transform 0.3s;"></i>
|
|
||||||
<p style="margin: 0; font-size: 0.85rem; font-weight: 600; color: var(--efintel-petroleum);">Arrastre sus boletas/facturas aquí o haga clic para buscar</p>
|
|
||||||
<span style="font-size: 0.7rem; color: var(--efintel-gray); display: block; margin-top: 0.25rem;">Formatos aceptados: PDF, PNG, JPG, JPEG (Múltiple)</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hidden native input (Visually hidden for Safari compatibility) -->
|
<!-- Simple & Reliable File Upload -->
|
||||||
<div style="width: 0; height: 0; overflow: hidden; opacity: 0; position: absolute; z-index: -1;">
|
<div style="margin-bottom: 0.75rem;">
|
||||||
|
<label for="id_receipt" style="display:block; font-size: 0.85rem; font-weight: 600; color: var(--efintel-petroleum); margin-bottom: 0.5rem;">
|
||||||
|
Adjuntar Evidencia(s) (Boleta / Factura)
|
||||||
|
</label>
|
||||||
|
<div style="border: 2px dashed var(--efintel-blue); border-radius: 6px; padding: 1.5rem; background: #f8fafc;">
|
||||||
{{ form.receipt }}
|
{{ form.receipt }}
|
||||||
|
<div style="margin-top: 0.5rem; font-size: 0.7rem; color: var(--efintel-gray);">
|
||||||
|
Formatos aceptados: PDF, PNG, JPG, JPEG. Puede seleccionar múltiples archivos manteniendo presionada la tecla Ctrl (o Cmd en Mac).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Live Queue files list -->
|
|
||||||
<div id="new-files-list" style="margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;"></div>
|
|
||||||
|
|
||||||
{% if form.receipt.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.receipt.errors }}</div>{% endif %}
|
{% if form.receipt.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.receipt.errors }}</div>{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if form.instance.pk and form.instance.documents.all %}
|
{% if form.instance.pk and form.instance.documents.all %}
|
||||||
<div style="margin-top: 1.25rem; border-top: 1px solid #e2e8f0; padding-top: 0.75rem;">
|
<div style="margin-top: 1.25rem; border-top: 1px solid #e2e8f0; padding-top: 0.75rem;">
|
||||||
<strong style="font-size: 0.8rem; color: var(--efintel-petroleum); display: block; margin-bottom: 0.5rem;">Documentos Adjuntos Actuales:</strong>
|
<strong style="font-size: 0.8rem; color: var(--efintel-petroleum); display: block; margin-bottom: 0.5rem;">Documentos Adjuntos Actuales:</strong>
|
||||||
@@ -612,186 +612,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client-side Upload File Deletion Queue using DataTransfer + Drag and Drop
|
|
||||||
const fileInput = document.getElementById('id_receipt');
|
|
||||||
const newFilesList = document.getElementById('new-files-list');
|
|
||||||
const dropZone = document.getElementById('drop-zone');
|
|
||||||
|
|
||||||
if (fileInput && newFilesList && dropZone) {
|
|
||||||
let dt = new DataTransfer();
|
|
||||||
|
|
||||||
// Clicking the drop zone triggers the hidden file input
|
|
||||||
dropZone.addEventListener('click', () => {
|
|
||||||
fileInput.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Highlight drop zone on drag hover
|
|
||||||
['dragenter', 'dragover'].forEach(eventName => {
|
|
||||||
dropZone.addEventListener(eventName, (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
dropZone.style.border = '2px dashed var(--efintel-blue)';
|
|
||||||
dropZone.style.background = 'var(--efintel-light)';
|
|
||||||
const icon = dropZone.querySelector('i');
|
|
||||||
if (icon) icon.style.transform = 'scale(1.2)';
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
['dragleave', 'drop'].forEach(eventName => {
|
|
||||||
dropZone.addEventListener(eventName, (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
dropZone.style.border = '2px dashed var(--efintel-blue)';
|
|
||||||
dropZone.style.background = 'white';
|
|
||||||
const icon = dropZone.querySelector('i');
|
|
||||||
if (icon) icon.style.transform = 'scale(1)';
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle dropped files
|
|
||||||
dropZone.addEventListener('drop', (e) => {
|
|
||||||
const files = e.dataTransfer.files;
|
|
||||||
if (files.length > 0) {
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const f = files[i];
|
|
||||||
// Validate file type before putting in queue (extensions: pdf, png, jpg, jpeg)
|
|
||||||
const ext = f.name.split('.').pop().toLowerCase();
|
|
||||||
if (['pdf', 'png', 'jpg', 'jpeg'].includes(ext)) {
|
|
||||||
dt.items.add(f);
|
|
||||||
} else {
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Archivo no permitido',
|
|
||||||
text: `El archivo "${f.name}" no es una evidencia válida (PDF, PNG, JPG, JPEG).`,
|
|
||||||
confirmButtonColor: 'var(--efintel-blue)'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileInput.files = dt.files;
|
|
||||||
renderNewFiles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle standard file selection
|
|
||||||
fileInput.addEventListener('change', () => {
|
|
||||||
let hasError = false;
|
|
||||||
for (let i = 0; i < fileInput.files.length; i++) {
|
|
||||||
const f = fileInput.files[i];
|
|
||||||
const ext = f.name.split('.').pop().toLowerCase();
|
|
||||||
if (['pdf', 'png', 'jpg', 'jpeg'].includes(ext)) {
|
|
||||||
dt.items.add(f);
|
|
||||||
} else {
|
|
||||||
hasError = true;
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Archivo no permitido',
|
|
||||||
text: `El archivo "${f.name}" no es una evidencia válida (PDF, PNG, JPG, JPEG).`,
|
|
||||||
confirmButtonColor: 'var(--efintel-blue)'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileInput.files = dt.files;
|
|
||||||
renderNewFiles();
|
|
||||||
|
|
||||||
// Si hubo error, resetear el input nativo temporalmente para evitar que se envíe el archivo inválido
|
|
||||||
if (hasError && dt.files.length === 0) {
|
|
||||||
fileInput.value = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderNewFiles() {
|
|
||||||
newFilesList.innerHTML = '';
|
|
||||||
|
|
||||||
if (dt.files.length > 0) {
|
|
||||||
const header = document.createElement('strong');
|
|
||||||
header.style.fontSize = '0.75rem';
|
|
||||||
header.style.color = 'var(--efintel-blue)';
|
|
||||||
header.style.display = 'block';
|
|
||||||
header.style.marginBottom = '0.35rem';
|
|
||||||
header.textContent = 'Archivos en cola para subir:';
|
|
||||||
newFilesList.appendChild(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.from(dt.files).forEach((file, index) => {
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.style.display = 'flex';
|
|
||||||
row.style.justifyContent = 'space-between';
|
|
||||||
row.style.alignItems = 'center';
|
|
||||||
row.style.background = 'var(--efintel-light)';
|
|
||||||
row.style.padding = '0.45rem 0.75rem';
|
|
||||||
row.style.borderRadius = '4px';
|
|
||||||
row.style.fontSize = '0.75rem';
|
|
||||||
row.style.border = '1px solid var(--efintel-border)';
|
|
||||||
row.style.marginBottom = '0.3rem';
|
|
||||||
|
|
||||||
// File Detail Info with preview
|
|
||||||
const infoWrapper = document.createElement('div');
|
|
||||||
infoWrapper.style.display = 'flex';
|
|
||||||
infoWrapper.style.alignItems = 'center';
|
|
||||||
infoWrapper.style.gap = '0.5rem';
|
|
||||||
|
|
||||||
// Check file extension
|
|
||||||
const ext = file.name.split('.').pop().toLowerCase();
|
|
||||||
if (['png', 'jpg', 'jpeg'].includes(ext)) {
|
|
||||||
// Render image thumbnail
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = URL.createObjectURL(file);
|
|
||||||
img.style.width = '24px';
|
|
||||||
img.style.height = '24px';
|
|
||||||
img.style.objectFit = 'cover';
|
|
||||||
img.style.borderRadius = '3px';
|
|
||||||
img.style.border = '1px solid #cbd5e1';
|
|
||||||
img.onload = () => URL.revokeObjectURL(img.src); // Free memory
|
|
||||||
infoWrapper.appendChild(img);
|
|
||||||
} else if (ext === 'pdf') {
|
|
||||||
// Render PDF file icon
|
|
||||||
const pdfIcon = document.createElement('span');
|
|
||||||
pdfIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`;
|
|
||||||
infoWrapper.appendChild(pdfIcon);
|
|
||||||
} else {
|
|
||||||
const defaultIcon = document.createElement('span');
|
|
||||||
defaultIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--efintel-blue)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>`;
|
|
||||||
infoWrapper.appendChild(defaultIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameSpan = document.createElement('span');
|
|
||||||
nameSpan.style.color = 'var(--efintel-petroleum)';
|
|
||||||
nameSpan.style.fontWeight = '600';
|
|
||||||
nameSpan.textContent = `${file.name} (${(file.size / 1024).toFixed(1)} KB)`;
|
|
||||||
infoWrapper.appendChild(nameSpan);
|
|
||||||
row.appendChild(infoWrapper);
|
|
||||||
|
|
||||||
const deleteBtn = document.createElement('button');
|
|
||||||
deleteBtn.type = 'button';
|
|
||||||
deleteBtn.style.background = 'none';
|
|
||||||
deleteBtn.style.border = 'none';
|
|
||||||
deleteBtn.style.color = 'var(--efintel-danger)';
|
|
||||||
deleteBtn.style.cursor = 'pointer';
|
|
||||||
deleteBtn.style.padding = '0.2rem';
|
|
||||||
deleteBtn.style.display = 'flex';
|
|
||||||
deleteBtn.style.alignItems = 'center';
|
|
||||||
deleteBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
|
|
||||||
|
|
||||||
deleteBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const newDt = new DataTransfer();
|
|
||||||
for (let i = 0; i < dt.files.length; i++) {
|
|
||||||
if (i !== index) {
|
|
||||||
newDt.items.add(dt.files[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dt = newDt;
|
|
||||||
fileInput.files = dt.files;
|
|
||||||
renderNewFiles();
|
|
||||||
});
|
|
||||||
row.appendChild(deleteBtn);
|
|
||||||
|
|
||||||
newFilesList.appendChild(row);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle logic for unregistered vendor inputs
|
|
||||||
const toggleNewVendor = document.getElementById('toggle-new-vendor');
|
const toggleNewVendor = document.getElementById('toggle-new-vendor');
|
||||||
const newVendorFields = document.getElementById('new-vendor-fields');
|
const newVendorFields = document.getElementById('new-vendor-fields');
|
||||||
const originalVendorSelect = document.getElementById('id_vendor');
|
const originalVendorSelect = document.getElementById('id_vendor');
|
||||||
|
|||||||
Reference in New Issue
Block a user