@ -1,11 +1,8 @@
{{define "document_upload"}}
{{define "document_upload"}}
< h2 > Document Uploads< / h2 >
< h2 > Document Uploads< / h2 >
< form id = "upload-form" hx-post = "/upload-documents" hx-encoding = "multipart/form-data" hx-target = "#upload-results"
< div class = "upload-container" >
hx-indicator=".upload-overlay">
<!-- Upload overlay - moved outside the form -->
< div class = "upload-container" >
<!-- Upload overlay -->
< div class = "upload-overlay htmx-indicator" >
< div class = "upload-overlay htmx-indicator" >
< div class = "upload-overlay-content" >
< div class = "upload-overlay-content" >
< div class = "overlay-spinner" > < / div >
< div class = "overlay-spinner" > < / div >
@ -69,18 +66,29 @@
< / button >
< / button >
< / div >
< / div >
<!-- Step 3: Submit -->
<!-- Step 3: Submit - form moved here -->
< div id = "step3" class = "content" style = "display: none;" >
< div id = "step3" class = "content" style = "display: none;" >
< h3 class = "submenu-header" > Step 3: Submit Uploads< / h3 >
< h3 class = "submenu-header" > Step 3: Submit Uploads< / h3 >
< div >
< div >
< form id = "upload-form" hx-post = "/upload-documents" hx-encoding = "multipart/form-data"
hx-include="[name='documentFiles']" hx-target="#upload-results" hx-indicator=".upload-overlay">
< input type = "hidden" name = "job-ids" id = "job-ids-field" >
< button type = "submit" class = "success-button" id = "final-submit-button" > Upload Documents to Jobs< / button >
< button type = "submit" class = "success-button" id = "final-submit-button" > Upload Documents to Jobs< / button >
< / form >
< div id = "upload-loading-indicator" class = "htmx-indicator" >
< div id = "upload-loading-indicator" class = "htmx-indicator" >
< span > Uploading...< / span >
< span > Uploading...< / span >
< div class = "loading-indicator" > < / div >
< div class = "loading-indicator" > < / div >
< / div >
< / div >
< / div >
< / div >
< div id = "upload-results" class = "upload-results" > < / div >
<!-- Step 4: Results - moved outside the form -->
< div id = "step4" class = "content" style = "display: none;" >
< h3 class = "submenu-header" > Step 4: Upload Results< / h3 >
< div id = "upload-results" class = "upload-results" >
<!-- Results will appear here after uploading documents -->
< / div >
< / div >
< / div >
< / div >
@ -89,8 +97,7 @@
< h3 class = "submenu-header" > Upload Complete< / h3 >
< h3 class = "submenu-header" > Upload Complete< / h3 >
< button type = "button" class = "btn-primary" hx-on:click = "restartUpload()" > Start New Upload< / button >
< button type = "button" class = "btn-primary" hx-on:click = "restartUpload()" > Start New Upload< / button >
< / div >
< / div >
< / div >
< / div >
< / form >
<!-- Edit File Modal (Initially Hidden) -->
<!-- Edit File Modal (Initially Hidden) -->
< div id = "editFileModal" class = "modal" style = "display:none;" >
< div id = "editFileModal" class = "modal" style = "display:none;" >
@ -200,39 +207,40 @@
const icon = getFileIcon(fileMetadata.displayName);
const icon = getFileIcon(fileMetadata.displayName);
const truncatedName = truncateFilename(fileMetadata.displayName);
const truncatedName = truncateFilename(fileMetadata.displayName);
// Get document type text
const docTypeSelect = document.getElementById('editDocumentType'); // Use the modal's select for options
let docTypeText = '';
if (docTypeSelect) {
const selectedOption = Array.from(docTypeSelect.options).find(opt => opt.value === fileMetadata.documentType);
if (selectedOption) {
docTypeText = selectedOption.text;
}
}
chip.innerHTML = `
chip.innerHTML = `
< span class = "file-chip-icon" > ${icon}< / span >
< span class = "file-chip-icon" > ${icon}< / span >
< span class = "file-chip-name" title = "${fileMetadata.displayName}" onclick = "openEditModal(${index})" > ${truncatedName}< / span >
< span class = "file-chip-name" title = "${fileMetadata.displayName}" > ${truncatedName}< / span >
< span class = "file-chip-doctype" > ${docTypeText }< / span >
< span class = "file-chip-doctype" > Type: ${fileMetadata.documentType}< / span >
< button type = "button" class = "file-chip-edit" onclick = "openEditModal(${index})" > ✏️< / button >
< button type = "button" class = "file-chip-edit" onclick = "openEditModal(${index})" title = "Edit file details" > ✏️< / button >
< button type = "button" class = "file-chip-remove" onclick = "removeFileChip(${index})" > × < / button >
< button type = "button" class = "file-chip-remove" onclick = "toggleFileActive(${index})" title = "${fileMetadata.isActive ? 'Remove from upload' : 'Add back to upload'}" > ${fileMetadata.isActive ? '❌' : '➕'}< / button >
`;
`;
filesArea.appendChild(chip);
filesArea.appendChild(chip);
}
}
function openEditModal(index) {
function toggleFileActive(index) {
const fileMetadata = selectedFilesData[index];
if (index >= 0 & & index < selectedFilesData.length ) {
if (!fileMetadata || !fileMetadata.isActive) return; // Don't edit removed or non-existent files
selectedFilesData[index].isActive = !selectedFilesData[index].isActive;
const chip = document.querySelector(`[data-index="${index}"]`);
if (chip) {
chip.className = `file-chip ${selectedFilesData[index].isActive ? '' : 'removed'}`;
const removeBtn = chip.querySelector('.file-chip-remove');
if (removeBtn) {
removeBtn.innerHTML = selectedFilesData[index].isActive ? '❌' : '➕';
removeBtn.title = selectedFilesData[index].isActive ? 'Remove from upload' : 'Add back to upload';
}
}
}
}
function openEditModal(index) {
if (index >= 0 & & index < selectedFilesData.length ) {
const fileData = selectedFilesData[index];
document.getElementById('editFileOriginalIndex').value = index;
document.getElementById('editFileOriginalIndex').value = index;
document.getElementById('editDisplayName').value = fileMetadata.displayName;
document.getElementById('editDisplayName').value = fileData.displayName;
document.getElementById('editDocumentType').value = fileMetadata.documentType;
document.getElementById('editDocumentType').value = fileData.documentType;
document.getElementById('editFileModal').style.display = 'block';
// Later: Add preview logic here if possible for fileMetadata.originalFile
}
const previewArea = document.getElementById('modal-preview-area');
previewArea.innerHTML = '< p > Document preview will be shown here in a future update.< / p > '; // Placeholder
document.getElementById('editFileModal').style.display = 'flex';
}
}
function closeEditModal() {
function closeEditModal() {
@ -241,99 +249,104 @@
function saveFileChanges() {
function saveFileChanges() {
const index = parseInt(document.getElementById('editFileOriginalIndex').value);
const index = parseInt(document.getElementById('editFileOriginalIndex').value);
const newDisplayName = document.getElementById('editDisplayName').value;
if (index >= 0 & & index < selectedFilesData.length ) {
const newDocumentType = document.getElementById('editDocumentType').value;
selectedFilesData[index].displayName = document.getElementById('editDisplayName').value;
selectedFilesData[index].documentType = document.getElementById('editDocumentType').value;
if (selectedFilesData[index]) {
selectedFilesData[index].displayName = newDisplayName;
selectedFilesData[index].documentType = newDocumentType;
// Re-render the chip's display name, icon, and doc type
// Re-render the chip
const chipElement = document.querySelector(`.file-chip[data-index="${index}"]`);
const chip = document.querySelector(`[data-index="${index}"]`);
if (chipElement) {
if (chip) {
const truncatedName = truncateFilename(newDisplayName);
const icon = getFileIcon(selectedFilesData[index].displayName);
chipElement.querySelector('.file-chip-name').textContent = truncatedName;
const truncatedName = truncateFilename(selectedFilesData[index].displayName);
chipElement.querySelector('.file-chip-name').title = newDisplayName;
chipElement.querySelector('.file-chip-icon').textContent = getFileIcon(newDisplayName);
// Update doc type text on chip
chip.innerHTML = `
const docTypeSelect = document.getElementById('editDocumentType');
< span class = "file-chip-icon" > ${icon}< / span >
let docTypeText = '';
< span class = "file-chip-name" title = "${selectedFilesData[index].displayName}" > ${truncatedName}< / span >
if (docTypeSelect) {
< span class = "file-chip-doctype" > Type: ${selectedFilesData[index].documentType}< / span >
const selectedOption = Array.from(docTypeSelect.options).find(opt => opt.value === newDocumentType);
< button type = "button" class = "file-chip-edit" onclick = "openEditModal(${index})" title = "Edit file details" > ✏️< / button >
if (selectedOption) {
< button type = "button" class = "file-chip-remove" onclick = "toggleFileActive(${index})" title = "${selectedFilesData[index].isActive ? 'Remove from upload' : 'Add back to upload'}" > ${selectedFilesData[index].isActive ? '❌' : '➕'}< / button >
docTypeText = selectedOption.text;
`;
}
}
chipElement.querySelector('.file-chip-doctype').textContent = docTypeText;
}
}
}
}
closeEditModal();
closeEditModal();
}
}
function removeFileChip(index) {
if (selectedFilesData[index]) {
selectedFilesData[index].isActive = false;
const chipElement = document.querySelector(`.file-chip[data-index="${index}"]`);
if (chipElement) {
chipElement.classList.add('removed');
// Optionally disable edit/remove buttons on a "removed" chip
chipElement.querySelector('.file-chip-edit').disabled = true;
}
// Check if all files are removed to disable "Continue" button
const allRemoved = selectedFilesData.every(f => !f.isActive);
continueToStep3Button.disabled = allRemoved || selectedFilesData.length === 0;
}
}
// Function to restart the upload process
// Function to restart the upload process
function restartUpload() {
function restartUpload() {
// Reset the form and file input
// Hide all sections except step 1
const form = document.getElementById('upload-form');
document.getElementById('step2').style.display = 'none';
form.reset();
document.getElementById('step3').style.display = 'none';
const fileInput = document.getElementById('document-files');
document.getElementById('step4').style.display = 'none';
if (fileInput) fileInput.value = '';
document.getElementById('restart-section').style.display = 'none';
// Remove any hidden jobNumbers inputs
form.querySelectorAll('input[name="jobNumbers"]').forEach(el => el.remove());
// Clear and hide the job IDs container
// Clear results
const jobIdsContainer = document.getElementById('job-ids-container');
document.getElementById('upload-results').innerHTML = '';
jobIdsContainer.innerHTML = '';
jobIdsContainer.style.display = 'none';
// Clear and hide the CSV preview
// Reset CSV preview if it exists
const csvPreview = document.getElementById('csv-preview');
const csvPreview = document.getElementById('csv-preview');
if (csvPreview) csvPreview.style.display = 'none';
if (csvPreview) {
const csvContent = document.getElementById('csv-preview-content');
csvPreview.style.display = 'none';
if (csvContent) csvContent.innerHTML = '< p > No jobs loaded yet< / p > ';
}
// Clear selected files data and UI
selectedFilesData = [];
const filesArea = document.getElementById('selected-files-area');
filesArea.innerHTML = '< p id = "no-files-selected-placeholder" > No files selected yet< / p > ';
// Clear upload results
const csvPreviewContent = document.getElementById('csv-preview-content');
const uploadResults = document.getElementById('upload-results');
if (csvPreviewContent) {
if (uploadResults) uploadResults.innerHTML = '';
csvPreviewContent.innerHTML = '< p > No jobs loaded yet< / p > ';
}
// Hide steps 2, 3, and restart section
// Reset job IDs container
document.getElementById('step2').style.display = 'none';
document.getElementById('job-ids-container').innerHTML = '';
document.getElementById('step3').style.display = 'none';
document.getElementById('restart-section').style.display = 'none';
// Show step 1
// Show step 1
document.getElementById('step1').style.display = 'block';
document.getElementById('step1').style.display = 'block';
// Disable the continue-to-step3 button
// Reset any file inputs
if (continueToStep3Button) continueToStep3Button.disabled = true;
const fileInput = document.getElementById('csv-file');
if (fileInput) {
fileInput.value = '';
}
const documentFilesInput = document.getElementById('document-files');
if (documentFilesInput) {
documentFilesInput.value = '';
}
// Reset selected files
selectedFilesData = [];
const filesArea = document.getElementById('selected-files-area');
if (filesArea) {
filesArea.innerHTML = '< p id = "no-files-selected-placeholder" > No files selected yet.< / p > ';
}
// Reset continue button
if (continueToStep3Button) {
continueToStep3Button.disabled = true;
}
}
// Add event listeners for the upload overlay
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('form').forEach(form => {
form.addEventListener('htmx:beforeRequest', function (evt) {
if (evt.detail.pathInfo.requestPath.includes('/upload-documents')) {
document.querySelector('.upload-overlay').style.display = 'flex';
}
}
});
form.addEventListener('htmx:afterRequest', function (evt) {
if (evt.detail.pathInfo.requestPath.includes('/upload-documents')) {
document.querySelector('.upload-overlay').style.display = 'none';
// Show restart section after successful upload
// If it's an upload action and successful, show step 4 and restart section
document.getElementById('upload-form').addEventListener('htmx:afterRequest', function (evt) {
if (evt.detail.successful ) {
if (evt.detail.pathInfo.requestPath === '/upload-documents' & & evt.detail.successful) {
document.getElementById('step4').style.display = 'block';
document.getElementById('restart-section').style.display = 'block';
document.getElementById('restart-section').style.display = 'block';
}
}
}
});
form.addEventListener('htmx:error', function (evt) {
document.querySelector('.upload-overlay').style.display = 'none';
});
});
});
// Prepare metadata for backend on form submission
// Prepare metadata for backend on form submission
@ -370,8 +383,16 @@
evt.detail.parameters['file_document_types'] = JSON.stringify(documentTypesArr);
evt.detail.parameters['file_document_types'] = JSON.stringify(documentTypesArr);
evt.detail.parameters['file_is_active_flags'] = JSON.stringify(isActiveArr);
evt.detail.parameters['file_is_active_flags'] = JSON.stringify(isActiveArr);
// Set the job numbers from the hidden input created by CSV processing
const jobIdsInput = document.querySelector('input[name="jobNumbers"]');
if (jobIdsInput) {
evt.detail.parameters['jobNumbers'] = jobIdsInput.value;
} else {
console.error('No jobNumbers input found. Make sure CSV was processed first.');
}
// 'documentFiles' will be sent by the browser from the < input type = "file" name = "documentFiles" >
// 'documentFiles' will be sent by the browser from the < input type = "file" name = "documentFiles" >
// 'jobNumbers' will be sent from the hidden input populated by the CSV step.
});
});
});
< / script >
< / script >
{{end}}
{{end}}