/** * Import-mapping modal controller. * * openImportMappingModal({ importId, filename, fc, onResult }) * * Populates the modal from the parsed FeatureCollection, lets the user pick * a target type and adjust the field map, then calls onResult with one of: * * { action: 'cancel' } — keep as Other / view only * { action: 'save', targetType, mapping } * { action: 'upload', targetType, mapping } * * The caller is responsible for updating external_imports + the staged * features (this module knows nothing about the DB or the map). * * See LUPMIS2_Import_Upload_Design.docx §3.1. */ import { Modal } from 'bootstrap'; import { TARGET_TYPES, TARGET_FIELDS, detectTargetType, autoMapFields, listSourceFields, sampleSourceValues, } from './import-detect.js'; const els = {}; // cached DOM lookups let modal = null; let state = null; // { importId, filename, fc, sourceFields, mapping, targetType, onResult } function cacheEls() { if (els.root) return; els.root = document.getElementById('importMappingModal'); els.filename = document.getElementById('import-modal-filename'); els.summary = document.getElementById('import-modal-summary'); els.target = document.getElementById('import-modal-target'); els.targetHint = document.getElementById('import-modal-target-hint'); els.fieldsWrap = document.getElementById('import-modal-fields-wrap'); els.tbody = document.getElementById('import-modal-fields-tbody'); els.btnSave = document.getElementById('import-modal-save'); els.btnSaveUpload = document.getElementById('import-modal-save-upload'); els.btnCancel = document.getElementById('import-modal-cancel'); // Populate the target-type dropdown once. if (els.target && !els.target.dataset.populated) { els.target.innerHTML = TARGET_TYPES .map((t) => ``) .join(''); els.target.dataset.populated = '1'; } // Event wiring (idempotent). if (els.target && !els.target.dataset.wired) { els.target.dataset.wired = '1'; els.target.addEventListener('change', onTargetChange); } if (els.btnSave && !els.btnSave.dataset.wired) { els.btnSave.dataset.wired = '1'; els.btnSave.addEventListener('click', () => finish('save')); } if (els.btnSaveUpload && !els.btnSaveUpload.dataset.wired) { els.btnSaveUpload.dataset.wired = '1'; els.btnSaveUpload.addEventListener('click', () => finish('upload')); } // Cancel uses Bootstrap's data-bs-dismiss; we hook the hidden event so // closing via × / ESC / Cancel all behave the same: a 'cancel' result. if (els.root && !els.root.dataset.wired) { els.root.dataset.wired = '1'; els.root.addEventListener('hidden.bs.modal', () => { if (state?.onResult && !state._resolved) { state._resolved = true; state.onResult({ action: 'cancel' }); } state = null; }); } } /** Render the field-mapping table for the current targetType. */ function renderFieldsTable() { const targetType = state.targetType; const columns = TARGET_FIELDS[targetType] || []; // "Other (view only)" → no fields to map. if (targetType === 'other' || columns.length === 0) { els.fieldsWrap.style.display = 'none'; return; } els.fieldsWrap.style.display = ''; // Each dropdown option shows the source field name AND a one-feature // sample value so the user can recognise the attribute by its content, // not just its name. Sample is computed once in state.sourceSamples; see // sampleSourceValues() in import-detect.js. // // The field name is rendered with Unicode mathematical-bold characters // (U+1D400 / U+1D41A / U+1D7CE blocks) because HTML / CSS bold doesn't // render inside '] .concat(state.sourceFields.map((s) => ``)) .join(''); els.tbody.innerHTML = columns.map((col) => { const current = state.mapping[col] || ''; // Mark the matching option as selected. The opening `