Initial commit
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user