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:
HotSwapp
2025-10-08 13:22:34 -05:00
parent dc1c10f44b
commit c23e8d0b8a
2 changed files with 163 additions and 13 deletions

View File

@@ -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 %}