feat(admin): add mapping workflow for unknown CSVs
- New POST /admin/map-files to reclassify unknown files to a chosen import type - Centralize VALID_IMPORT_TYPES and pass to admin template - UI: dropdown + 'Map Selected' button in Unknown card - JS: mapSelectedFiles() posts selection and reloads on success - Keeps UUID suffix, prevents traversal, logs actions
This commit is contained in:
@@ -463,6 +463,21 @@
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if import_type == 'unknown' and valid_import_types %}
|
||||
<div class="mb-3 d-flex align-items-end gap-2">
|
||||
<div>
|
||||
<label class="form-label mb-1">Map selected to:</label>
|
||||
<select class="form-select form-select-sm" id="mapTypeSelect-{{ loop.index }}">
|
||||
{% for t in valid_import_types %}
|
||||
<option value="{{ t }}">{{ t.title().replace('_', ' ') }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-warning" onclick="mapSelectedFiles(this, '{{ import_type }}')">
|
||||
<i class="bi bi-tags"></i> Map Selected
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form action="/admin/import/{{ import_type }}" method="post">
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
@@ -783,5 +798,48 @@ async function deleteFile(filename, event) {
|
||||
alert(`Error deleting file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Map selected unknown files to a chosen import type
|
||||
async function mapSelectedFiles(buttonEl, importType) {
|
||||
// Find the surrounding card and form
|
||||
const cardBody = buttonEl.closest('.card-body');
|
||||
const form = cardBody.querySelector('form');
|
||||
const selectEl = cardBody.querySelector('select.form-select');
|
||||
if (!form || !selectEl) return;
|
||||
|
||||
// Collect selected filenames
|
||||
const checked = Array.from(form.querySelectorAll('.file-checkbox:checked'))
|
||||
.map(cb => cb.value);
|
||||
if (checked.length === 0) {
|
||||
alert('Select at least one file to map.');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetType = selectEl.value;
|
||||
if (!targetType) {
|
||||
alert('Choose a target type.');
|
||||
return;
|
||||
}
|
||||
|
||||
buttonEl.disabled = true;
|
||||
try {
|
||||
const resp = await fetch('/admin/map-files', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ target_type: targetType, filenames: checked })
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Mapping failed');
|
||||
}
|
||||
// Refresh UI
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert(`Mapping failed: ${e.message}`);
|
||||
} finally {
|
||||
buttonEl.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user