feat: warn about duplicate events

This commit is contained in:
kai
2026-08-28 14:23:15 +02:00
parent f05bae9ab1
commit f0d40f5fd4
2 changed files with 190 additions and 13 deletions
+96 -13
View File
@@ -64,6 +64,11 @@
display: none;
}
.duplicate-warning { display:none; margin:10px 0; padding:12px 14px; color:#fde68a; background:#422006; border:1px solid #d97706; border-radius:9px; }
.duplicate-warning strong { display:block; margin-bottom:6px; }
.duplicate-warning ul { margin:6px 0 0; padding-left:20px; }
.duplicate-warning a { color:#fef3c7; text-decoration:underline; }
</style>
</head>
@@ -154,7 +159,10 @@
<input
type="text"
name="artist"
id="artist"
required
minlength="2"
maxlength="255"
placeholder="z. B. Iron Maiden"
>
@@ -238,6 +246,7 @@
<input
type="datetime-local"
name="start_datetime"
id="start-datetime"
required
>
@@ -245,6 +254,9 @@
</p>
<div id="duplicate-warning" class="duplicate-warning" role="status" aria-live="polite"></div>
<input type="hidden" name="duplicate_confirmed" id="duplicate-confirmed" value="false">
<p id="festival-end-field" hidden>
<label>
Enddatum und Uhrzeit<br>
@@ -882,6 +894,67 @@ flyerInput.addEventListener(
// Request unter typischen Proxy-Limits und der Server muss keine riesigen
// Originaldateien verarbeiten.
const concertForm = flyerInput.form;
const artistInput = document.getElementById("artist");
const startDatetimeInput = document.getElementById("start-datetime");
const duplicateWarning = document.getElementById("duplicate-warning");
const duplicateConfirmed = document.getElementById("duplicate-confirmed");
let duplicateTimeout = null;
let duplicateRequestController = null;
function showDuplicateMatches(matches) {
duplicateWarning.replaceChildren();
if (!matches.length) {
duplicateWarning.style.display = "none";
return;
}
const heading = document.createElement("strong");
heading.textContent = "⚠️ Möglicherweise existiert diese Veranstaltung bereits:";
const list = document.createElement("ul");
matches.forEach(match => {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = `/concerts/${match.id}`;
link.target = "_blank";
link.rel = "noopener";
link.textContent = `${match.artist} · ${match.date} ${match.time}${match.venue ? ` · ${match.venue}` : ""}`;
item.appendChild(link);
list.appendChild(item);
});
duplicateWarning.append(heading, list);
duplicateWarning.style.display = "block";
}
async function checkForDuplicates(showWarning = true) {
const artist = artistInput.value.trim();
const startDate = startDatetimeInput.value;
if (artist.length < 2 || !startDate) {
if (showWarning) showDuplicateMatches([]);
return [];
}
if (duplicateRequestController) duplicateRequestController.abort();
duplicateRequestController = new AbortController();
try {
const params = new URLSearchParams({artist, start_date: startDate});
const response = await fetch(`/api/concerts/duplicates?${params}`, {
signal: duplicateRequestController.signal
});
if (!response.ok) return [];
const matches = (await response.json()).matches || [];
if (showWarning) showDuplicateMatches(matches);
return matches;
} catch (error) {
if (error.name !== "AbortError") console.error("Duplicate check failed:", error);
return [];
}
}
[artistInput, startDatetimeInput].forEach(input => input.addEventListener("input", () => {
duplicateConfirmed.value = "false";
concertForm.dataset.readyToSubmit = "false";
clearTimeout(duplicateTimeout);
duplicateTimeout = setTimeout(() => checkForDuplicates(true), 450);
}));
async function optimizeFlyerUpload(file) {
const bitmap = await createImageBitmap(file);
const maxSide = 1800;
@@ -903,21 +976,31 @@ async function optimizeFlyerUpload(file) {
}
concertForm.addEventListener("submit", async event => {
if (concertForm.dataset.flyerOptimized === "true") return;
const file = flyerInput.files[0];
if (!file) return;
if (concertForm.dataset.readyToSubmit === "true") return;
event.preventDefault();
try {
const transfer = new DataTransfer();
transfer.items.add(await optimizeFlyerUpload(file));
flyerInput.files = transfer.files;
concertForm.dataset.flyerOptimized = "true";
concertForm.requestSubmit();
} catch (error) {
console.error("Flyer optimization failed:", error);
concertForm.dataset.flyerOptimized = "true";
concertForm.requestSubmit();
const matches = await checkForDuplicates(true);
if (matches.length && duplicateConfirmed.value !== "true") {
const proceed = window.confirm(
"Am selben Tag gibt es bereits eine Veranstaltung mit einem sehr ähnlichen Bandnamen. Trotzdem speichern?"
);
if (!proceed) return;
duplicateConfirmed.value = "true";
}
const file = flyerInput.files[0];
if (file && concertForm.dataset.flyerOptimized !== "true") {
try {
const transfer = new DataTransfer();
transfer.items.add(await optimizeFlyerUpload(file));
flyerInput.files = transfer.files;
} catch (error) {
console.error("Flyer optimization failed:", error);
}
concertForm.dataset.flyerOptimized = "true";
}
concertForm.dataset.readyToSubmit = "true";
concertForm.requestSubmit();
});