feat: Add banner management feature and improve event/gallery image handling
- Introduced a new "Banners" feature, enabling banner creation, management, and display across the admin panel and frontend. - Enhanced image handling for events and gallery by converting images to optimized webp format. - Added `banners` table in the database schema for storing announcements. - Integrated new `/api/banners` route in backend for banner operations. - Updated `index.astro` to include banner display component. - Added supporting UI and APIs in the admin panel for banner management.
This commit is contained in:
@ -84,6 +84,24 @@ const title = 'Admin';
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="sec-banner" style="display:none">
|
||||
<h2>Banner verwalten</h2>
|
||||
<div class="events-row">
|
||||
<div class="card">
|
||||
<h3>Neuen Banner erstellen</h3>
|
||||
<label>Text<textarea id="banner-text" rows="3" placeholder="z.B. Wir sind vom 24.12. bis 02.01. geschlossen"></textarea></label>
|
||||
<label>Von (Datum)<input id="banner-start" type="date" /></label>
|
||||
<label>Bis (Datum)<input id="banner-end" type="date" /></label>
|
||||
<button id="btn-create-banner">Banner erstellen</button>
|
||||
<div id="banner-create-msg" class="muted"></div>
|
||||
</div>
|
||||
<div class="card" style="min-width:380px;">
|
||||
<h3>Banner-Liste</h3>
|
||||
<div id="banner-list" class="grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="sec-publish" style="display:none">
|
||||
<h2>Veröffentlichen</h2>
|
||||
<label>Commit-Message<input id="pub-msg" placeholder="Änderungen beschreiben" value="Update events" /></label>
|
||||
@ -109,10 +127,12 @@ const title = 'Admin';
|
||||
// UI-Bereiche für eingeloggte Nutzer einblenden
|
||||
document.getElementById('sec-events').style.display = '';
|
||||
document.getElementById('sec-gallery').style.display = '';
|
||||
document.getElementById('sec-banner').style.display = '';
|
||||
document.getElementById('sec-publish').style.display = '';
|
||||
// Direkt Events laden und auf Sektion fokussieren
|
||||
await loadEvents();
|
||||
await loadGallery();
|
||||
await loadBanners();
|
||||
document.getElementById('sec-events').scrollIntoView({ behavior: 'smooth' });
|
||||
} catch (e) {
|
||||
const el = document.getElementById('auth-status');
|
||||
@ -120,6 +140,7 @@ const title = 'Admin';
|
||||
// Kein Auto-Redirect, damit keine Schleife entsteht. Login-Button verwenden.
|
||||
document.getElementById('sec-events').style.display = 'none';
|
||||
document.getElementById('sec-gallery').style.display = 'none';
|
||||
document.getElementById('sec-banner').style.display = 'none';
|
||||
document.getElementById('sec-publish').style.display = 'none';
|
||||
}
|
||||
}
|
||||
@ -366,6 +387,94 @@ const title = 'Admin';
|
||||
} catch(e){ msg.textContent = 'Fehler: '+e.message }
|
||||
});
|
||||
|
||||
// ========== Banner ==========
|
||||
async function loadBanners() {
|
||||
const listEl = document.getElementById('banner-list');
|
||||
listEl.innerHTML = '<div class="muted">Lade...</div>';
|
||||
try {
|
||||
const data = await api('/api/banners');
|
||||
listEl.innerHTML = '';
|
||||
const bannersList = (data.banners || []).slice();
|
||||
bannersList.sort((a,b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
|
||||
bannersList.forEach((banner) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
const statusText = banner.isActive ? '✓ Aktiv' : '✗ Inaktiv';
|
||||
card.innerHTML = `
|
||||
<div><strong>${banner.text.substring(0, 60)}${banner.text.length > 60 ? '...' : ''}</strong></div>
|
||||
<div class="muted">Von: ${banner.startDate}</div>
|
||||
<div class="muted">Bis: ${banner.endDate}</div>
|
||||
<div class="pill">${statusText}</div>
|
||||
<div class="row-buttons">
|
||||
<button data-id="${banner.id}" class="btn-toggle-banner">${banner.isActive ? 'Deaktivieren' : 'Aktivieren'}</button>
|
||||
<button data-id="${banner.id}" class="btn-del-banner">Löschen</button>
|
||||
</div>`;
|
||||
listEl.appendChild(card);
|
||||
});
|
||||
|
||||
listEl.querySelectorAll('.btn-toggle-banner').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.getAttribute('data-id');
|
||||
if (!id) return;
|
||||
const banner = bannersList.find(b => b.id === id);
|
||||
if (!banner) return;
|
||||
try {
|
||||
await api(`/api/banners/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
text: banner.text,
|
||||
startDate: banner.startDate,
|
||||
endDate: banner.endDate,
|
||||
isActive: !banner.isActive
|
||||
})
|
||||
});
|
||||
await loadBanners();
|
||||
} catch(e){ alert('Fehler: '+e.message); }
|
||||
})
|
||||
});
|
||||
|
||||
listEl.querySelectorAll('.btn-del-banner').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.getAttribute('data-id');
|
||||
if (!id) return;
|
||||
if (!confirm('Banner wirklich löschen?')) return;
|
||||
try { await api(`/api/banners/${id}`, { method: 'DELETE' }); await loadBanners(); } catch(e){ alert('Fehler: '+e.message); }
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
listEl.innerHTML = '<div class="muted">Fehler beim Laden</div>';
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btn-create-banner').addEventListener('click', async () => {
|
||||
const text = (document.getElementById('banner-text')).value.trim();
|
||||
const startDate = (document.getElementById('banner-start')).value.trim();
|
||||
const endDate = (document.getElementById('banner-end')).value.trim();
|
||||
const msg = document.getElementById('banner-create-msg');
|
||||
|
||||
if (!text || !startDate || !endDate) {
|
||||
msg.textContent = 'Bitte alle Felder ausfüllen';
|
||||
return;
|
||||
}
|
||||
|
||||
msg.textContent = 'Erstelle Banner...';
|
||||
try {
|
||||
await api('/api/banners', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, startDate, endDate, isActive: true })
|
||||
});
|
||||
msg.textContent = 'Banner erstellt';
|
||||
(document.getElementById('banner-text')).value = '';
|
||||
(document.getElementById('banner-start')).value = '';
|
||||
(document.getElementById('banner-end')).value = '';
|
||||
await loadBanners();
|
||||
} catch(e){ msg.textContent = 'Fehler: '+e.message }
|
||||
});
|
||||
|
||||
refreshAuth();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../components/Layout.astro";
|
||||
import Hero from "../components/Hero.astro";
|
||||
import Banner from "../components/Banner.astro";
|
||||
import Welcome from "../components/Welcome.astro";
|
||||
import EventsGrid from "../components/EventsGrid.astro";
|
||||
import Drinks from "../components/Drinks.astro";
|
||||
@ -76,6 +77,7 @@ const images = [
|
||||
|
||||
<Layout>
|
||||
<Hero id="hero" />
|
||||
<Banner />
|
||||
<Welcome id="welcome" />
|
||||
<EventsGrid id="events" events={events} />
|
||||
<ImageCarousel id="gallery" images={images} />
|
||||
|
||||
Reference in New Issue
Block a user