Files
2026-04-23 21:38:24 -04:00

205 lines
8.4 KiB
JavaScript

// CSV Bulk Import JavaScript
// Handles file upload with AJAX, progress indicators, and result display
document.addEventListener('DOMContentLoaded', function () {
setupCsvImportForm('csvImportCustomersForm', 'csvCustomersFile', 'csvImportCustomersBtn', '/Tools/CsvImportCustomers', 'csvCustomersResults');
setupCsvImportForm('csvImportCatalogForm', 'csvCatalogFile', 'csvImportCatalogBtn', '/Tools/CsvImportCatalogItems', 'csvCatalogResults');
setupCsvImportForm('csvImportInventoryForm', 'csvInventoryFile', 'csvImportInventoryBtn', '/Tools/CsvImportInventoryItems', 'csvInventoryResults');
setupCsvImportForm('csvImportQuotesForm', 'csvQuotesFile', 'csvImportQuotesBtn', '/Tools/CsvImportQuotes', 'csvQuotesResults');
setupCsvImportForm('csvImportJobsForm', 'csvJobsFile', 'csvImportJobsBtn', '/Tools/CsvImportJobs', 'csvJobsResults');
setupCsvImportForm('csvImportAppointmentsForm', 'csvAppointmentsFile', 'csvImportAppointmentsBtn', '/Tools/CsvImportAppointments', 'csvAppointmentsResults');
setupCsvImportForm('csvImportEquipmentForm', 'csvEquipmentFile', 'csvImportEquipmentBtn', '/Tools/CsvImportEquipment', 'csvEquipmentResults');
setupCsvImportForm('csvImportMaintenanceForm', 'csvMaintenanceFile', 'csvImportMaintenanceBtn', '/Tools/CsvImportMaintenance', 'csvMaintenanceResults');
setupCsvImportForm('csvImportSettingsForm', 'csvSettingsFile', 'csvImportSettingsBtn', '/Tools/CsvImportCompanySettings', 'csvSettingsResults');
setupCsvImportForm('csvImportVendorsForm', 'csvVendorsFile', 'csvImportVendorsBtn', '/Tools/CsvImportVendors', 'csvVendorsResults');
setupCsvImportForm('csvImportShopWorkersForm', 'csvShopWorkersFile', 'csvImportShopWorkersBtn', '/Tools/CsvImportShopWorkers', 'csvShopWorkersResults');
setupCsvImportForm('csvImportPrepServicesForm', 'csvPrepServicesFile', 'csvImportPrepServicesBtn', '/Tools/CsvImportPrepServices', 'csvPrepServicesResults');
});
function setupCsvImportForm(formId, fileInputId, submitBtnId, actionUrl, resultsId) {
const form = document.getElementById(formId);
if (!form) return;
form.addEventListener('submit', async function (e) {
e.preventDefault();
const fileInput = document.getElementById(fileInputId);
const submitBtn = document.getElementById(submitBtnId);
const resultsDiv = document.getElementById(resultsId);
const spinner = submitBtn.querySelector('.spinner-border');
// Validate file selection
if (!fileInput.files || fileInput.files.length === 0) {
showToast('Please select a CSV file to import.', 'error');
return;
}
const file = fileInput.files[0];
// Validate file extension
if (!file.name.toLowerCase().endsWith('.csv')) {
showToast('Please select a valid CSV file.', 'error');
return;
}
// Validate file size (max 10MB)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
showToast('File size must be less than 10MB.', 'error');
return;
}
// Show loading state
spinner.classList.remove('d-none');
submitBtn.disabled = true;
resultsDiv.classList.add('d-none');
resultsDiv.innerHTML = '';
// Prepare form data (use the form element so all fields, including account selects, are included)
const formData = new FormData(form);
// Get anti-forgery token
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
try {
const response = await fetch(actionUrl, {
method: 'POST',
body: formData,
headers: {
'RequestVerificationToken': token
}
});
const result = await response.json();
// Display results
displayImportResults(result, resultsDiv);
// Show toast notification
if (result.success) {
showToast(`Import completed: ${result.successCount} records imported successfully!`, 'success');
// Clear file input
fileInput.value = '';
} else {
showToast('Import completed with errors. Please review the details below.', 'error');
}
} catch (error) {
console.error('Import error:', error);
showToast('An error occurred during import: ' + error.message, 'error');
displayImportResults({ success: false, message: error.message, errors: [error.toString()] }, resultsDiv);
} finally {
// Hide loading state
spinner.classList.add('d-none');
submitBtn.disabled = false;
}
});
}
function displayImportResults(result, resultsDiv) {
resultsDiv.classList.remove('d-none');
const cardClass = result.success ? 'border-success' : 'border-danger';
const headerClass = result.success ? 'bg-success' : 'bg-danger';
const icon = result.success ? 'check-circle' : 'exclamation-triangle';
const skipped = result.skippedCount || 0;
const colSize = skipped > 0 ? 'col-md-3' : 'col-md-4';
let html = `
<div class="card ${cardClass}">
<div class="card-header ${headerClass} text-white">
<h6 class="mb-0"><i class="bi bi-${icon}"></i> Import Results</h6>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="${colSize}">
<div class="text-center">
<div class="display-6 text-success">${result.successCount || 0}</div>
<small class="text-muted">Imported</small>
</div>
</div>
${skipped > 0 ? `
<div class="${colSize}">
<div class="text-center">
<div class="display-6 text-warning">${skipped}</div>
<small class="text-muted">Skipped (already exist)</small>
</div>
</div>` : ''}
<div class="${colSize}">
<div class="text-center">
<div class="display-6 text-danger">${result.errorCount || 0}</div>
<small class="text-muted">Errors</small>
</div>
</div>
<div class="${colSize}">
<div class="text-center">
<div class="display-6 text-info">${result.totalRows || 0}</div>
<small class="text-muted">Total Rows</small>
</div>
</div>
</div>
<p class="mb-2"><strong>${result.message || 'Import completed.'}</strong></p>
`;
// Display errors
if (result.errors && result.errors.length > 0) {
html += `
<div class="alert alert-danger mt-3">
<h6><i class="bi bi-exclamation-triangle"></i> Errors:</h6>
<ul class="mb-0 small">
`;
result.errors.forEach(error => {
html += `<li>${escapeHtml(error)}</li>`;
});
html += `
</ul>
</div>
`;
}
// Display warnings
if (result.warnings && result.warnings.length > 0) {
html += `
<div class="alert alert-warning mt-3">
<h6><i class="bi bi-info-circle"></i> Warnings:</h6>
<ul class="mb-0 small">
`;
result.warnings.forEach(warning => {
html += `<li>${escapeHtml(warning)}</li>`;
});
html += `
</ul>
</div>
`;
}
html += `
</div>
</div>
`;
resultsDiv.innerHTML = html;
}
function showToast(message, type = 'success') {
const toastId = type === 'success' ? 'successToast' : 'errorToast';
const toastElement = document.getElementById(toastId);
if (!toastElement) return;
const toastBody = toastElement.querySelector('.toast-body');
toastBody.textContent = message;
const toast = new bootstrap.Toast(toastElement, {
autohide: true,
delay: 5000
});
toast.show();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}