feat: warn about duplicate events
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user