const tableBody = document.querySelector("#visitors"); const count = document.querySelector("#count"); const message = document.querySelector("#message"); const csvButton = document.querySelector("#export-csv"); const printButton = document.querySelector("#print-list"); let visitors = []; let sortKey = "createdAt"; let sortDirection = -1; const sortCollator = new Intl.Collator("da", { numeric: true, sensitivity: "base" }); function sortVisitors() { // Move existing rows so unsaved result inputs are retained. const rows = new Map([...tableBody.querySelectorAll("tr")].map((row) => [row.querySelector(".result-form")?.dataset.id, row])); const value = (visitor) => { const row = rows.get(String(visitor.id)); if (sortKey === "result") return row?.querySelector('[name="result"]')?.value ?? visitor.result; if (sortKey === "x10") return row?.querySelector('[name="x10"]')?.value ?? visitor.x10; return visitor[sortKey]; }; visitors.sort((a, b) => { const av = value(a), bv = value(b); if (av === null || av === undefined || av === "") return bv === null || bv === undefined || bv === "" ? a.id - b.id : 1; if (bv === null || bv === undefined || bv === "") return -1; return sortDirection * sortCollator.compare(String(av), String(bv)) || a.id - b.id; }); for (const visitor of visitors) { const row = rows.get(String(visitor.id)); if (row) tableBody.append(row); } document.querySelectorAll("button[data-sort]").forEach((button) => { const active = button.dataset.sort === sortKey; button.closest("th").setAttribute("aria-sort", active ? (sortDirection === 1 ? "ascending" : "descending") : "none"); button.querySelector(".sort-arrow").textContent = active ? (sortDirection === 1 ? " ↑" : " ↓") : " ↕"; }); } document.querySelectorAll("button[data-sort]").forEach((button) => { button.addEventListener("click", () => { sortDirection = sortKey === button.dataset.sort ? -sortDirection : 1; sortKey = button.dataset.sort; sortVisitors(); }); }); function formatDateTime(value) { return new Intl.DateTimeFormat("da-DK", { dateStyle: "short", timeStyle: "short" }).format(new Date(value)); } function showError(text) { message.textContent = text; message.className = "notice error"; message.hidden = false; } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } async function loadVisitors() { csvButton.disabled = true; printButton.disabled = true; try { const response = await fetch("/api/visitors"); if (!response.ok) { showError("Listen kunne ikke hentes. Tjek admin-login og databaseforbindelse."); count.textContent = "Fejl"; return; } visitors = await response.json(); updateParticipantMap(visitors); message.hidden = true; csvButton.disabled = false; printButton.disabled = false; count.textContent = `${visitors.length} registreret`; if (visitors.length === 0) { tableBody.innerHTML = 'Ingen besøgende registreret endnu.'; return; } tableBody.innerHTML = visitors.map((visitor) => ` ${visitor.id} ${escapeHtml(visitor.name)} ${escapeHtml(visitor.streetAddress)} ${escapeHtml(visitor.postalCode)} ${escapeHtml(visitor.city)} ${escapeHtml(visitor.birthday)} ${visitor.email ? `${escapeHtml(visitor.email)}` : ""} ${escapeHtml(formatDateTime(visitor.createdAt))}
${escapeHtml(visitor.result)} ${escapeHtml(visitor.x10)} `).join(""); sortVisitors(); } catch { showError("Der er ikke forbindelse til besøgslisten lige nu."); count.textContent = "Fejl"; } } tableBody.addEventListener("submit", async (event) => { const form = event.target.closest(".result-form"); if (!form) return; event.preventDefault(); const button = form.closest("tr").querySelector(".save-result-button"); if (button.disabled) return; const status = form.querySelector(".result-status"); const result = form.elements.result.value.trim(); const x10 = form.elements.x10.value === "" ? null : Number(form.elements.x10.value); button.disabled = true; status.textContent = "Gemmer..."; try { const response = await fetch(`/api/visitors/${form.dataset.id}/result`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ result, x10 }) }); if (!response.ok) throw new Error("Resultat kunne ikke gemmes."); const saved = await response.json(); const visitor = visitors.find((item) => String(item.id) === form.dataset.id); if (visitor) { visitor.result = saved.result; visitor.x10 = saved.x10; } form.elements.result.value = saved.result; form.parentElement.querySelector(".print-result").textContent = saved.result; form.elements.x10.value = saved.x10 ?? ""; form.closest("tr").querySelector(".print-x10").textContent = saved.x10 ?? ""; status.textContent = "Gemt"; } catch { status.textContent = "Ikke gemt. Prøv igen."; } finally { button.disabled = false; } }); tableBody.addEventListener("click", async (event) => { const button = event.target.closest("button[data-id]"); if (!button || button.disabled) return; const visitor = visitors.find((item) => String(item.id) === button.dataset.id); if (!visitor || !window.confirm(`Slet registreringen for ${visitor.name}? Dette kan ikke fortrydes.`)) return; button.disabled = true; csvButton.disabled = true; printButton.disabled = true; try { const response = await fetch(`/api/visitors/${visitor.id}`, { method: "DELETE" }); if (!response.ok && response.status !== 404) throw new Error("Sletning mislykkedes."); await loadVisitors(); } catch { showError("Registreringen kunne ikke slettes. Prøv igen."); button.disabled = false; csvButton.disabled = false; printButton.disabled = false; } }); function csvCell(value) { let text = String(value ?? ""); // Prevent spreadsheet programs from evaluating visitor input as formulas. if (/^[\s]*[=+@-]/.test(text)) text = "'" + text; return `"${text.replaceAll('"', '""')}"`; } csvButton.addEventListener("click", () => { const rows = [ ["Skivenr.", "Navn", "Vejnavn og husnummer", "Postnummer", "Postby", "Fødselsdag", "Email", "Registreret", "Resultat", "X-10"], ...visitors.map((visitor) => [visitor.id, visitor.name, visitor.streetAddress, visitor.postalCode, visitor.city, visitor.birthday, visitor.email, formatDateTime(visitor.createdAt), visitor.result, visitor.x10]) ]; const csv = "\uFEFF" + rows.map((row) => row.map(csvCell).join(";")).join("\r\n") + "\r\n"; const url = URL.createObjectURL(new Blob([csv], { type: "text/csv;charset=utf-8" })); const link = document.createElement("a"); link.href = url; link.download = "skydesportens-dag-besogende.csv"; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); }); printButton.addEventListener("click", () => window.print()); loadVisitors();