205 lines
8.4 KiB
JavaScript
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;
|
|
}
|