Add formula template export/import and unsaved-changes guard
- Export: GET /CompanySettings/ExportCustomItemTemplates downloads all company templates as an indented JSON backup (strips internal IDs/paths) - Import: POST /CompanySettings/ImportCustomItemTemplates restores from that file; runs full field + formula validation, skips name duplicates, returns per-item results (imported / skipped / errors) - Unsaved-changes guard: cfModal now intercepts backdrop/ESC/X when the form is dirty and prompts before discarding work - Export and Import buttons added to the Custom Formulas card header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
(function () {
|
||||
let cfFields = [];
|
||||
let cfEditing = false;
|
||||
let cfFormDirty = false;
|
||||
|
||||
// ── Load & Render ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -221,9 +222,11 @@
|
||||
document.getElementById('cfDiagramImg').src = `/CompanySettings/TemplateDiagram?templateId=${t.id}`;
|
||||
document.getElementById('cfDiagramPreview').style.display = '';
|
||||
}
|
||||
cfFormDirty = false;
|
||||
}
|
||||
|
||||
function cfResetForm() {
|
||||
cfFormDirty = false;
|
||||
document.getElementById('cfId').value = '0';
|
||||
document.getElementById('cfName').value = '';
|
||||
document.getElementById('cfDescription').value = '';
|
||||
@@ -528,6 +531,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
cfFormDirty = false;
|
||||
bootstrap.Modal.getInstance(document.getElementById('cfModal'))?.hide();
|
||||
cfLoadTemplates();
|
||||
} catch (e) {
|
||||
@@ -862,4 +866,103 @@
|
||||
cfWtStep = i;
|
||||
cfRenderWtStep();
|
||||
};
|
||||
|
||||
// ── Import ────────────────────────────────────────────────────────────────
|
||||
|
||||
window.cfShowImport = function () {
|
||||
document.getElementById('cfImportFile').value = '';
|
||||
document.getElementById('cfImportResults').classList.add('d-none');
|
||||
document.getElementById('cfImportSummary').innerHTML = '';
|
||||
const btn = document.getElementById('cfImportBtn');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-upload me-1"></i>Import';
|
||||
new bootstrap.Modal(document.getElementById('cfImportModal')).show();
|
||||
};
|
||||
|
||||
window.cfSubmitImport = async function () {
|
||||
const fileInput = document.getElementById('cfImportFile');
|
||||
if (!fileInput.files.length) {
|
||||
showCfError('Please select a .json export file first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('cfImportBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Importing…';
|
||||
|
||||
const form = new FormData();
|
||||
form.append('file', fileInput.files[0]);
|
||||
form.append('__RequestVerificationToken', getAntiForgeryToken());
|
||||
|
||||
try {
|
||||
const res = await fetch('/CompanySettings/ImportCustomItemTemplates', { method: 'POST', body: form });
|
||||
const data = await res.json();
|
||||
|
||||
const resultsEl = document.getElementById('cfImportResults');
|
||||
const summaryEl = document.getElementById('cfImportSummary');
|
||||
resultsEl.classList.remove('d-none');
|
||||
|
||||
if (!data.success) {
|
||||
summaryEl.innerHTML = `<div class="alert alert-danger alert-permanent mb-0"><i class="bi bi-x-circle me-2"></i>${escHtml(data.message)}</div>`;
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-upload me-1"></i>Import';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
if (data.imported > 0)
|
||||
html += `<div class="alert alert-success alert-permanent mb-2"><i class="bi bi-check-circle me-2"></i><strong>${data.imported}</strong> template${data.imported !== 1 ? 's' : ''} imported successfully.</div>`;
|
||||
|
||||
if (data.skipped > 0) {
|
||||
const names = (data.skippedNames || []).map(n => `<li>${escHtml(n)}</li>`).join('');
|
||||
html += `<div class="alert alert-warning alert-permanent mb-2">
|
||||
<i class="bi bi-skip-forward me-2"></i><strong>${data.skipped}</strong> skipped — name already exists:
|
||||
<ul class="mb-0 mt-1 small">${names}</ul>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (data.errors && data.errors.length) {
|
||||
const items = data.errors.map(e => `<li>${escHtml(e)}</li>`).join('');
|
||||
html += `<div class="alert alert-danger alert-permanent mb-2">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i><strong>${data.errors.length}</strong> error${data.errors.length !== 1 ? 's' : ''}:
|
||||
<ul class="mb-0 mt-1 small">${items}</ul>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (data.imported === 0 && data.skipped === 0 && (!data.errors || !data.errors.length))
|
||||
html = '<div class="alert alert-info alert-permanent mb-0"><i class="bi bi-info-circle me-2"></i>The file contained no templates to import.</div>';
|
||||
|
||||
summaryEl.innerHTML = html;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="bi bi-check me-1"></i>Done';
|
||||
|
||||
if (data.imported > 0) cfLoadTemplates();
|
||||
} catch (e) {
|
||||
showCfError('Import request failed: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-upload me-1"></i>Import';
|
||||
}
|
||||
};
|
||||
|
||||
// ── Unsaved-changes guard ─────────────────────────────────────────────────
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const modal = document.getElementById('cfModal');
|
||||
if (!modal) return;
|
||||
|
||||
// Any user interaction inside the modal marks the form dirty
|
||||
modal.addEventListener('input', function () { cfFormDirty = true; });
|
||||
modal.addEventListener('change', function () { cfFormDirty = true; });
|
||||
|
||||
// Intercept backdrop click, ESC, and the X button when there is unsaved work
|
||||
modal.addEventListener('hide.bs.modal', function (e) {
|
||||
if (!cfFormDirty) return;
|
||||
e.preventDefault();
|
||||
if (confirm('You have unsaved changes. Close anyway and lose your work?')) {
|
||||
cfFormDirty = false;
|
||||
bootstrap.Modal.getInstance(modal)?.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user