Vertragsverwaltung_Plusfit24/views/admin/member-detail.ejs
2026-03-28 10:36:08 +00:00

545 lines
26 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PlusFit24 Mitglied #<%= member.id %></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 member-detail-page">
<div class="admin-layout">
<aside class="admin-sidebar">
<div class="logo admin-logo">Plusfit<span>24</span></div>
<nav class="admin-nav">
<a href="/admin" class="nav-link">📋 Tarife</a>
<a href="/admin#kategorien" class="nav-link">🏷️ Kategorien</a>
<a href="/admin#mitglieder" class="nav-link active">👥 Mitglieder</a>
<a href="/admin/contracts" class="nav-link">📑 Verträge</a>
<a href="/admin/billing" class="nav-link">💶 Abrechnung</a>
<a href="/admin/finance" class="nav-link">📊 Finanzen</a>
<a href="/admin/mailing" class="nav-link">📧 Mailing</a>
<a href="/dokumentation/handbuch.docx" class="nav-link" target="_blank">❓ Hilfe</a>
<a href="/admin#einstellungen" class="nav-link">⚙️ 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="detail-header">
<a href="/admin#mitglieder" class="btn btn-outline btn-sm">← Zurück</a>
<div class="detail-header-title">
<h1><%= member.first_name %> <%= member.last_name %></h1>
<span class="member-id-badge">Mitglied #<%= member.id %></span>
<span class="status-badge <%= member.status === 'active' ? 'active' : member.status === 'paused' ? 'warning' : member.status === 'pending' ? 'warning' : 'inactive' %>">
<%= member.status === 'active' ? '✅ Aktiv' : member.status === 'paused' ? '⏸ Pausiert' : member.status === 'pending' ? '⏳ Ausstehend' : '❌ Inaktiv' %>
</span>
<% if (member.status === 'pending') { %>
<form method="POST" action="/admin/members/<%= member.id %>/confirm" style="display:inline">
<button type="submit" class="btn btn-sm btn-success">✅ Manuell bestätigen</button>
</form>
<% } %>
<% if (member.is_minor) { %>
<span class="status-badge warning">⚠️ Minderjährig</span>
<% } %>
</div>
<div class="detail-header-actions">
<button class="btn btn-outline btn-sm" onclick="toggleModal('directMailModal')">📧 E-Mail senden</button>
<button class="btn btn-primary" id="editBtn" onclick="enableEdit()">✏️ Bearbeiten</button>
<button class="btn btn-success hidden" id="saveBtn" form="memberForm">💾 Speichern</button>
<button class="btn btn-outline hidden" id="cancelBtn" onclick="cancelEdit()">✕ Abbrechen</button>
</div>
</div>
<% if (success) { %><div class="alert alert-success"><%= success %></div><% } %>
<% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
<form method="POST" action="/admin/members/<%= member.id %>/update" id="memberForm">
<div class="karteikarte-grid">
<!-- ===== KARTE 1: Persönliche Daten ===== -->
<div class="karte">
<div class="karte-header"><span class="karte-icon">👤</span><h3>Persönliche Daten</h3></div>
<div class="karte-body">
<div class="karte-row">
<div class="karte-field">
<label>Anrede</label>
<select name="salutation" disabled class="karte-input">
<option value="Herr" <%= member.salutation === 'Herr' ? 'selected' : '' %>>Herr</option>
<option value="Frau" <%= member.salutation === 'Frau' ? 'selected' : '' %>>Frau</option>
<option value="Keine Angabe"<%= member.salutation === 'Keine Angabe'? 'selected' : '' %>>Keine Angabe</option>
</select>
</div>
<div class="karte-field">
<label>Titel</label>
<input type="text" name="title" value="<%= member.title || '' %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Vorname</label>
<input type="text" name="first_name" value="<%= member.first_name %>" disabled class="karte-input">
</div>
<div class="karte-field">
<label>Nachname</label>
<input type="text" name="last_name" value="<%= member.last_name %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Geburtsdatum</label>
<input type="date" name="birth_date" value="<%= member.birth_date ? new Date(member.birth_date).toISOString().split('T')[0] : '' %>" disabled class="karte-input">
</div>
<div class="karte-field">
<label>Telefon</label>
<input type="tel" name="phone" value="<%= member.phone || '' %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>E-Mail</label>
<input type="email" name="email" value="<%= member.email %>" disabled class="karte-input">
</div>
</div>
</div>
</div>
<!-- ===== KARTE 2: Adresse ===== -->
<div class="karte">
<div class="karte-header"><span class="karte-icon">📍</span><h3>Adresse</h3></div>
<div class="karte-body">
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Straße, Hausnummer</label>
<input type="text" name="street" value="<%= member.street %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Adresszusatz</label>
<input type="text" name="address_addition" value="<%= member.address_addition || '' %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field" style="max-width:140px">
<label>PLZ</label>
<input type="text" name="zip" value="<%= member.zip %>" disabled class="karte-input">
</div>
<div class="karte-field">
<label>Ort</label>
<input type="text" name="city" value="<%= member.city %>" disabled class="karte-input">
</div>
</div>
</div>
</div>
<!-- ===== KARTE 3: Vertrag ===== -->
<div class="karte">
<div class="karte-header"><span class="karte-icon">📄</span><h3>Vertrag</h3></div>
<div class="karte-body">
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Tarif</label>
<select name="tariff_id" disabled class="karte-input">
<% tariffs.forEach(t => { %>
<option value="<%= t.id %>" <%= member.tariff_id === t.id ? 'selected' : '' %>>
<%= t.name %> <%= Number(t.price_monthly).toFixed(2) %>€/Monat
</option>
<% }) %>
</select>
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Vereinbarter Preis/Monat <small>(vertraglich)</small></label>
<div class="input-wrap" style="border-radius:6px">
<input type="number" name="agreed_price" step="0.01" min="0"
value="<%= member.agreed_price ? Number(member.agreed_price).toFixed(2) : '' %>"
disabled class="karte-input" style="border:none;padding:5px 0">
<span style="font-size:0.8rem;color:var(--text-muted);padding-right:8px">€</span>
</div>
</div>
<div class="karte-field">
<label>Vereinbarte Laufzeit <small>(Monate)</small></label>
<input type="number" name="agreed_duration" min="1"
value="<%= member.agreed_duration || '' %>"
disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Startpaket (€) <small>(0 = erlassen)</small></label>
<input type="number" name="start_package_price" step="0.01" min="0"
value="<%= member.start_package_price != null ? Number(member.start_package_price).toFixed(2) : '35.00' %>"
disabled class="karte-input">
</div>
<div class="karte-field">
<label>Startpaket abgerechnet</label>
<input type="text"
value="<%= member.start_package_paid ? '✅ Ja' : '❌ Nein' %>"
disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Abschlussdatum</label>
<input type="text" value="<%= member.signup_date ? new Date(member.signup_date).toLocaleDateString('de-DE') : '' %>" disabled class="karte-input karte-readonly">
</div>
<div class="karte-field">
<label>Vertragsbeginn</label>
<input type="text" value="<%= member.contract_start ? new Date(member.contract_start).toLocaleDateString('de-DE') : '' %>" disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Vertragsende</label>
<input type="text" value="<%= member.contract_end ? new Date(member.contract_end).toLocaleDateString('de-DE') : '' %>" disabled class="karte-input karte-readonly">
</div>
<div class="karte-field">
<label>Eff. Ende <small>(inkl. Auszeit)</small></label>
<input type="text" value="<%= member.effective_end ? new Date(member.effective_end).toLocaleDateString('de-DE') : '' %>" disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Erste Zahlung am</label>
<input type="text" value="<%= member.first_payment_date ? new Date(member.first_payment_date).toLocaleDateString('de-DE') : '' %>" disabled class="karte-input karte-readonly">
</div>
<div class="karte-field">
<label>Erster Betrag</label>
<input type="text" value="<%= member.first_payment_amt ? Number(member.first_payment_amt).toFixed(2) + ' €' : '' %>" disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Status</label>
<select name="status" disabled class="karte-input">
<option value="active" <%= member.status === 'active' ? 'selected' : '' %>>✅ Aktiv</option>
<option value="inactive" <%= member.status === 'inactive' ? 'selected' : '' %>>❌ Inaktiv</option>
<option value="paused" <%= member.status === 'paused' ? 'selected' : '' %>>⏸ Pausiert</option>
</select>
</div>
<div class="karte-field">
<label>Auszeit gesamt</label>
<input type="text" value="<%= member.pause_months_total || 0 %> Monat(e)" disabled class="karte-input karte-readonly">
</div>
</div>
<!-- Auszeiten Tabelle -->
<div class="auszeit-section">
<div class="auszeit-header">
<span class="auszeit-title">⏸ Auszeiten</span>
</div>
<% if (pauses.length === 0) { %>
<p class="karte-empty" id="noPausesMsg">Keine Auszeiten eingetragen.</p>
<% } else { %>
<table class="pause-table">
<thead>
<tr>
<th>Von</th>
<th>Bis</th>
<th>Monate</th>
<th>Grund</th>
<th>Eingetragen am</th>
<th class="edit-only hidden">Aktion</th>
</tr>
</thead>
<tbody>
<% pauses.forEach(p => { %>
<tr>
<td><%= new Date(p.pause_start).toLocaleDateString('de-DE') %></td>
<td><%= new Date(p.pause_end).toLocaleDateString('de-DE') %></td>
<td><strong><%= p.pause_months %></strong></td>
<td><%= p.reason || '' %></td>
<td><%= new Date(p.created_at).toLocaleDateString('de-DE') %></td>
<td class="edit-only hidden">
<form method="POST" action="/admin/members/<%= member.id %>/pauses/<%= p.id %>/delete"
onsubmit="return confirm('Auszeit wirklich löschen?')">
<button type="submit" class="btn btn-sm btn-danger">🗑</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
<% } %>
<!-- Neue Auszeit nur im Bearbeiten-Modus sichtbar -->
<div class="neue-auszeit hidden" id="neueAuszeit">
<div class="neue-auszeit-title">+ Neue Auszeit eintragen</div>
<div class="auszeit-form-row">
<div class="karte-field">
<label>Von *</label>
<input type="date" name="pause_start" form="auszeitForm" required class="karte-input">
</div>
<div class="karte-field">
<label>Bis *</label>
<input type="date" name="pause_end" form="auszeitForm" required class="karte-input">
</div>
<div class="karte-field">
<label>Grund</label>
<input type="text" name="reason" form="auszeitForm" class="karte-input" placeholder="z.B. Urlaub, Verletzung...">
</div>
<div class="karte-field" style="max-width:120px; justify-content:flex-end; padding-top:22px">
<button type="submit" form="auszeitForm" class="btn btn-primary">Speichern</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ===== KARTE 4: Bankdaten ===== -->
<div class="karte">
<div class="karte-header"><span class="karte-icon">🏦</span><h3>Bankdaten / SEPA</h3></div>
<div class="karte-body">
<div class="karte-row">
<div class="karte-field">
<label>Geldinstitut</label>
<input type="text" name="bank_name" value="<%= member.bank_name || '' %>" disabled class="karte-input">
</div>
<div class="karte-field">
<label>Kontoinhaber</label>
<input type="text" name="account_holder" value="<%= member.account_holder || '' %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>SEPA akzeptiert</label>
<input type="text" value="<%= member.sepa_accepted ? '✅ Ja' : '❌ Nein' %>" disabled class="karte-input karte-readonly">
</div>
<div class="karte-field">
<label>AGB akzeptiert</label>
<input type="text" value="<%= member.agb_accepted ? '✅ Ja' : '❌ Nein' %>" disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>IBAN</label>
<input type="text" name="iban" id="ibanInput"
value="<%= member.iban ? member.iban.replace(/(.{4})/g, '$1 ').trim() : '' %>"
disabled class="karte-input karte-iban" maxlength="34" autocomplete="off">
<div class="iban-message" id="ibanMessage"></div>
</div>
</div>
<% if (member.is_minor) { %>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Einverständniserklärung (Erziehungsberechtigte)</label>
<input type="text" value="<%= member.guardian_consent ? '✅ Vorhanden' : '❌ Fehlt' %>" disabled class="karte-input karte-readonly">
</div>
</div>
<% } %>
</div>
</div>
<!-- ===== KARTE 5: Rechnungshistorie ===== -->
<div class="karte">
<div class="karte-header">
<span class="karte-icon">🧾</span>
<h3>Rechnungshistorie</h3>
<span class="karte-header-sub"><%= invoices.length %> Rechnung(en)</span>
</div>
<div class="karte-body" style="padding:0">
<% if (invoices.length === 0) { %>
<p class="karte-empty" style="padding:20px">Noch keine Rechnungen vorhanden.</p>
<% } else { %>
<div class="invoice-history-wrap">
<table class="invoice-history-table">
<thead>
<tr>
<th>Nr.</th>
<th>Periode</th>
<th>Betrag</th>
<th>Status</th>
<th>Datum</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<% invoices.forEach(inv => { %>
<tr class="invoice-history-row">
<td class="invoice-nr">PF24-<%= String(inv.id).padStart(6,'0') %></td>
<td><strong><%= inv.period %></strong></td>
<td>
<% if (inv.status === 'cancelled') { %>
<span style="text-decoration:line-through;color:var(--text-muted)">
<%= Number(inv.amount).toFixed(2).replace('.', ',') %> €
</span>
<% } else { %>
<strong><%= Number(inv.amount).toFixed(2).replace('.', ',') %> €</strong>
<% } %>
</td>
<td>
<span class="invoice-status <%= inv.status === 'paid' ? 'paid' : inv.status === 'cancelled' ? 'cancelled' : 'open' %>">
<%= inv.status === 'paid' ? '✅ Bezahlt' : inv.status === 'cancelled' ? '🚫 Storniert' : '🔴 Offen' %>
</span>
</td>
<td><small class="text-muted"><%= new Date(inv.created_at).toLocaleDateString('de-DE') %></small></td>
<td>
<% if (inv.status === 'cancelled') { %>
<a href="/admin/billing/export/storno-pdf/<%= inv.id %>"
class="btn btn-sm btn-storno" target="_blank">🚫 Storno-PDF</a>
<% } else { %>
<a href="/admin/billing/export/pdf/<%= inv.id %>"
class="btn btn-sm btn-outline" target="_blank">📄 Rechnung</a>
<% } %>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== KARTE 5: Zugangskarte / NFC ===== -->
<div class="karte">
<div class="karte-header">
<span class="karte-icon">🔑</span>
<h3>Zugangskarte / NFC</h3>
<% if (member.card_issued) { %>
<span class="status-badge active" style="margin-left:auto">✅ Ausgegeben</span>
<% } else { %>
<span class="status-badge inactive" style="margin-left:auto">⚠️ Nicht ausgegeben</span>
<% } %>
</div>
<div class="karte-body">
<!-- Access Token -->
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Access Token <small>(auf NFC-Karte schreiben)</small></label>
<div class="token-wrap">
<code class="token-display"><%= member.access_token || '' %></code>
<form method="POST" action="/admin/members/<%= member.id %>/regenerate-token"
onsubmit="return confirm('Token neu generieren? Die alte Karte funktioniert dann nicht mehr!')">
<button type="submit" class="btn btn-sm btn-warning token-regen-btn"
title="Neuen Token generieren (z.B. bei Kartenverlust)">
🔄 Neu
</button>
</form>
</div>
</div>
</div>
<!-- NFC UID -->
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>NFC Karten-UID <small>(vom Lesegerät auslesen)</small></label>
<form method="POST" action="/admin/members/<%= member.id %>/update-nfc"
id="nfcForm" style="display:flex;gap:8px;align-items:flex-start">
<input type="text" name="nfc_uid"
value="<%= member.nfc_uid || '' %>"
placeholder="z.B. A1:B2:C3:D4"
class="karte-input karte-iban"
style="text-transform:uppercase;flex:1">
<button type="submit" class="btn btn-sm btn-primary" style="white-space:nowrap;margin-top:1px">
💾 Speichern
</button>
</form>
</div>
</div>
<% if (member.card_issued_at) { %>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Karte ausgegeben am</label>
<input type="text"
value="<%= new Date(member.card_issued_at).toLocaleDateString('de-DE') %>"
disabled class="karte-input karte-readonly">
</div>
</div>
<% } %>
<!-- Info Box -->
<div class="nfc-info-box">
<strong> So wird die Karte eingerichtet:</strong>
<ol>
<li>Access Token mit NFC-Schreiber auf die Karte schreiben</li>
<li>Karten-UID mit dem Lesegerät auslesen und hier eintragen</li>
<li>Beim Zutritt: Lesegerät prüft UID + Token gegen die Datenbank</li>
</ol>
</div>
</div>
</div>
</div><!-- end karteikarte-grid -->
</form>
<!-- Auszeit Form (außerhalb von memberForm!) -->
<form id="auszeitForm" method="POST" action="/admin/members/<%= member.id %>/pauses/add"></form>
</main>
</div>
<!-- Modal: Direkte E-Mail -->
<div class="modal-overlay hidden" id="directMailModal">
<div class="modal">
<div class="modal-header">
<h3>E-Mail an <%= member.first_name %> <%= member.last_name %></h3>
<button onclick="toggleModal('directMailModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/mailing/send-one">
<input type="hidden" name="membership_id" value="<%= member.id %>">
<div class="form-group">
<label>Empfänger</label>
<input type="text" value="<%= member.email %>" class="form-control" disabled>
</div>
<div class="form-group">
<label>Betreff *</label>
<input type="text" name="subject" class="form-control" required placeholder="Betreff">
</div>
<div class="form-group">
<label>Nachricht *</label>
<textarea name="body" class="form-control" rows="8" required
placeholder="Schreibe hier deine Nachricht..."></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('directMailModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-primary">📧 Senden</button>
</div>
</form>
</div>
</div>
<script src="/js/iban.js"></script>
<script>
const editableFields = document.querySelectorAll('.karte-input:not(.karte-readonly)');
function enableEdit() {
editableFields.forEach(f => f.removeAttribute('disabled'));
document.getElementById('editBtn').classList.add('hidden');
document.getElementById('saveBtn').classList.remove('hidden');
document.getElementById('cancelBtn').classList.remove('hidden');
document.querySelectorAll('.karte').forEach(k => k.classList.add('edit-mode'));
// Auszeit-Bereich einblenden
document.getElementById('neueAuszeit').classList.remove('hidden');
document.querySelectorAll('.edit-only').forEach(el => el.classList.remove('hidden'));
// IBAN Validierung
const ibanInput = document.getElementById('ibanInput');
if (ibanInput) attachIBANValidation(ibanInput, null, document.getElementById('ibanMessage'));
}
function cancelEdit() {
window.location.reload();
}
</script>
</body>
</html>