diff --git a/public/app.mjs b/public/app.mjs index 8cb9d28..e88c9b0 100644 --- a/public/app.mjs +++ b/public/app.mjs @@ -336,24 +336,60 @@ function render_detail_panel() { // Linked files const files_list = qs(content, '.detail-files-list'); - const linked_files = all_pdfs.filter(p => (comp.file_ids ?? []).includes(p.id)); - if (linked_files.length === 0) { - const note = document.createElement('p'); - note.className = 'detail-empty-note'; - note.textContent = 'No files linked.'; - files_list.replaceChildren(note); - } else { - files_list.replaceChildren(...linked_files.map(pdf => { + + async function save_file_ids(new_ids) { + const result = await api.update_component(comp.id, { file_ids: new_ids }); + const idx = all_components.findIndex(c => c.id === comp.id); + if (idx !== -1) all_components[idx] = result.component; + render_detail_panel(); + } + + function rebuild_detail_files() { + const linked_files = all_pdfs.filter(p => (comp.file_ids ?? []).includes(p.id)); + const rows = linked_files.map(pdf => { + const row = document.createElement('div'); + row.className = 'detail-file-row'; + const a = document.createElement('a'); a.className = 'detail-file-link'; a.href = `/pdf/${pdf.filename}`; a.target = '_blank'; a.rel = 'noopener'; a.textContent = pdf.display_name; - return a; - })); + + const unlink_btn = document.createElement('button'); + unlink_btn.type = 'button'; + unlink_btn.className = 'btn-icon btn-danger'; + unlink_btn.textContent = '✕'; + unlink_btn.title = 'Unlink file'; + unlink_btn.addEventListener('click', () => { + const new_ids = (comp.file_ids ?? []).filter(id => id !== pdf.id); + save_file_ids(new_ids); + }); + + row.append(a, unlink_btn); + return row; + }); + + if (rows.length === 0) { + const note = document.createElement('p'); + note.className = 'detail-empty-note'; + note.textContent = 'No files linked.'; + files_list.replaceChildren(note); + } else { + files_list.replaceChildren(...rows); + } } + rebuild_detail_files(); + + qs(content, '.detail-link-file-btn').addEventListener('click', () => { + open_file_picker((pdf) => { + const new_ids = [...new Set([...(comp.file_ids ?? []), pdf.id])]; + save_file_ids(new_ids); + }); + }); + // Inventory entries const inv_list = qs(content, '.detail-inventory-list'); const entries = inventory_for_component(comp.id); @@ -1421,42 +1457,6 @@ function open_component_dialog(comp = null) { desc_input.value = comp?.description ?? ''; const active_fields = new Map(Object.entries(comp?.fields ?? {})); - const active_file_ids = new Set(comp?.file_ids ?? []); - const file_rows_el = qs(dlg, '#c-file-rows'); - - function rebuild_file_rows() { - const linked = all_pdfs.filter(p => active_file_ids.has(p.id)); - file_rows_el.replaceChildren(...linked.map(pdf => { - const row = document.createElement('div'); - row.className = 'c-field-input-row'; - - const name_el = document.createElement('span'); - name_el.className = 'c-field-input-label'; - name_el.textContent = pdf.display_name; - - const remove_btn = document.createElement('button'); - remove_btn.type = 'button'; - remove_btn.className = 'btn-icon btn-danger'; - remove_btn.textContent = '✕'; - remove_btn.title = 'Unlink file'; - remove_btn.addEventListener('click', () => { - active_file_ids.delete(pdf.id); - rebuild_file_rows(); - }); - - row.append(name_el, remove_btn); - return row; - })); - } - - rebuild_file_rows(); - - qs(dlg, '#c-link-file').addEventListener('click', () => { - open_file_picker((pdf) => { - active_file_ids.add(pdf.id); - rebuild_file_rows(); - }); - }); function rebuild_field_rows() { field_rows_el.replaceChildren(...[...active_fields.entries()].map(([fid, val]) => { @@ -1552,7 +1552,7 @@ function open_component_dialog(comp = null) { for (const [fid, val] of active_fields.entries()) { if (val.trim()) fields[fid] = val.trim(); } - const body = { name, description: desc_input.value.trim(), fields, file_ids: [...active_file_ids] }; + const body = { name, description: desc_input.value.trim(), fields }; if (comp) { const result = await api.update_component(comp.id, body); const idx = all_components.findIndex(c => c.id === comp.id); diff --git a/public/style.css b/public/style.css index 2225620..15bfcb0 100644 --- a/public/style.css +++ b/public/style.css @@ -1571,6 +1571,12 @@ nav { gap: 0.3rem; } +.detail-file-row { + display: flex; + align-items: center; + gap: 0.5rem; +} + .detail-file-link { color: var(--accent); text-decoration: none; diff --git a/public/templates.html b/public/templates.html index 27f738f..b39a537 100644 --- a/public/templates.html +++ b/public/templates.html @@ -58,7 +58,10 @@