382 lines
16 KiB
Plaintext
382 lines
16 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>PlusFit24 – Admin Dashboard</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700;800&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
</head>
|
||
<body class="admin-body">
|
||
<div class="admin-layout">
|
||
|
||
<aside class="admin-sidebar">
|
||
<div class="logo admin-logo">Plusfit<span>24</span></div>
|
||
<nav class="admin-nav">
|
||
<a href="#" class="nav-link active" onclick="showSection('tarife', this)">📋 Tarife</a>
|
||
<a href="#" class="nav-link" onclick="showSection('kategorien', this)">🏷️ Kategorien</a>
|
||
<a href="#" class="nav-link" onclick="showSection('mitglieder', this)">👥 Mitglieder</a>
|
||
<a href="#" class="nav-link" onclick="showSection('einstellungen', this)">⚙️ Einstellungen</a>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<span>👤 <%= admin %></span>
|
||
<a href="/admin/logout" class="logout-link">Abmelden</a>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="admin-main">
|
||
<div class="admin-header">
|
||
<h1 id="sectionTitle">Tarife</h1>
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="stat-number"><%= stats.total || 0 %></div>
|
||
<div class="stat-label">Gesamt Mitglieder</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number"><%= stats.active_count || 0 %></div>
|
||
<div class="stat-label">Aktive Mitglieder</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number"><%= stats.last_30_days || 0 %></div>
|
||
<div class="stat-label">Letzte 30 Tage</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number"><%= stats.minors || 0 %></div>
|
||
<div class="stat-label">Minderjährige</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<% if (success) { %><div class="alert alert-success"><%= success %></div><% } %>
|
||
<% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
|
||
|
||
<!-- ===== TARIFE ===== -->
|
||
<section id="section-tarife" class="admin-section">
|
||
<div class="section-header">
|
||
<h2>Tarife verwalten</h2>
|
||
<button class="btn btn-primary" onclick="toggleModal('createTariffModal')">+ Neuer Tarif</button>
|
||
</div>
|
||
<div class="tariff-admin-grid">
|
||
<% tariffs.forEach(tariff => { %>
|
||
<div class="tariff-admin-card <%= tariff.active ? '' : 'inactive' %>">
|
||
<div class="tariff-admin-header">
|
||
<span class="tariff-status-badge <%= tariff.active ? 'active' : 'inactive' %>">
|
||
<%= tariff.active ? '✅ Aktiv' : '❌ Inaktiv' %>
|
||
</span>
|
||
<% if (tariff.category_name) { %>
|
||
<span class="category-pill"><%= tariff.category_name %></span>
|
||
<% } %>
|
||
</div>
|
||
<h3><%= tariff.name %></h3>
|
||
<div class="tariff-admin-details">
|
||
<span>💰 <%= Number(tariff.price_monthly).toFixed(2) %>€/Monat</span>
|
||
<span>📅 <%= tariff.duration_months %> Monate</span>
|
||
<span>📦 Startpaket: <%= Number(tariff.start_package_price).toFixed(2) %>€</span>
|
||
</div>
|
||
<div class="tariff-admin-actions">
|
||
<button class="btn btn-sm btn-outline" onclick="editTariff(<%= JSON.stringify(tariff) %>)">✏️ Bearbeiten</button>
|
||
<form method="POST" action="/admin/tariffs/<%= tariff.id %>/toggle" style="display:inline">
|
||
<button type="submit" class="btn btn-sm <%= tariff.active ? 'btn-warning' : 'btn-success' %>">
|
||
<%= tariff.active ? '⏸ Deaktivieren' : '▶ Aktivieren' %>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<% }) %>
|
||
<% if (tariffs.length === 0) { %>
|
||
<div class="no-data-card">Noch keine Tarife angelegt.</div>
|
||
<% } %>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== KATEGORIEN ===== -->
|
||
<section id="section-kategorien" class="admin-section hidden">
|
||
<div class="section-header">
|
||
<h2>Kategorien verwalten</h2>
|
||
<button class="btn btn-primary" onclick="toggleModal('createCategoryModal')">+ Neue Kategorie</button>
|
||
</div>
|
||
|
||
<div class="category-list">
|
||
<% if (categories.length === 0) { %>
|
||
<div class="no-data-card">Noch keine Kategorien angelegt.</div>
|
||
<% } %>
|
||
<% categories.forEach(cat => { %>
|
||
<div class="category-row">
|
||
<div class="category-info">
|
||
<span class="category-name">🏷️ <%= cat.name %></span>
|
||
<span class="category-meta">
|
||
<%= tariffs.filter(t => t.category_id === cat.id).length %> Tarif(e)
|
||
</span>
|
||
</div>
|
||
<div class="category-actions">
|
||
<button class="btn btn-sm btn-outline"
|
||
onclick="editCategory(<%= cat.id %>, '<%= cat.name.replace(/'/g, `\\'`) %>')">
|
||
✏️ Umbenennen
|
||
</button>
|
||
<form method="POST" action="/admin/categories/<%= cat.id %>/delete" style="display:inline"
|
||
onsubmit="return confirm('Kategorie \'<%= cat.name %>\' wirklich löschen?')">
|
||
<button type="submit" class="btn btn-sm btn-danger">🗑 Löschen</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<% }) %>
|
||
</div>
|
||
|
||
<div class="info-box">
|
||
<strong>ℹ️ Hinweis:</strong> Kategorien können nur gelöscht werden wenn keine Tarife zugeordnet sind.
|
||
Weise die Tarife zuerst einer anderen Kategorie zu.
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== MITGLIEDER ===== -->
|
||
<section id="section-mitglieder" class="admin-section hidden">
|
||
<div class="section-header">
|
||
<h2>Mitglieder</h2>
|
||
<input type="text" id="memberSearch" placeholder="Suche nach Name, E-Mail..."
|
||
class="search-input" onkeyup="filterMembers()">
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table class="admin-table" id="memberTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>E-Mail</th>
|
||
<th>Tarif</th>
|
||
<th>Geburtsdatum</th>
|
||
<th>Min.</th>
|
||
<th>IBAN</th>
|
||
<th>Angemeldet am</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<% memberships.forEach(m => { %>
|
||
<tr class="member-row" onclick="window.location='/admin/members/<%= m.id %>'" style="cursor:pointer">
|
||
<td><strong><%= m.salutation %> <%= m.first_name %> <%= m.last_name %></strong></td>
|
||
<td><%= m.email %></td>
|
||
<td><%= m.tariff_name || '–' %></td>
|
||
<td><%= m.birth_date ? new Date(m.birth_date).toLocaleDateString('de-DE') : '–' %></td>
|
||
<td><%= m.is_minor ? '⚠️ Ja' : 'Nein' %></td>
|
||
<td class="iban-cell"><%= m.iban ? m.iban.replace(/(.{4})/g, '$1 ').trim() : '–' %></td>
|
||
<td><%= new Date(m.created_at).toLocaleDateString('de-DE') %></td>
|
||
</tr>
|
||
<% }) %>
|
||
<% if (memberships.length === 0) { %>
|
||
<tr><td colspan="7" class="no-data">Noch keine Mitglieder registriert.</td></tr>
|
||
<% } %>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===== EINSTELLUNGEN ===== -->
|
||
<section id="section-einstellungen" class="admin-section hidden">
|
||
<h2>Passwort ändern</h2>
|
||
<div class="settings-card">
|
||
<form method="POST" action="/admin/change-password">
|
||
<div class="form-group">
|
||
<label>Aktuelles Passwort</label>
|
||
<div class="input-wrap"><input type="password" name="current_password" required></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Neues Passwort</label>
|
||
<div class="input-wrap"><input type="password" name="new_password" required minlength="8"></div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Neues Passwort bestätigen</label>
|
||
<div class="input-wrap"><input type="password" name="confirm_password" required minlength="8"></div>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">Passwort ändern</button>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Modal: Neuer Tarif -->
|
||
<div class="modal-overlay hidden" id="createTariffModal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3>Neuer Tarif</h3>
|
||
<button onclick="toggleModal('createTariffModal')" class="modal-close">✕</button>
|
||
</div>
|
||
<form method="POST" action="/admin/tariffs">
|
||
<div class="form-group">
|
||
<label>Name *</label>
|
||
<input type="text" name="name" required class="form-control" placeholder="z.B. Single 12 Monate">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Kategorie</label>
|
||
<select name="category_id" class="form-control">
|
||
<option value="">– Keine Kategorie –</option>
|
||
<% categories.forEach(cat => { %>
|
||
<option value="<%= cat.id %>"><%= cat.name %></option>
|
||
<% }) %>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Laufzeit (Monate) *</label>
|
||
<input type="number" name="duration_months" required class="form-control" min="1" value="12">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Preis/Monat (€) *</label>
|
||
<input type="number" name="price_monthly" required class="form-control" step="0.01" min="0">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Startpaket Preis (€)</label>
|
||
<input type="number" name="start_package_price" class="form-control" step="0.01" value="35.00">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Beschreibung</label>
|
||
<textarea name="description" class="form-control" rows="2"></textarea>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="toggleModal('createTariffModal')" class="btn btn-outline">Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Tarif erstellen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Tarif bearbeiten -->
|
||
<div class="modal-overlay hidden" id="editTariffModal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3>Tarif bearbeiten</h3>
|
||
<button onclick="toggleModal('editTariffModal')" class="modal-close">✕</button>
|
||
</div>
|
||
<form method="POST" id="editTariffForm">
|
||
<div class="form-group">
|
||
<label>Name *</label>
|
||
<input type="text" name="name" id="edit_name" required class="form-control">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Kategorie</label>
|
||
<select name="category_id" id="edit_category" class="form-control">
|
||
<option value="">– Keine Kategorie –</option>
|
||
<% categories.forEach(cat => { %>
|
||
<option value="<%= cat.id %>"><%= cat.name %></option>
|
||
<% }) %>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Laufzeit (Monate) *</label>
|
||
<input type="number" name="duration_months" id="edit_duration" required class="form-control" min="1">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Preis/Monat (€) *</label>
|
||
<input type="number" name="price_monthly" id="edit_price" required class="form-control" step="0.01" min="0">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Startpaket Preis (€)</label>
|
||
<input type="number" name="start_package_price" id="edit_start_package" class="form-control" step="0.01">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Beschreibung</label>
|
||
<textarea name="description" id="edit_description" class="form-control" rows="2"></textarea>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="toggleModal('editTariffModal')" class="btn btn-outline">Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Neue Kategorie -->
|
||
<div class="modal-overlay hidden" id="createCategoryModal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3>Neue Kategorie</h3>
|
||
<button onclick="toggleModal('createCategoryModal')" class="modal-close">✕</button>
|
||
</div>
|
||
<form method="POST" action="/admin/categories">
|
||
<div class="form-group">
|
||
<label>Kategoriename *</label>
|
||
<input type="text" name="name" required class="form-control" placeholder="z.B. Senioren">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="toggleModal('createCategoryModal')" class="btn btn-outline">Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Kategorie erstellen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Kategorie bearbeiten -->
|
||
<div class="modal-overlay hidden" id="editCategoryModal">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3>Kategorie umbenennen</h3>
|
||
<button onclick="toggleModal('editCategoryModal')" class="modal-close">✕</button>
|
||
</div>
|
||
<form method="POST" id="editCategoryForm">
|
||
<div class="form-group">
|
||
<label>Neuer Name *</label>
|
||
<input type="text" name="name" id="edit_cat_name" required class="form-control">
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" onclick="toggleModal('editCategoryModal')" class="btn btn-outline">Abbrechen</button>
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const sectionTitles = {
|
||
tarife: 'Tarife', kategorien: 'Kategorien',
|
||
mitglieder: 'Mitglieder', einstellungen: 'Einstellungen'
|
||
};
|
||
|
||
function showSection(name, el) {
|
||
document.querySelectorAll('.admin-section').forEach(s => s.classList.add('hidden'));
|
||
document.getElementById('section-' + name).classList.remove('hidden');
|
||
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||
if (el) el.classList.add('active');
|
||
document.getElementById('sectionTitle').textContent = sectionTitles[name] || name;
|
||
event && event.preventDefault();
|
||
}
|
||
|
||
function toggleModal(id) {
|
||
document.getElementById(id).classList.toggle('hidden');
|
||
}
|
||
|
||
function editTariff(tariff) {
|
||
document.getElementById('edit_name').value = tariff.name;
|
||
document.getElementById('edit_category').value = tariff.category_id || '';
|
||
document.getElementById('edit_duration').value = tariff.duration_months;
|
||
document.getElementById('edit_price').value = tariff.price_monthly;
|
||
document.getElementById('edit_start_package').value = tariff.start_package_price;
|
||
document.getElementById('edit_description').value = tariff.description || '';
|
||
document.getElementById('editTariffForm').action = '/admin/tariffs/' + tariff.id + '/update';
|
||
toggleModal('editTariffModal');
|
||
}
|
||
|
||
function editCategory(id, name) {
|
||
document.getElementById('edit_cat_name').value = name;
|
||
document.getElementById('editCategoryForm').action = '/admin/categories/' + id + '/update';
|
||
toggleModal('editCategoryModal');
|
||
}
|
||
|
||
function filterMembers() {
|
||
const q = document.getElementById('memberSearch').value.toLowerCase();
|
||
document.querySelectorAll('.member-row').forEach(row => {
|
||
row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none';
|
||
});
|
||
}
|
||
|
||
// URL-Hash auswerten (nach Redirect mit #kategorien etc.)
|
||
const hash = window.location.hash.replace('#', '');
|
||
if (hash && document.getElementById('section-' + hash)) {
|
||
const navLink = document.querySelector(`[onclick*="'${hash}'"]`);
|
||
showSection(hash, navLink);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|