Files

206 lines
6.6 KiB
JavaScript

(function () {
const loginView = document.getElementById("loginView");
const adminView = document.getElementById("adminView");
const loginForm = document.getElementById("loginForm");
const passwordInput = document.getElementById("passwordInput");
const loginMessage = document.getElementById("loginMessage");
const addDateForm = document.getElementById("addDateForm");
const dateInput = document.getElementById("dateInput");
const addDateMessage = document.getElementById("addDateMessage");
const datesList = document.getElementById("datesList");
const dateCount = document.getElementById("dateCount");
const emptyDates = document.getElementById("emptyDates");
const logoutBtn = document.getElementById("logoutBtn");
let dates = [];
function setMessage(element, message, success) {
element.textContent = message;
element.classList.toggle("is-success", Boolean(success));
}
function todayKey() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const day = String(today.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
function formatDate(date) {
return new Date(`${date}T00:00:00`).toLocaleDateString("de-DE", {
weekday: "short",
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}
async function request(path, options) {
const response = await fetch(path, {
...options,
headers: {
"Content-Type": "application/json",
...(options && options.headers),
},
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
const error = new Error(data.message || "Die Anfrage ist fehlgeschlagen.");
error.status = response.status;
throw error;
}
return data;
}
function showLogin(message) {
adminView.classList.add("hidden");
loginView.classList.remove("hidden");
if (message) {
setMessage(loginMessage, message, false);
}
passwordInput.focus();
}
function renderDates() {
datesList.innerHTML = "";
dateCount.textContent = `${dates.length} ${dates.length === 1 ? "Termin" : "Termine"}`;
emptyDates.classList.toggle("hidden", dates.length !== 0);
dates.forEach((date) => {
const row = document.createElement("li");
row.className = "date-row";
const label = document.createElement("span");
label.className = "date-label";
label.textContent = formatDate(date.date);
const typeSelect = document.createElement("select");
typeSelect.className = "date-type-select";
typeSelect.setAttribute("aria-label", `Tagestyp für ${formatDate(date.date)}`);
[["short", "Kurz"], ["long", "Lang"]].forEach(([value, text]) => {
const option = document.createElement("option");
option.value = value;
option.textContent = text;
option.selected = date.type === value;
typeSelect.appendChild(option);
});
typeSelect.addEventListener("change", async () => {
typeSelect.disabled = true;
try {
await request("/api/admin/dates", {
method: "PUT",
body: JSON.stringify({ date: date.date, type: typeSelect.value }),
});
date.type = typeSelect.value;
setMessage(addDateMessage, "Tagestyp aktualisiert.", true);
} catch (error) {
if (error.status === 401) {
showLogin("Deine Anmeldung ist abgelaufen.");
return;
}
typeSelect.value = date.type;
setMessage(addDateMessage, error.message, false);
} finally {
typeSelect.disabled = false;
}
});
const deleteButton = document.createElement("button");
deleteButton.className = "delete-date-btn";
deleteButton.type = "button";
deleteButton.textContent = "Löschen";
deleteButton.addEventListener("click", async () => {
if (!window.confirm(`${formatDate(date.date)} wirklich löschen?`)) {
return;
}
deleteButton.disabled = true;
try {
await request("/api/admin/dates", {
method: "DELETE",
body: JSON.stringify({ date: date.date }),
});
dates = dates.filter((item) => item.date !== date.date);
renderDates();
setMessage(addDateMessage, "Datum gelöscht.", true);
} catch (error) {
if (error.status === 401) {
showLogin("Deine Anmeldung ist abgelaufen.");
return;
}
setMessage(addDateMessage, error.message, false);
deleteButton.disabled = false;
}
});
row.append(label, typeSelect, deleteButton);
datesList.appendChild(row);
});
}
async function loadDates() {
try {
dates = await request("/api/admin/dates", { method: "GET" });
dates.sort((first, second) => first.date.localeCompare(second.date));
loginView.classList.add("hidden");
adminView.classList.remove("hidden");
renderDates();
} catch (error) {
if (error.status === 401) {
showLogin();
return;
}
showLogin("Die Termine konnten nicht geladen werden.");
}
}
loginForm.addEventListener("submit", async (event) => {
event.preventDefault();
setMessage(loginMessage, "", false);
try {
await request("/api/admin/login", {
method: "POST",
body: JSON.stringify({ password: passwordInput.value }),
});
passwordInput.value = "";
await loadDates();
} catch (error) {
setMessage(loginMessage, error.message, false);
}
});
addDateForm.addEventListener("submit", async (event) => {
event.preventDefault();
const type = addDateForm.elements.dateType.value;
setMessage(addDateMessage, "", false);
try {
await request("/api/admin/dates", {
method: "POST",
body: JSON.stringify({ date: dateInput.value, type }),
});
dates.push({ date: dateInput.value, type });
dates.sort((first, second) => first.date.localeCompare(second.date));
renderDates();
setMessage(addDateMessage, "Datum hinzugefügt.", true);
addDateForm.reset();
dateInput.min = todayKey();
} catch (error) {
if (error.status === 401) {
showLogin("Deine Anmeldung ist abgelaufen.");
return;
}
setMessage(addDateMessage, error.message, false);
}
});
logoutBtn.addEventListener("click", async () => {
try {
await request("/api/admin/logout", { method: "POST" });
} finally {
showLogin();
}
});
dateInput.min = todayKey();
loadDates();
})();