Add CSV upload for references

This commit is contained in:
Ian Keane 2026-06-12 10:15:43 -04:00
parent b426020a76
commit 46f2a303dc
2 changed files with 143 additions and 1 deletions

111
app.js
View file

@ -1557,6 +1557,117 @@ async function importCSVRows(rows) {
return results;
}
// ── References CSV upload ──────────────────────────────────
const refsCsvFileInput = $('refs-csv-file-input');
$('btn-upload-refs-csv').addEventListener('click', () => {
bulkEditPopover.hidden = true;
openUploadRefsCsvModal();
});
refsCsvFileInput.addEventListener('change', () => {
$('btn-refs-csv-import').disabled = !refsCsvFileInput.files.length;
$('refs-csv-results').hidden = true;
$('refs-csv-results').innerHTML = '';
});
$('btn-refs-csv-cancel').addEventListener('click', () => $('upload-refs-csv-modal').close());
$('btn-refs-csv-import').addEventListener('click', async () => {
const file = refsCsvFileInput.files[0];
if (!file) return;
$('btn-refs-csv-import').disabled = true;
$('btn-refs-csv-cancel').textContent = 'Close';
const resultsEl = $('refs-csv-results');
resultsEl.hidden = false;
resultsEl.innerHTML = '<p class="csv-status">Importing…</p>';
try {
const text = await file.text();
const rows = parseCSV(text);
if (!rows.length) { resultsEl.innerHTML = '<p class="csv-error">No data rows found.</p>'; return; }
const results = await importRefsCsvRows(rows);
await loadAll();
let html = `<p class="csv-ok">✓ ${results.created} reference${results.created !== 1 ? 's' : ''} imported.</p>`;
if (results.warnings.length) {
html += `<p class="csv-error">${results.warnings.length} skipped — tune not found:</p><ul class="csv-errors">`;
results.warnings.forEach(w => { html += `<li>${esc(w)}</li>`; });
html += '</ul>';
}
if (results.errors.length) {
html += `<p class="csv-error">${results.errors.length} error${results.errors.length !== 1 ? 's' : ''}:</p><ul class="csv-errors">`;
results.errors.forEach(e => { html += `<li>${esc(e.name)}: ${esc(e.error)}</li>`; });
html += '</ul>';
}
resultsEl.innerHTML = html;
} catch (e) {
resultsEl.innerHTML = `<p class="csv-error">Failed: ${esc(e.message)}</p>`;
$('btn-refs-csv-import').disabled = false;
}
});
function openUploadRefsCsvModal() {
refsCsvFileInput.value = '';
$('btn-refs-csv-import').disabled = true;
$('btn-refs-csv-cancel').textContent = 'Cancel';
$('refs-csv-results').hidden = true;
$('refs-csv-results').innerHTML = '';
$('refs-csv-format-details').open = true;
$('upload-refs-csv-modal').showModal();
}
async function importRefsCsvRows(rows) {
const siteCache = new Map();
const musCache = new Map();
const results = { created: 0, warnings: [], errors: [] };
async function resolve(endpoint, list, cache, name) {
if (!name) return null;
const lower = name.toLowerCase();
if (cache.has(lower)) return cache.get(lower);
const existing = list.find(x => x.name.toLowerCase() === lower);
if (existing) { cache.set(lower, existing.id); return existing.id; }
const item = await apiFetch(endpoint, { method: 'POST', body: JSON.stringify({ name }) });
list.push(item);
cache.set(lower, item.id);
return item.id;
}
for (const row of rows) {
const tuneName = row.tune_name?.trim();
if (!tuneName) continue;
const tune = allTunes.find(t => (t.name ?? '').toLowerCase() === tuneName.toLowerCase());
if (!tune) {
results.warnings.push(tuneName);
continue;
}
try {
const link = row.link?.trim() || null;
const siteId = await resolve('/reference_sites/', allReferenceSites, siteCache, row.site?.trim() || null);
const musicians = [];
for (const key of ['musician1', 'musician2', 'musician3', 'musician4']) {
const name = row[key]?.trim();
if (!name) continue;
const id = await resolve('/musicians/', allMusicians, musCache, name);
if (id) musicians.push({ id });
}
if (!link && !siteId && musicians.length === 0) continue;
await apiFetch(`/tunes/${tune.id}/references`, {
method: 'POST',
body: JSON.stringify({ link, site_id: siteId, musicians }),
});
results.created++;
} catch (e) {
results.errors.push({ name: tuneName, error: e.message });
}
}
return results;
}
// ── Mobile truncation tooltip ───────────────────────────────────
{
const tip = $('mob-tooltip');