Add term column

This commit is contained in:
Ian Keane 2026-06-11 17:28:14 -04:00
parent 52eb0f6189
commit dba48e90b7
2 changed files with 31 additions and 1 deletions

23
app.js
View file

@ -10,6 +10,7 @@ let allMusicians = [];
let allInstruments = [];
let allTunings = [];
let allReferenceSites = [];
let allTerms = [];
let sortCol = 'name';
let sortDir = 'asc';
@ -43,6 +44,7 @@ const sourceDatalist = $('source-list');
const musicianDatalist = $('musician-list');
const instrumentSelect = $('instrument-select');
const tuningDatalist = $('tuning-list');
const termDatalist = $('term-list');
const btnDeleteTune = $('btn-delete-tune');
const btnEditNotes = $('btn-edit-notes');
const btnBulkEdit = $('btn-bulk-edit');
@ -116,18 +118,20 @@ function setStatus(msg, isError = false) {
async function loadAll() {
setStatus('Loading…');
try {
[allTunes, allSources, allMusicians, allInstruments, allTunings, allReferenceSites] = await Promise.all([
[allTunes, allSources, allMusicians, allInstruments, allTunings, allReferenceSites, allTerms] = await Promise.all([
apiFetch('/tunes/'),
apiFetch('/sources/'),
apiFetch('/musicians/'),
apiFetch('/instruments'),
apiFetch('/tunings/'),
apiFetch('/reference_sites/'),
apiFetch('/terms/'),
]);
populateFilterOptions();
populateDatalist(sourceDatalist, allSources);
populateDatalist(musicianDatalist, allMusicians);
populateDatalist(tuningDatalist, allTunings);
populateDatalist(termDatalist, allTerms);
populateDatalist(refSiteDatalist, allReferenceSites);
populateInstrumentSelect();
renderTable();
@ -204,6 +208,12 @@ $('btn-quick-add-tuning').addEventListener('click', async () => {
});
});
$('btn-quick-add-term').addEventListener('click', async () => {
await quickAdd('/terms/', 'New term name:', allTerms, termDatalist, item => {
tuneForm.elements.inst_term.value = item.name;
});
});
$('btn-quick-add-instrument').addEventListener('click', async () => {
const item = await quickAdd('/instruments', 'New instrument name:', allInstruments, null);
if (item) {
@ -231,6 +241,11 @@ function resolveTuningId(name) {
return allTunings.find(x => x.name.toLowerCase() === name.toLowerCase())?.id ?? null;
}
function resolveTermId(name) {
if (!name) return null;
return allTerms.find(x => x.name.toLowerCase() === name.toLowerCase())?.id ?? null;
}
// ── Rows: one per instrument entry ────────────────────────────
// For tunes with no instrument entries, emit one row with empty instrument cols
function buildRows(tunes) {
@ -550,6 +565,7 @@ async function openEdit(tuneId, entryId) {
populateInstrumentSelect(entry?.instrument_id ?? null);
if (entry) {
tuneForm.elements.inst_tuning.value = entry.tuning ?? '';
tuneForm.elements.inst_term.value = entry.term ?? '';
tuneForm.elements.inst_learned_from.value = entry.learned_from ?? '';
tuneForm.elements.inst_date_learned.value = entry.date_learned ?? '';
tuneForm.elements.inst_status.value = entry.status ?? '';
@ -708,6 +724,7 @@ async function saveTune() {
const instBody = instId ? {
instrument_id: instId,
tuning_id: resolveTuningId(f.inst_tuning.value),
term_id: resolveTermId(f.inst_term.value),
learned_from_id: resolveMusicianId(f.inst_learned_from.value),
date_learned: f.inst_date_learned.value || null,
status: f.inst_status.value || null,
@ -1432,6 +1449,7 @@ async function importCSVRows(rows) {
const tunCache = new Map(); // name.lower → id (tunings)
const musCache = new Map(); // name.lower → id (musicians)
const instCache = new Map(); // name.lower → id (instruments)
const termCache = new Map(); // name.lower → id (terms)
const tuneCache = new Map(); // name.lower → id (tunes created this run)
const results = { created: 0, skipped: 0, errors: [] };
@ -1455,6 +1473,7 @@ async function importCSVRows(rows) {
const tuningId = await resolveOrCreate('/tunings/', allTunings, tunCache, row.tuning?.trim() || null);
const learnedFromId = await resolveOrCreate('/musicians/', allMusicians, musCache, row.learned_from?.trim() || null);
const instId = await resolveOrCreate('/instruments', allInstruments, instCache, row.instrument?.trim() || null);
const termId = await resolveOrCreate('/terms/', allTerms, termCache, row.term?.trim() || null);
// Find existing tune or create new
const lower = tuneName.toLowerCase();
@ -1481,6 +1500,7 @@ async function importCSVRows(rows) {
body: JSON.stringify({
instrument_id: instId,
tuning_id: tuningId,
term_id: termId,
learned_from_id: learnedFromId,
date_learned: row.date_learned?.trim() || null,
status,
@ -1537,6 +1557,7 @@ function attachTabAutocomplete(input, datalist) {
attachTabAutocomplete(sourceInput, sourceDatalist);
attachTabAutocomplete(tuneForm.elements.inst_tuning, tuningDatalist);
attachTabAutocomplete(tuneForm.elements.inst_term, termDatalist);
attachTabAutocomplete(tuneForm.elements.inst_learned_from, musicianDatalist);
// ── Boot ─────────────────────────────────────────────────────

View file

@ -121,6 +121,14 @@
<button type="button" class="quick-add-btn" id="btn-quick-add-tuning" title="Add new tuning">+</button>
</span>
</label>
<label>Term
<span class="autocomplete-row">
<input name="inst_term" type="text"
list="term-list" placeholder="— none —" autocomplete="off" />
<datalist id="term-list"></datalist>
<button type="button" class="quick-add-btn" id="btn-quick-add-term" title="Add new term">+</button>
</span>
</label>
<label>Learned from
<span class="autocomplete-row">
<input name="inst_learned_from" type="text"
@ -230,6 +238,7 @@
<tr><td>date_learned</td><td></td><td>YYYY-MM-DD</td></tr>
<tr><td>status</td><td></td><td>callable / review / to_learn</td></tr>
<tr><td>difficulty</td><td></td><td>easy / hard</td></tr>
<tr><td>term</td><td></td><td>Created if new</td></tr>
</tbody>
</table>
<p class="csv-format-note">If a tune with the same name already exists a new instrument entry is added to it. Sources, tunings, and musicians are created automatically if they dont exist.</p>