696 lines
23 KiB
JavaScript
Executable File
696 lines
23 KiB
JavaScript
Executable File
/**
|
||
* What To Cook? - Frontend Application
|
||
*/
|
||
|
||
// Category icons mapping
|
||
const CATEGORY_ICONS = {
|
||
"Reis": "🍚",
|
||
"Nudeln": "🍝",
|
||
"Kartoffeln": "🥔",
|
||
"Teigig": "🍕",
|
||
"asiatisch": "🥢",
|
||
"Hausmannskost": "🥩",
|
||
"Suppen": "🍲",
|
||
"Eintöpfe": "🥘",
|
||
"Süßspeisen": "🥞",
|
||
"Fisch": "🐟"
|
||
};
|
||
|
||
const CATEGORIES = [
|
||
"Reis",
|
||
"Nudeln",
|
||
"Kartoffeln",
|
||
"Teigig",
|
||
"asiatisch",
|
||
"Hausmannskost",
|
||
"Suppen",
|
||
"Eintöpfe",
|
||
"Süßspeisen",
|
||
"Fisch"
|
||
];
|
||
|
||
const EFFORTS = ["gering", "mittel", "hoch"];
|
||
|
||
function getDishCategories(dish) {
|
||
if (!dish) return [];
|
||
if (Array.isArray(dish.categories) && dish.categories.length > 0) {
|
||
return dish.categories;
|
||
}
|
||
if (dish.category) {
|
||
return dish.category.split(",").map(c => c.trim()).filter(Boolean);
|
||
}
|
||
return [];
|
||
}
|
||
|
||
function renderCategoryBadges(dish) {
|
||
const cats = getDishCategories(dish);
|
||
if (cats.length === 0) return "";
|
||
return cats.map(cat => {
|
||
const icon = CATEGORY_ICONS[cat] || "🍲";
|
||
return `<span class="badge-tag badge-category"><span>${icon}</span> ${cat}</span>`;
|
||
}).join("");
|
||
}
|
||
|
||
// Application State
|
||
const state = {
|
||
activeTab: "generator",
|
||
slots: [
|
||
{ slot_number: 1, active: true, category: "", efforts: [], current_dish: null },
|
||
{ slot_number: 2, active: true, category: "", efforts: [], current_dish: null },
|
||
{ slot_number: 3, active: false, category: "", efforts: [], current_dish: null },
|
||
{ slot_number: 4, active: false, category: "", efforts: [], current_dish: null },
|
||
{ slot_number: 5, active: false, category: "", efforts: [], current_dish: null },
|
||
],
|
||
admin: {
|
||
dishes: [],
|
||
search: "",
|
||
category: "alle",
|
||
effort: "alle",
|
||
deleteTargetId: null
|
||
}
|
||
};
|
||
|
||
// --- Initialization ---
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
initTabs();
|
||
renderSlots();
|
||
populateDropdowns();
|
||
initGeneratorEvents();
|
||
initAdminEvents();
|
||
loadAdminDishes();
|
||
});
|
||
|
||
// --- Tab Navigation ---
|
||
function initTabs() {
|
||
const btnGenerator = document.getElementById("tab-btn-generator");
|
||
const btnAdmin = document.getElementById("tab-btn-admin");
|
||
const panelGenerator = document.getElementById("tab-generator");
|
||
const panelAdmin = document.getElementById("tab-admin");
|
||
|
||
btnGenerator.addEventListener("click", () => switchTab("generator"));
|
||
btnAdmin.addEventListener("click", () => switchTab("admin"));
|
||
|
||
function switchTab(tabName) {
|
||
state.activeTab = tabName;
|
||
if (tabName === "generator") {
|
||
btnGenerator.classList.add("active");
|
||
btnAdmin.classList.remove("active");
|
||
panelGenerator.classList.add("active");
|
||
panelAdmin.classList.remove("active");
|
||
panelAdmin.hidden = true;
|
||
panelGenerator.hidden = false;
|
||
} else {
|
||
btnAdmin.classList.add("active");
|
||
btnGenerator.classList.remove("active");
|
||
panelAdmin.classList.add("active");
|
||
panelGenerator.classList.remove("active");
|
||
panelGenerator.hidden = true;
|
||
panelAdmin.hidden = false;
|
||
loadAdminDishes();
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Populate Category Dropdowns ---
|
||
function populateDropdowns() {
|
||
const adminCatSelect = document.getElementById("admin-filter-category");
|
||
const modalCatGroup = document.getElementById("modal-category-group");
|
||
|
||
if (modalCatGroup) {
|
||
modalCatGroup.innerHTML = "";
|
||
}
|
||
|
||
CATEGORIES.forEach(cat => {
|
||
const icon = CATEGORY_ICONS[cat] || "🍲";
|
||
|
||
// Admin filter
|
||
if (adminCatSelect) {
|
||
const optFilter = document.createElement("option");
|
||
optFilter.value = cat;
|
||
optFilter.textContent = `${icon} ${cat}`;
|
||
adminCatSelect.appendChild(optFilter);
|
||
}
|
||
|
||
// Modal checkbox pill
|
||
if (modalCatGroup) {
|
||
const label = document.createElement("label");
|
||
label.className = "category-checkbox-label";
|
||
label.innerHTML = `
|
||
<input type="checkbox" name="modal-category" value="${cat}">
|
||
<span class="cat-pill">
|
||
<span>${icon}</span> ${cat}
|
||
</span>
|
||
`;
|
||
modalCatGroup.appendChild(label);
|
||
}
|
||
});
|
||
}
|
||
|
||
// --- Generator Slots Rendering ---
|
||
function renderSlots() {
|
||
const container = document.getElementById("slots-container");
|
||
container.innerHTML = "";
|
||
|
||
state.slots.forEach(slot => {
|
||
const card = document.createElement("div");
|
||
card.className = `slot-card ${slot.active ? "active" : "inactive"}`;
|
||
card.id = `slot-card-${slot.slot_number}`;
|
||
|
||
card.innerHTML = `
|
||
<div class="slot-card-header">
|
||
<div class="slot-title-group">
|
||
<span class="slot-number-badge">${slot.slot_number}</span>
|
||
<span class="slot-name">Gericht ${slot.slot_number}</span>
|
||
</div>
|
||
<label class="slot-toggle-label">
|
||
<span>${slot.active ? "Aktiv" : "Freigelassen"}</span>
|
||
<label class="switch">
|
||
<input type="checkbox" id="slot-toggle-${slot.slot_number}" ${slot.active ? "checked" : ""}>
|
||
<span class="slider"></span>
|
||
</label>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="slot-body">
|
||
<div class="form-group">
|
||
<label for="slot-cat-${slot.slot_number}" class="form-label">Kategorie:</label>
|
||
<select id="slot-cat-${slot.slot_number}" class="custom-select">
|
||
<option value="" ${slot.category === "" ? "selected" : ""}>🎲 Beliebig (Alle Kategorien)</option>
|
||
${CATEGORIES.map(cat => {
|
||
const icon = CATEGORY_ICONS[cat] || "🍲";
|
||
return `<option value="${cat}" ${slot.category === cat ? "selected" : ""}>${icon} ${cat}</option>`;
|
||
}).join("")}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">Aufwand (Mehrfachauswahl möglich):</label>
|
||
<div class="effort-chips-group" id="effort-chips-${slot.slot_number}">
|
||
<button type="button" class="effort-chip ${slot.efforts.includes("gering") ? "selected" : ""}" data-effort="gering">
|
||
<span>🟢</span> Gering
|
||
</button>
|
||
<button type="button" class="effort-chip ${slot.efforts.includes("mittel") ? "selected" : ""}" data-effort="mittel">
|
||
<span>🟡</span> Mittel
|
||
</button>
|
||
<button type="button" class="effort-chip ${slot.efforts.includes("hoch") ? "selected" : ""}" data-effort="hoch">
|
||
<span>🔴</span> Hoch
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(card);
|
||
|
||
// Event: Toggle Active
|
||
const toggleInput = card.querySelector(`#slot-toggle-${slot.slot_number}`);
|
||
toggleInput.addEventListener("change", (e) => {
|
||
slot.active = e.target.checked;
|
||
renderSlots();
|
||
updateActiveCounter();
|
||
});
|
||
|
||
// Event: Category Change
|
||
const catSelect = card.querySelector(`#slot-cat-${slot.slot_number}`);
|
||
catSelect.addEventListener("change", (e) => {
|
||
slot.category = e.target.value;
|
||
});
|
||
|
||
// Event: Effort Multi-Select Chips
|
||
const chips = card.querySelectorAll(`#effort-chips-${slot.slot_number} .effort-chip`);
|
||
chips.forEach(chip => {
|
||
chip.addEventListener("click", () => {
|
||
const effortVal = chip.getAttribute("data-effort");
|
||
const idx = slot.efforts.indexOf(effortVal);
|
||
if (idx > -1) {
|
||
slot.efforts.splice(idx, 1);
|
||
chip.classList.remove("selected");
|
||
} else {
|
||
slot.efforts.push(effortVal);
|
||
chip.classList.add("selected");
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
updateActiveCounter();
|
||
}
|
||
|
||
function updateActiveCounter() {
|
||
const activeCount = state.slots.filter(s => s.active).length;
|
||
const countEl = document.getElementById("active-slots-count");
|
||
if (countEl) countEl.textContent = activeCount;
|
||
|
||
const btnHero = document.getElementById("btn-roll-all");
|
||
if (btnHero) {
|
||
const btnText = btnHero.querySelector(".btn-text");
|
||
if (activeCount === 0) {
|
||
btnText.textContent = "Bitte mindestens 1 Gericht aktivieren";
|
||
btnHero.disabled = true;
|
||
} else if (activeCount === 1) {
|
||
btnText.textContent = "1 Gericht auslosen";
|
||
btnHero.disabled = false;
|
||
} else {
|
||
btnText.textContent = `${activeCount} Gerichte auslosen`;
|
||
btnHero.disabled = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Generator Events (Roll Logic) ---
|
||
function initGeneratorEvents() {
|
||
const btnRollAll = document.getElementById("btn-roll-all");
|
||
const btnRerollAll = document.getElementById("btn-reroll-all");
|
||
const btnBack = document.getElementById("btn-back-to-config");
|
||
|
||
btnRollAll.addEventListener("click", () => executeRoll(false));
|
||
btnRerollAll.addEventListener("click", () => executeRoll(true));
|
||
|
||
btnBack.addEventListener("click", () => {
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
});
|
||
}
|
||
|
||
async function executeRoll(isReroll = false) {
|
||
const activeSlots = state.slots.filter(s => s.active);
|
||
if (activeSlots.length === 0) {
|
||
showToast("Bitte aktiviere mindestens 1 Gericht.", "error");
|
||
return;
|
||
}
|
||
|
||
const btnRollAll = document.getElementById("btn-roll-all");
|
||
const btnRerollAll = document.getElementById("btn-reroll-all");
|
||
btnRollAll.disabled = true;
|
||
btnRerollAll.disabled = true;
|
||
|
||
try {
|
||
const payload = {
|
||
slots: activeSlots.map(s => ({
|
||
slot_number: s.slot_number,
|
||
category: s.category || null,
|
||
efforts: s.efforts.length > 0 ? s.efforts : null,
|
||
current_dish_id: isReroll && s.current_dish ? s.current_dish.id : null
|
||
}))
|
||
};
|
||
|
||
const res = await fetch("/api/roll", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
throw new Error(data.detail || "Fehler beim Auslosen.");
|
||
}
|
||
|
||
// Update slots state with rolled dishes
|
||
data.results.forEach(resItem => {
|
||
const slot = state.slots.find(s => s.slot_number === resItem.slot_number);
|
||
if (slot) {
|
||
slot.current_dish = resItem.dish;
|
||
}
|
||
});
|
||
|
||
renderResults(data.results);
|
||
showToast(isReroll ? "Alle Gerichte neu ausgelost!" : "Zufallsauswahl erfolgreich!", "success");
|
||
|
||
// Scroll smoothly to results
|
||
const resultsSection = document.getElementById("results-section");
|
||
resultsSection.classList.remove("hidden");
|
||
resultsSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
|
||
} catch (err) {
|
||
showToast(err.message, "error");
|
||
} finally {
|
||
btnRollAll.disabled = false;
|
||
btnRerollAll.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Render Results Grid
|
||
function renderResults(results) {
|
||
const container = document.getElementById("results-list");
|
||
const countBadge = document.getElementById("results-count-badge");
|
||
container.innerHTML = "";
|
||
|
||
const validDishes = results.filter(r => r.dish !== null);
|
||
countBadge.textContent = `${validDishes.length} ${validDishes.length === 1 ? "Gericht" : "Gerichte"}`;
|
||
|
||
results.forEach(item => {
|
||
const slot = state.slots.find(s => s.slot_number === item.slot_number);
|
||
const dish = item.dish;
|
||
const card = document.createElement("div");
|
||
card.className = "result-card";
|
||
card.id = `result-card-${item.slot_number}`;
|
||
|
||
if (!dish) {
|
||
card.innerHTML = `
|
||
<div class="result-card-top">
|
||
<span class="result-slot-label">Gericht ${item.slot_number}</span>
|
||
</div>
|
||
<div class="result-dish-name" style="color: var(--text-dim);">
|
||
⚠️ Keine passenden Gerichte gefunden
|
||
</div>
|
||
<p style="font-size: 0.85rem; color: var(--text-muted);">
|
||
Für die gewählte Kombination (Kategorie / Aufwand) gibt es noch keine Einträge.
|
||
</p>
|
||
`;
|
||
container.appendChild(card);
|
||
return;
|
||
}
|
||
|
||
const effortLabel = dish.effort.charAt(0).toUpperCase() + dish.effort.slice(1);
|
||
|
||
card.innerHTML = `
|
||
<div class="result-card-top">
|
||
<span class="result-slot-label">Gericht ${item.slot_number}</span>
|
||
</div>
|
||
<div class="result-dish-name" id="dish-name-${item.slot_number}">${dish.name}</div>
|
||
<div class="result-tags">
|
||
${renderCategoryBadges(dish)}
|
||
<span class="badge-tag badge-effort-${dish.effort}">
|
||
${effortLabel}
|
||
</span>
|
||
</div>
|
||
<div class="result-card-actions">
|
||
<button class="btn-reroll-single" data-slot="${item.slot_number}" title="Nur dieses Gericht neu würfeln">
|
||
<span class="dice-icon">🎲</span> Neu würfeln
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(card);
|
||
|
||
// Event: Single Reroll Button
|
||
const btnRerollSingle = card.querySelector(".btn-reroll-single");
|
||
btnRerollSingle.addEventListener("click", () => rerollSingleSlot(item.slot_number, btnRerollSingle, card));
|
||
});
|
||
}
|
||
|
||
// --- Single Slot Reroll Logic ---
|
||
async function rerollSingleSlot(slotNumber, btnElement, cardElement) {
|
||
const slot = state.slots.find(s => s.slot_number === slotNumber);
|
||
if (!slot || !slot.current_dish) return;
|
||
|
||
btnElement.classList.add("spinning");
|
||
cardElement.classList.add("rerolling");
|
||
|
||
// Collect other dishes currently on screen so we don't duplicate
|
||
const otherDishIds = state.slots
|
||
.filter(s => s.active && s.slot_number !== slotNumber && s.current_dish)
|
||
.map(s => s.current_dish.id);
|
||
|
||
try {
|
||
const payload = {
|
||
slot_number: slotNumber,
|
||
category: slot.category || null,
|
||
efforts: slot.efforts.length > 0 ? slot.efforts : null,
|
||
current_dish_id: slot.current_dish.id,
|
||
other_dish_ids: otherDishIds
|
||
};
|
||
|
||
const res = await fetch("/api/roll-single", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.detail || "Fehler beim Neu-Würfeln.");
|
||
|
||
if (data.status === "ok" && data.dish) {
|
||
slot.current_dish = data.dish;
|
||
const effortLabel = data.dish.effort.charAt(0).toUpperCase() + data.dish.effort.slice(1);
|
||
|
||
// Smoothly update the card content
|
||
const nameEl = cardElement.querySelector(`#dish-name-${slotNumber}`);
|
||
if (nameEl) nameEl.textContent = data.dish.name;
|
||
|
||
const tagsEl = cardElement.querySelector(".result-tags");
|
||
if (tagsEl) {
|
||
tagsEl.innerHTML = `
|
||
${renderCategoryBadges(data.dish)}
|
||
<span class="badge-tag badge-effort-${data.dish.effort}">
|
||
${effortLabel}
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
showToast(`Gericht ${slotNumber} neu gewürfelt: ${data.dish.name}`, "success");
|
||
} else {
|
||
showToast("Kein anderes passendes Gericht in dieser Kategorie gefunden.", "error");
|
||
}
|
||
} catch (err) {
|
||
showToast(err.message, "error");
|
||
} finally {
|
||
setTimeout(() => {
|
||
btnElement.classList.remove("spinning");
|
||
cardElement.classList.remove("rerolling");
|
||
}, 450);
|
||
}
|
||
}
|
||
|
||
// --- Admin Section & CRUD ---
|
||
function initAdminEvents() {
|
||
const searchInput = document.getElementById("admin-search-input");
|
||
const clearSearchBtn = document.getElementById("admin-search-clear");
|
||
const catFilter = document.getElementById("admin-filter-category");
|
||
const effortFilter = document.getElementById("admin-filter-effort");
|
||
|
||
searchInput.addEventListener("input", (e) => {
|
||
state.admin.search = e.target.value.trim();
|
||
clearSearchBtn.classList.toggle("hidden", state.admin.search.length === 0);
|
||
loadAdminDishes();
|
||
});
|
||
|
||
clearSearchBtn.addEventListener("click", () => {
|
||
searchInput.value = "";
|
||
state.admin.search = "";
|
||
clearSearchBtn.classList.add("hidden");
|
||
loadAdminDishes();
|
||
});
|
||
|
||
catFilter.addEventListener("change", (e) => {
|
||
state.admin.category = e.target.value;
|
||
loadAdminDishes();
|
||
});
|
||
|
||
effortFilter.addEventListener("change", (e) => {
|
||
state.admin.effort = e.target.value;
|
||
loadAdminDishes();
|
||
});
|
||
|
||
// Modal Open/Close
|
||
const dishModal = document.getElementById("dish-modal");
|
||
const btnOpenAdd = document.getElementById("btn-open-add-dish");
|
||
const btnCloseModal = document.getElementById("btn-close-modal");
|
||
const btnCancelModal = document.getElementById("btn-cancel-modal");
|
||
const dishForm = document.getElementById("dish-form");
|
||
|
||
btnOpenAdd.addEventListener("click", () => {
|
||
openDishModal();
|
||
});
|
||
|
||
btnCloseModal.addEventListener("click", () => dishModal.close());
|
||
btnCancelModal.addEventListener("click", () => dishModal.close());
|
||
|
||
dishForm.addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
await saveDishFromModal();
|
||
});
|
||
|
||
// Delete Modal
|
||
const deleteModal = document.getElementById("delete-modal");
|
||
const btnCancelDelete = document.getElementById("btn-cancel-delete");
|
||
const btnConfirmDelete = document.getElementById("btn-confirm-delete");
|
||
|
||
btnCancelDelete.addEventListener("click", () => deleteModal.close());
|
||
btnConfirmDelete.addEventListener("click", async () => {
|
||
if (state.admin.deleteTargetId) {
|
||
await executeDeleteDish(state.admin.deleteTargetId);
|
||
deleteModal.close();
|
||
}
|
||
});
|
||
}
|
||
|
||
async function loadAdminDishes() {
|
||
const listContainer = document.getElementById("admin-dishes-list");
|
||
const statsSummary = document.getElementById("admin-stats-summary");
|
||
const countLabel = document.getElementById("dishes-count-label");
|
||
|
||
try {
|
||
const params = new URLSearchParams();
|
||
if (state.admin.category && state.admin.category !== "alle") {
|
||
params.append("category", state.admin.category);
|
||
}
|
||
if (state.admin.effort && state.admin.effort !== "alle") {
|
||
params.append("effort", state.admin.effort);
|
||
}
|
||
if (state.admin.search) {
|
||
params.append("search", state.admin.search);
|
||
}
|
||
|
||
const res = await fetch(`/api/dishes?${params.toString()}`);
|
||
const dishes = await res.json();
|
||
state.admin.dishes = dishes;
|
||
|
||
// Load overall stats
|
||
const statsRes = await fetch("/api/stats");
|
||
const stats = await statsRes.json();
|
||
statsSummary.textContent = `${stats.total} Gerichte in der Datenbank`;
|
||
countLabel.textContent = `${dishes.length} ${dishes.length === 1 ? "Gericht" : "Gerichte"} angezeigt`;
|
||
|
||
if (dishes.length === 0) {
|
||
listContainer.innerHTML = `
|
||
<div class="loading-spinner">Keine Gerichte mit diesen Suchkriterien gefunden.</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
listContainer.innerHTML = "";
|
||
dishes.forEach(dish => {
|
||
const row = document.createElement("div");
|
||
row.className = "admin-dish-row";
|
||
const effortLabel = dish.effort.charAt(0).toUpperCase() + dish.effort.slice(1);
|
||
|
||
row.innerHTML = `
|
||
<div class="admin-dish-info">
|
||
<div class="admin-dish-name">${dish.name}</div>
|
||
<div class="admin-dish-badges">
|
||
${renderCategoryBadges(dish)}
|
||
<span class="badge-tag badge-effort-${dish.effort}">${effortLabel}</span>
|
||
</div>
|
||
</div>
|
||
<div class="admin-dish-actions">
|
||
<button class="btn-action-icon btn-edit" title="Bearbeiten" data-id="${dish.id}">✏️</button>
|
||
<button class="btn-action-icon btn-delete" title="Löschen" data-id="${dish.id}">🗑️</button>
|
||
</div>
|
||
`;
|
||
|
||
listContainer.appendChild(row);
|
||
|
||
row.querySelector(".btn-edit").addEventListener("click", () => openDishModal(dish));
|
||
row.querySelector(".btn-delete").addEventListener("click", () => confirmDeleteDish(dish));
|
||
});
|
||
|
||
} catch (err) {
|
||
listContainer.innerHTML = `<div class="loading-spinner" style="color: var(--danger);">Fehler beim Laden: ${err.message}</div>`;
|
||
}
|
||
}
|
||
|
||
function openDishModal(dish = null) {
|
||
const modal = document.getElementById("dish-modal");
|
||
const modalTitle = document.getElementById("modal-title");
|
||
const formId = document.getElementById("dish-form-id");
|
||
const nameInput = document.getElementById("dish-name-input");
|
||
const catCheckboxes = document.querySelectorAll('input[name="modal-category"]');
|
||
|
||
if (dish) {
|
||
modalTitle.textContent = "Gericht bearbeiten";
|
||
formId.value = dish.id;
|
||
nameInput.value = dish.name;
|
||
const cats = getDishCategories(dish);
|
||
catCheckboxes.forEach(cb => {
|
||
cb.checked = cats.includes(cb.value);
|
||
});
|
||
const radio = document.querySelector(`input[name="modal-effort"][value="${dish.effort}"]`);
|
||
if (radio) radio.checked = true;
|
||
} else {
|
||
modalTitle.textContent = "Neues Gericht anlegen";
|
||
formId.value = "";
|
||
nameInput.value = "";
|
||
catCheckboxes.forEach(cb => {
|
||
cb.checked = false;
|
||
});
|
||
const radio = document.querySelector(`input[name="modal-effort"][value="mittel"]`);
|
||
if (radio) radio.checked = true;
|
||
}
|
||
|
||
modal.showModal();
|
||
}
|
||
|
||
async function saveDishFromModal() {
|
||
const modal = document.getElementById("dish-modal");
|
||
const formId = document.getElementById("dish-form-id").value;
|
||
const name = document.getElementById("dish-name-input").value.trim();
|
||
const selectedCats = Array.from(document.querySelectorAll('input[name="modal-category"]:checked')).map(cb => cb.value);
|
||
const effortRadio = document.querySelector('input[name="modal-effort"]:checked');
|
||
const effort = effortRadio ? effortRadio.value : "mittel";
|
||
|
||
if (!name) {
|
||
showToast("Bitte gib einen Namen für das Gericht ein.", "error");
|
||
return;
|
||
}
|
||
if (selectedCats.length === 0) {
|
||
showToast("Bitte wähle mindestens eine Kategorie aus.", "error");
|
||
return;
|
||
}
|
||
if (!effort) {
|
||
showToast("Bitte wähle den Aufwand aus.", "error");
|
||
return;
|
||
}
|
||
|
||
const isEdit = !!formId;
|
||
const url = isEdit ? `/api/dishes/${formId}` : "/api/dishes";
|
||
const method = isEdit ? "PUT" : "POST";
|
||
|
||
try {
|
||
const res = await fetch(url, {
|
||
method: method,
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ name, categories: selectedCats, effort })
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.detail || "Fehler beim Speichern.");
|
||
|
||
showToast(isEdit ? "Gericht aktualisiert!" : "Gericht erfolgreich hinzugefügt!", "success");
|
||
modal.close();
|
||
loadAdminDishes();
|
||
} catch (err) {
|
||
showToast(err.message, "error");
|
||
}
|
||
}
|
||
|
||
function confirmDeleteDish(dish) {
|
||
state.admin.deleteTargetId = dish.id;
|
||
const deleteModal = document.getElementById("delete-modal");
|
||
const textEl = document.getElementById("delete-modal-text");
|
||
textEl.innerHTML = `Möchtest du <strong>"${dish.name}"</strong> wirklich löschen?`;
|
||
deleteModal.showModal();
|
||
}
|
||
|
||
async function executeDeleteDish(dishId) {
|
||
try {
|
||
const res = await fetch(`/api/dishes/${dishId}`, { method: "DELETE" });
|
||
const data = await res.json();
|
||
if (!res.ok) throw new Error(data.detail || "Fehler beim Löschen.");
|
||
|
||
showToast("Gericht wurde gelöscht.", "success");
|
||
loadAdminDishes();
|
||
} catch (err) {
|
||
showToast(err.message, "error");
|
||
}
|
||
}
|
||
|
||
// --- Toast System ---
|
||
function showToast(message, type = "info") {
|
||
const container = document.getElementById("toast-container");
|
||
if (!container) return;
|
||
|
||
const toast = document.createElement("div");
|
||
toast.className = `toast toast-${type}`;
|
||
const icon = type === "success" ? "✅" : (type === "error" ? "⚠️" : "ℹ️");
|
||
|
||
toast.innerHTML = `<span>${icon}</span><span>${message}</span>`;
|
||
container.appendChild(toast);
|
||
|
||
setTimeout(() => {
|
||
toast.style.transition = "opacity 0.3s, transform 0.3s";
|
||
toast.style.opacity = "0";
|
||
toast.style.transform = "translateY(-10px)";
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3500);
|
||
}
|