Add admin booking management prompt and deletion
This commit is contained in:
@@ -262,6 +262,68 @@
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.bookings-list {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
margin: 18px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.booking-row {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #e5d8ce;
|
||||
}
|
||||
|
||||
.booking-date {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 1.08rem;
|
||||
}
|
||||
|
||||
.booking-activities {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 12px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.booking-activity {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.booking-metadata {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 14px;
|
||||
color: var(--color-text-light);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.delete-booking-btn {
|
||||
justify-self: start;
|
||||
min-height: 36px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #c84a62;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: #a83049;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-booking-btn:hover,
|
||||
.delete-booking-btn:focus-visible {
|
||||
background: #fff0f2;
|
||||
}
|
||||
|
||||
.delete-booking-btn:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.admin-calendar {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
<nav class="admin-nav" aria-label="Verwaltung">
|
||||
<button class="admin-nav-btn is-active" type="button" data-view="datesView">Termine</button>
|
||||
<button class="admin-nav-btn" type="button" data-view="bookingsView">Gebuchte Termine</button>
|
||||
<button class="admin-nav-btn" type="button" data-view="activitiesView">Aktivitäten</button>
|
||||
</nav>
|
||||
|
||||
@@ -73,6 +74,17 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="bookingsView" class="admin-panel hidden">
|
||||
<section class="admin-section" aria-labelledby="bookingsTitle">
|
||||
<div class="section-heading">
|
||||
<h2 id="bookingsTitle">Gebuchte Termine</h2>
|
||||
<p>Neueste Buchungen stehen oben.</p>
|
||||
</div>
|
||||
<ul id="bookingsList" class="bookings-list" aria-live="polite"></ul>
|
||||
<p id="emptyBookings" class="empty-dates hidden">Noch keine Termine gebucht.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="activitiesView" class="admin-panel hidden">
|
||||
<section class="admin-section" aria-labelledby="activitiesTitle">
|
||||
<div class="section-heading">
|
||||
|
||||
+74
-1
@@ -19,6 +19,8 @@
|
||||
const emptyActivities = document.getElementById("emptyActivities");
|
||||
const activityMessage = document.getElementById("activityMessage");
|
||||
const activityAddRow = document.getElementById("activityAddRow");
|
||||
const bookingsList = document.getElementById("bookingsList");
|
||||
const emptyBookings = document.getElementById("emptyBookings");
|
||||
const newActivityIcon = document.getElementById("newActivityIcon");
|
||||
const newActivityName = document.getElementById("newActivityName");
|
||||
const newActivityType = document.getElementById("newActivityType");
|
||||
@@ -33,6 +35,7 @@
|
||||
let viewYear = today.getFullYear();
|
||||
let viewMonth = today.getMonth();
|
||||
let activities = [];
|
||||
let bookings = [];
|
||||
|
||||
function setMessage(element, message, success) {
|
||||
element.textContent = message;
|
||||
@@ -149,6 +152,76 @@
|
||||
prevMonthBtn.disabled = viewYear === today.getFullYear() && viewMonth === today.getMonth();
|
||||
}
|
||||
|
||||
function renderBookings() {
|
||||
bookingsList.innerHTML = "";
|
||||
emptyBookings.classList.toggle("hidden", bookings.length > 0);
|
||||
bookings.forEach((booking) => {
|
||||
const row = document.createElement("li");
|
||||
row.className = "booking-row";
|
||||
|
||||
const date = document.createElement("strong");
|
||||
date.className = "booking-date";
|
||||
date.textContent = formatDate(booking.date);
|
||||
row.appendChild(date);
|
||||
|
||||
if (booking.activities && booking.activities.length > 0) {
|
||||
const activityList = document.createElement("div");
|
||||
activityList.className = "booking-activities";
|
||||
booking.activities.forEach((activity) => {
|
||||
const activityItem = document.createElement("span");
|
||||
activityItem.className = "booking-activity";
|
||||
activityItem.textContent = `${activity.icon || ""} ${activity.name}`.trim();
|
||||
activityList.appendChild(activityItem);
|
||||
});
|
||||
row.appendChild(activityList);
|
||||
}
|
||||
|
||||
const metadata = document.createElement("div");
|
||||
metadata.className = "booking-metadata";
|
||||
if (booking.name && booking.name.trim()) {
|
||||
const name = document.createElement("span");
|
||||
name.textContent = booking.name;
|
||||
metadata.appendChild(name);
|
||||
}
|
||||
if (booking.bookedAt) {
|
||||
const created = document.createElement("time");
|
||||
created.dateTime = booking.bookedAt;
|
||||
created.textContent = `Gebucht am ${new Date(booking.bookedAt).toLocaleString("de-DE", { dateStyle: "medium", timeStyle: "short" })}`;
|
||||
metadata.appendChild(created);
|
||||
}
|
||||
if (metadata.childElementCount > 0) row.appendChild(metadata);
|
||||
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.className = "delete-booking-btn";
|
||||
deleteButton.type = "button";
|
||||
deleteButton.textContent = "Termin löschen";
|
||||
deleteButton.addEventListener("click", () => deleteBooking(booking, deleteButton));
|
||||
row.appendChild(deleteButton);
|
||||
bookingsList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteBooking(booking, deleteButton) {
|
||||
if (!window.confirm(`Den gebuchten Termin am ${formatDate(booking.date)} wirklich löschen?`)) return;
|
||||
deleteButton.disabled = true;
|
||||
try {
|
||||
await request("/api/admin/bookings", { method: "DELETE", body: JSON.stringify({ date: booking.date }) });
|
||||
bookedDates.delete(booking.date);
|
||||
await loadBookings();
|
||||
renderCalendar();
|
||||
renderDates();
|
||||
} catch (error) {
|
||||
deleteButton.disabled = false;
|
||||
if (error.status === 401) showLogin("Deine Anmeldung ist abgelaufen.");
|
||||
else window.alert(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBookings() {
|
||||
bookings = await request("/api/admin/bookings", { method: "GET" });
|
||||
renderBookings();
|
||||
}
|
||||
|
||||
async function loadDates() {
|
||||
try {
|
||||
const [loadedDates, loadedBookedDates] = await Promise.all([
|
||||
@@ -161,7 +234,7 @@
|
||||
draftDates = new Map(dates.map((date) => [date.date, date.type]));
|
||||
loginView.classList.add("hidden");
|
||||
adminView.classList.remove("hidden");
|
||||
await loadActivities();
|
||||
await Promise.all([loadActivities(), loadBookings()]);
|
||||
renderCalendar();
|
||||
renderDates();
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user