Add admin activity management
This commit is contained in:
+239
@@ -15,6 +15,14 @@
|
||||
const prevMonthBtn = document.getElementById("prevMonthBtn");
|
||||
const nextMonthBtn = document.getElementById("nextMonthBtn");
|
||||
const saveDatesBtn = document.getElementById("saveDatesBtn");
|
||||
const activitiesList = document.getElementById("activitiesList");
|
||||
const emptyActivities = document.getElementById("emptyActivities");
|
||||
const activityMessage = document.getElementById("activityMessage");
|
||||
const activityAddRow = document.getElementById("activityAddRow");
|
||||
const newActivityIcon = document.getElementById("newActivityIcon");
|
||||
const newActivityName = document.getElementById("newActivityName");
|
||||
const newActivityType = document.getElementById("newActivityType");
|
||||
const activityIcons = ["🧺", "🍸", "💆", "🥾", "🎬", "🍣", "🎨", "🎢", "🌲", "🍒", "🍜", "🏊", "🛋️", "🏸", "🍛", "🖼️", "🎮", "✏️", "🍕", "🍔", "☕", "🍰", "🍽️", "🎵", "🎤", "🎭", "📚", "🧩", "🎲", "🚲", "🚗", "✈️", "🏖️", "⛺", "🌅", "🌸", "🐶", "🐱", "📷", "🎸", "🧘", "🏋️", "⚽", "🎳", "🛍️", "💌", "❤️", "⭐", "🌈"];
|
||||
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||
const months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
|
||||
const today = new Date();
|
||||
@@ -24,6 +32,7 @@
|
||||
let draftDates = new Map();
|
||||
let viewYear = today.getFullYear();
|
||||
let viewMonth = today.getMonth();
|
||||
let activities = [];
|
||||
|
||||
function setMessage(element, message, success) {
|
||||
element.textContent = message;
|
||||
@@ -152,6 +161,7 @@
|
||||
draftDates = new Map(dates.map((date) => [date.date, date.type]));
|
||||
loginView.classList.add("hidden");
|
||||
adminView.classList.remove("hidden");
|
||||
await loadActivities();
|
||||
renderCalendar();
|
||||
renderDates();
|
||||
} catch (error) {
|
||||
@@ -160,6 +170,193 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderActivities() {
|
||||
activitiesList.innerHTML = "";
|
||||
emptyActivities.classList.toggle("hidden", activities.length > 0);
|
||||
const orderedActivities = [...activities].sort((first, second) => {
|
||||
return (first.type === "custom") - (second.type === "custom");
|
||||
});
|
||||
orderedActivities.forEach((activity) => {
|
||||
const row = document.createElement("li");
|
||||
row.className = `activity-row${activity.disabled ? " is-disabled" : ""}`;
|
||||
const identity = document.createElement("div");
|
||||
identity.className = "activity-identity";
|
||||
const icon = document.createElement("button");
|
||||
icon.className = "activity-row-icon";
|
||||
icon.type = "button";
|
||||
icon.textContent = activity.icon;
|
||||
icon.title = "Icon ändern";
|
||||
icon.setAttribute("aria-label", `${activity.name}: Icon ändern`);
|
||||
icon.disabled = activity.type === "custom";
|
||||
icon.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
toggleIconPicker(activity, identity);
|
||||
});
|
||||
const iconPicker = createIconPicker(activity);
|
||||
const name = document.createElement("span");
|
||||
name.className = "activity-row-name";
|
||||
name.textContent = activity.name;
|
||||
const meta = document.createElement("small");
|
||||
meta.textContent = `${activity.type === "custom" ? "Benutzerdefiniert" : activity.type === "short" ? "Kurz" : "Lang"}${activity.disabled ? " · Deaktiviert" : ""}`;
|
||||
identity.append(icon, name, meta);
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "activity-actions";
|
||||
const renameButton = createRowAction("✎", "Aktivität umbenennen", () => renameActivity(activity, identity, name, renameButton));
|
||||
const typeButton = createRowAction("", activity.type === "long" ? "Auf kurz ändern" : "Auf lang ändern", () => changeType(activity));
|
||||
typeButton.classList.add("type-action", activity.type === "long" ? "is-long" : "is-short");
|
||||
const durationIndicator = document.createElement("span");
|
||||
durationIndicator.className = "duration-indicator";
|
||||
typeButton.appendChild(durationIndicator);
|
||||
typeButton.disabled = activity.type === "custom";
|
||||
const disableButton = document.createElement("button");
|
||||
disableButton.className = "row-action icon-action";
|
||||
disableButton.type = "button";
|
||||
disableButton.textContent = activity.disabled ? "▶" : "⏸";
|
||||
disableButton.title = activity.disabled ? "Aktivität aktivieren" : "Aktivität deaktivieren";
|
||||
disableButton.setAttribute("aria-label", `${activity.name}: ${activity.disabled ? "Aktivität aktivieren" : "Aktivität deaktivieren"}`);
|
||||
disableButton.addEventListener("click", () => toggleActivity(activity));
|
||||
if (activity.type !== "custom") {
|
||||
const deleteButton = createRowAction("🗑", "Aktivität löschen", () => deleteActivity(activity));
|
||||
deleteButton.classList.add("is-danger");
|
||||
actions.append(renameButton, typeButton, disableButton, deleteButton);
|
||||
} else {
|
||||
actions.append(renameButton, disableButton);
|
||||
}
|
||||
identity.append(iconPicker);
|
||||
row.append(identity, actions);
|
||||
activitiesList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function createRowAction(symbol, label, action) {
|
||||
const button = document.createElement("button");
|
||||
button.className = "row-action icon-action";
|
||||
button.type = "button";
|
||||
button.textContent = symbol;
|
||||
button.title = label;
|
||||
button.setAttribute("aria-label", label);
|
||||
button.addEventListener("click", action);
|
||||
return button;
|
||||
}
|
||||
|
||||
function createIconPicker(activity, onSelect) {
|
||||
const picker = document.createElement("div");
|
||||
picker.className = "activity-icon-picker hidden";
|
||||
picker.setAttribute("role", "dialog");
|
||||
picker.setAttribute("aria-label", `${activity.name}: Icon auswählen`);
|
||||
activityIcons.forEach((icon) => {
|
||||
const option = document.createElement("button");
|
||||
option.type = "button";
|
||||
option.className = "icon-option";
|
||||
option.textContent = icon;
|
||||
option.title = `Icon ${icon}`;
|
||||
option.setAttribute("aria-label", `Icon ${icon} auswählen`);
|
||||
option.classList.toggle("is-selected", icon === activity.icon);
|
||||
option.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
picker.classList.add("hidden");
|
||||
if (onSelect) onSelect(icon);
|
||||
else if (icon !== activity.icon) updateActivity(activity, { icon });
|
||||
});
|
||||
picker.appendChild(option);
|
||||
});
|
||||
return picker;
|
||||
}
|
||||
|
||||
function toggleIconPicker(activity, identity) {
|
||||
const picker = identity.querySelector(".activity-icon-picker");
|
||||
if (!picker) return;
|
||||
document.querySelectorAll(".activity-icon-picker").forEach((item) => {
|
||||
if (item !== picker) item.classList.add("hidden");
|
||||
});
|
||||
picker.classList.toggle("hidden");
|
||||
}
|
||||
|
||||
async function updateActivity(activity, changes) {
|
||||
try {
|
||||
await request("/api/admin/activities", { method: "PUT", body: JSON.stringify({ originalName: activity.name, ...activity, ...changes }) });
|
||||
await loadActivities();
|
||||
setMessage(activityMessage, "Aktivität gespeichert.", true);
|
||||
} catch (error) {
|
||||
if (error.status === 401) showLogin("Deine Anmeldung ist abgelaufen.");
|
||||
else setMessage(activityMessage, error.message, false);
|
||||
}
|
||||
}
|
||||
|
||||
function renameActivity(activity, identity, nameElement, renameButton) {
|
||||
const existingInput = identity.querySelector(".rename-input");
|
||||
if (existingInput) return;
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.className = "activity-name-input rename-input";
|
||||
input.type = "text";
|
||||
input.maxLength = 100;
|
||||
input.value = activity.name;
|
||||
input.setAttribute("aria-label", `${activity.name}: neuer Name`);
|
||||
nameElement.replaceWith(input);
|
||||
|
||||
const cancelButton = createRowAction("×", "Umbenennen abbrechen", () => renderActivities());
|
||||
renameButton.textContent = "✓";
|
||||
renameButton.title = "Namen speichern";
|
||||
renameButton.setAttribute("aria-label", "Namen speichern");
|
||||
renameButton.onclick = async () => {
|
||||
const nextName = input.value.trim();
|
||||
if (!nextName || nextName === activity.name) {
|
||||
renderActivities();
|
||||
return;
|
||||
}
|
||||
await updateActivity(activity, { name: nextName });
|
||||
};
|
||||
renameButton.parentElement.insertBefore(cancelButton, renameButton.nextSibling);
|
||||
input.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") renameButton.click();
|
||||
if (event.key === "Escape") cancelButton.click();
|
||||
});
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
function changeType(activity) {
|
||||
updateActivity(activity, { type: activity.type === "long" ? "short" : "long" });
|
||||
}
|
||||
|
||||
|
||||
async function loadActivities() {
|
||||
try {
|
||||
activities = await request("/api/admin/activities", { method: "GET" });
|
||||
renderActivities();
|
||||
} catch (error) {
|
||||
if (error.status === 401) showLogin("Deine Anmeldung ist abgelaufen.");
|
||||
else setMessage(activityMessage, error.message, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleActivity(activity) {
|
||||
try {
|
||||
await request("/api/admin/activities", { method: "PUT", body: JSON.stringify({ originalName: activity.name, ...activity, disabled: !activity.disabled }) });
|
||||
await loadActivities();
|
||||
setMessage(activityMessage, "Aktivitätsstatus gespeichert.", true);
|
||||
} catch (error) { setMessage(activityMessage, error.message, false); }
|
||||
}
|
||||
|
||||
async function deleteActivity(activity) {
|
||||
if (!window.confirm(`Aktivität „${activity.name}“ wirklich löschen?`)) return;
|
||||
try {
|
||||
await request("/api/admin/activities", { method: "DELETE", body: JSON.stringify({ name: activity.name }) });
|
||||
await loadActivities();
|
||||
setMessage(activityMessage, "Aktivität gelöscht.", true);
|
||||
} catch (error) { setMessage(activityMessage, error.message, false); }
|
||||
}
|
||||
|
||||
function createPickerForNewActivity() {
|
||||
const draft = { name: "Neue Aktivität", icon: newActivityIcon.textContent, type: newActivityType.classList.contains("is-long") ? "long" : "short" };
|
||||
const picker = createIconPicker(draft, (icon) => { newActivityIcon.textContent = icon; });
|
||||
picker.classList.remove("hidden");
|
||||
picker.classList.add("activity-add-picker");
|
||||
newActivityIcon.parentElement.appendChild(picker);
|
||||
return picker;
|
||||
}
|
||||
|
||||
loginForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
setMessage(loginMessage, "", false);
|
||||
@@ -187,6 +384,48 @@
|
||||
}
|
||||
});
|
||||
|
||||
newActivityIcon.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
const existingPicker = newActivityIcon.parentElement.querySelector(".activity-add-picker");
|
||||
if (existingPicker) {
|
||||
existingPicker.remove();
|
||||
return;
|
||||
}
|
||||
createPickerForNewActivity();
|
||||
});
|
||||
newActivityType.addEventListener("click", () => {
|
||||
const isLong = newActivityType.classList.toggle("is-long");
|
||||
newActivityType.classList.toggle("is-short", !isLong);
|
||||
newActivityType.title = isLong ? "Auf kurz ändern" : "Auf lang ändern";
|
||||
newActivityType.setAttribute("aria-label", `Neue Aktivität: ${isLong ? "auf kurz" : "auf lang"} ändern`);
|
||||
});
|
||||
activityAddRow.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const activity = { name: newActivityName.value.trim(), icon: newActivityIcon.textContent, type: newActivityType.classList.contains("is-long") ? "long" : "short" };
|
||||
try {
|
||||
await request("/api/admin/activities", { method: "POST", body: JSON.stringify(activity) });
|
||||
newActivityName.value = "";
|
||||
newActivityIcon.textContent = "🧺";
|
||||
newActivityType.classList.remove("is-long");
|
||||
newActivityType.classList.add("is-short");
|
||||
await loadActivities();
|
||||
setMessage(activityMessage, "Aktivität hinzugefügt.", true);
|
||||
} catch (error) {
|
||||
if (error.status === 401) showLogin("Deine Anmeldung ist abgelaufen.");
|
||||
else setMessage(activityMessage, error.message, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll(".admin-nav-btn").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
document.querySelectorAll(".admin-nav-btn").forEach((item) => item.classList.toggle("is-active", item === button));
|
||||
document.querySelectorAll(".admin-panel").forEach((panel) => panel.classList.toggle("hidden", panel.id !== button.dataset.view));
|
||||
});
|
||||
});
|
||||
document.addEventListener("click", () => {
|
||||
document.querySelectorAll(".activity-icon-picker").forEach((picker) => picker.classList.add("hidden"));
|
||||
});
|
||||
|
||||
prevMonthBtn.addEventListener("click", () => {
|
||||
viewMonth -= 1;
|
||||
if (viewMonth < 0) { viewMonth = 11; viewYear -= 1; }
|
||||
|
||||
Reference in New Issue
Block a user