diff --git a/static/app.js b/static/app.js
index 6ba57e3..ca917da 100644
--- a/static/app.js
+++ b/static/app.js
@@ -11,11 +11,27 @@ async function api(path, opts = {}) {
if (!resp.ok) {
let msg = resp.statusText;
try { msg = (await resp.json()).detail || msg; } catch {}
- throw new Error(msg);
+ const err = new Error(msg);
+ err.status = resp.status;
+ throw err;
}
return resp.json();
}
+// create a request; on a duplicate warning (409) ask the user before retrying
+async function createRequest(body) {
+ try {
+ return await api("/api/requests", { method: "POST", body: JSON.stringify(body) });
+ } catch (err) {
+ if (err.status === 409 && confirm(err.message + "\nTrotzdem anlegen?")) {
+ return api("/api/requests?allow_duplicate=true", {
+ method: "POST", body: JSON.stringify(body),
+ });
+ }
+ throw err;
+ }
+}
+
function toast(msg, isError = false) {
const t = $("#toast");
t.textContent = msg;
@@ -216,15 +232,12 @@ $("#search-form").addEventListener("submit", async (e) => {
if (!libId) { toast("Erst eine Library für diesen Typ anlegen (Tab Libraries)", true); return; }
const volInput = box.querySelector(`[data-vol="${i}"]`);
try {
- await api("/api/requests", {
- method: "POST",
- body: JSON.stringify({
+ await createRequest({
library_id: parseInt(libId),
title: r.title, authors: r.authors, narrator: r.narrator || "",
external_id: r.external_id,
year: r.year, series: r.series, cover_url: r.cover_url,
volume: volInput && volInput.value ? parseInt(volInput.value) : (r.volume ?? null),
- }),
});
setLastLib(type, libId);
btn.textContent = "✓ Angefragt";
@@ -277,16 +290,13 @@ $("#manual-form").addEventListener("submit", async (e) => {
});
toast(`${created.length} Anfragen angelegt`);
} else {
- await api("/api/requests", {
- method: "POST",
- body: JSON.stringify({
- library_id: parseInt(data.library_id),
- title: data.title, authors: data.authors, series: data.series,
- narrator: data.narrator || "",
- external_id: data.external_id,
- year: data.year ? parseInt(data.year) : null,
- volume: data.volume ? parseInt(data.volume) : null,
- }),
+ await createRequest({
+ library_id: parseInt(data.library_id),
+ title: data.title, authors: data.authors, series: data.series,
+ narrator: data.narrator || "",
+ external_id: data.external_id,
+ year: data.year ? parseInt(data.year) : null,
+ volume: data.volume ? parseInt(data.volume) : null,
});
toast("Anfrage angelegt");
}
@@ -675,10 +685,47 @@ function openQuickDialog(i) {
$("#quick-library").innerHTML =
libOptions(item.media_type) || "";
$("#quick-results").innerHTML = "";
+ renderQuickOpenRequests(item);
$("#quick-dialog").showModal();
runQuickSearch();
}
+// offer linking to similar already-open requests instead of creating a duplicate
+function renderQuickOpenRequests(item) {
+ const box = $("#quick-open-requests");
+ const tokens = cleanFileName(item.name).toLowerCase().split(" ").filter((t) => t.length > 2);
+ const matches = missingReqs
+ .filter((r) => r.media_type === item.media_type)
+ .map((r) => {
+ const hay = `${r.title} ${r.authors} ${r.series} ${r.narrator}`.toLowerCase();
+ return { r, hits: tokens.filter((t) => hay.includes(t)).length };
+ })
+ .filter((m) => m.hits >= 2)
+ .sort((a, b) => b.hits - a.hits)
+ .slice(0, 3);
+ if (!matches.length) { box.innerHTML = ""; return; }
+ box.innerHTML =
+ '
Passende offene Requests:
' +
+ matches
+ .map(
+ ({ r }) => `
+ ${esc(r.title)}${r.volume != null ? " · Band " + r.volume : ""} — ${esc(r.authors)} (${esc(r.library_name)})
+
+
`
+ )
+ .join("");
+ box.querySelectorAll("[data-link-req]").forEach((b) =>
+ b.addEventListener("click", () => {
+ const i = quickItemIndex;
+ scanItems[i].request_id = parseInt(b.dataset.linkReq);
+ scanItems[i].checked = true;
+ renderImportTable();
+ $("#quick-dialog").close();
+ toast("Mit bestehendem Request verbunden");
+ })
+ );
+}
+
async function runQuickSearch() {
const item = scanItems[quickItemIndex];
const box = $("#quick-results");
@@ -726,14 +773,11 @@ async function pickQuickResult(r) {
if (!libId) { toast("Erst eine Library für diesen Typ anlegen", true); return; }
const i = quickItemIndex;
try {
- const req = await api("/api/requests", {
- method: "POST",
- body: JSON.stringify({
- library_id: parseInt(libId),
- title: r.title, authors: r.authors, narrator: r.narrator || "",
- external_id: r.external_id, volume: r.volume ?? null,
- year: r.year, series: r.series, cover_url: r.cover_url,
- }),
+ const req = await createRequest({
+ library_id: parseInt(libId),
+ title: r.title, authors: r.authors, narrator: r.narrator || "",
+ external_id: r.external_id, volume: r.volume ?? null,
+ year: r.year, series: r.series, cover_url: r.cover_url,
});
setLastLib(scanItems[i].media_type, libId);
missingReqs.push(req);
diff --git a/static/index.html b/static/index.html
index 738650a..59f16c5 100644
--- a/static/index.html
+++ b/static/index.html
@@ -179,6 +179,7 @@
+