add(ui/ux): show the target library in the import row, trim the scissors

This commit is contained in:
Steppenstreuner
2026-08-29 10:35:33 +02:00
parent 8bffe6006b
commit 78d726daaf
5 changed files with 59 additions and 4 deletions
+45 -1
View File
@@ -764,10 +764,11 @@ function renderImportTable() {
${appendOpts ? `<optgroup label="Bereits importiert — Teile anhängen">${appendOpts}</optgroup>` : ""}
</select>
<button class="secondary" data-quick="${i}" title="Metadaten suchen und Anfrage direkt verbinden">🔍</button>
${item.parts || (item.is_dir && item.files.length > 1)
${item.parts || item.maybe_separate
? `<button class="secondary" data-split="${i}" title="${item.parts ? "Zusammenfassung wieder auflösen" : "Ordner in einzelne Titel aufteilen"}">✂️</button>`
: ""}
</td>
<td>${libraryCell(item, i)}</td>
${item.importState ? importStateCell(item)
: item.conflict ? '<td class="state conflict" title="Mehrere Einträge zeigen auf dieselbe Anfrage">⚠ Konflikt</td>'
: scoreCell(item)}
@@ -782,6 +783,32 @@ function renderImportTable() {
tbody.querySelectorAll("[data-quick]").forEach((b) =>
b.addEventListener("click", () => openQuickDialog(parseInt(b.dataset.quick)))
);
tbody.querySelectorAll("[data-lib-for]").forEach((sel) =>
sel.addEventListener("change", async () => {
const item = scanItems[sel.dataset.libFor];
const req = requestById(item.request_id);
if (!req) return;
const previous = req.library_id;
req.library_id = parseInt(sel.value);
try {
const saved = await api("/api/requests/" + req.id, {
method: "PUT",
body: JSON.stringify({
library_id: req.library_id, title: req.title, authors: req.authors,
narrator: req.narrator || "", series: req.series, volume: req.volume,
year: req.year, external_id: req.external_id, cover_url: req.cover_url,
}),
});
Object.assign(req, saved);
toast(`${req.title}" → ${saved.library_name}`);
} catch (err) {
req.library_id = previous;
sel.value = String(previous);
toast(err.message, true);
}
renderImportTable();
})
);
tbody.querySelectorAll("[data-split]").forEach((b) =>
b.addEventListener("click", () => splitItem(parseInt(b.dataset.split)))
);
@@ -808,6 +835,23 @@ function renderImportTable() {
updateImportPageInfo(pages);
}
// which library the assigned request writes to - visible in the row, because the
// select inside the search dialog is easy to miss
function libraryCell(item, i) {
const req = requestById(item.request_id);
if (!req) return '<span class="muted">—</span>';
const opts = libraries
.filter((l) => l.media_type === req.media_type)
.map((l) => `<option value="${l.id}" ${l.id === req.library_id ? "selected" : ""}>${esc(l.name)}</option>`)
.join("");
return `<select data-lib-for="${i}" title="Ziel-Library dieser Anfrage ändern">${opts}</select>`;
}
function requestById(id) {
if (!id) return null;
return missingReqs.find((r) => r.id === id) || importedReqs.find((r) => r.id === id) || null;
}
function importStateCell(item) {
if (item.importState === "running") {
return '<td class="state running"><span class="spinner"></span> läuft…</td>';
+1
View File
@@ -184,6 +184,7 @@
<th>Datei/Ordner</th>
<th>Typ</th>
<th>Zuordnung</th>
<th>Library</th>
<th>Score</th>
</tr>
</thead>
+3 -1
View File
@@ -57,7 +57,9 @@ button:disabled { opacity: 0.6; cursor: default; }
.tag.warn { background: #8a6d1f; }
/* per-entry import feedback */
#import-progress { display: flex; align-items: center; gap: 0.6rem; flex: 1; }
/* :not([hidden]) matters: a plain "display: flex" would beat the hidden
attribute and leave an empty bar sitting there (same trap as a <dialog>) */
#import-progress:not([hidden]) { display: flex; align-items: center; gap: 0.6rem; flex: 1; }
#import-progress progress { flex: 1; max-width: 26rem; height: 0.7rem; }
tr.row-running { background: rgba(90, 140, 220, 0.12); }
tr.row-done { background: rgba(80, 170, 110, 0.12); }