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

678 lines
29 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

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 Finanzübersicht</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">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
</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="/admin" class="nav-link">📋 Tarife</a>
<a href="/admin#kategorien" class="nav-link">🏷️ Kategorien</a>
<a href="/admin#mitglieder" class="nav-link">👥 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 active">📊 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">
<h1 class="finance-title">📊 Finanzübersicht</h1>
<% if (success) { %><div class="alert alert-success"><%= success %></div><% } %>
<% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
<!-- ===== KPI KARTEN ===== -->
<div class="finance-kpi-grid">
<div class="kpi-card kpi-blue">
<div class="kpi-label">Gesamtumsatz (bezahlt)</div>
<div class="kpi-value"><%= Number(totalRevenue.paid_total).toFixed(2).replace('.', ',') %> €</div>
<div class="kpi-sub"><%= totalRevenue.invoice_count %> Rechnungen gesamt</div>
</div>
<div class="kpi-card kpi-red">
<div class="kpi-label">Offene Posten</div>
<div class="kpi-value"><%= Number(totalRevenue.open_total).toFixed(2).replace('.', ',') %> €</div>
<div class="kpi-sub"><%= openInvoices.length %> offene Rechnungen</div>
</div>
<div class="kpi-card kpi-orange">
<div class="kpi-label">Rückläufer (offen)</div>
<div class="kpi-value"><%= chargebackStats.open_count %></div>
<div class="kpi-sub"><%= Number(chargebackStats.total_amount).toFixed(2).replace('.', ',') %> € gesamt</div>
</div>
<div class="kpi-card kpi-purple">
<div class="kpi-label">Mahngebühren (offen)</div>
<div class="kpi-value"><%= Number(dunningStats.open_total).toFixed(2).replace('.', ',') %> €</div>
<div class="kpi-sub"><%= dunningStats.open_count %> offene Mahnungen</div>
</div>
<div class="kpi-card kpi-green">
<div class="kpi-label">Mahngebühren (bezahlt)</div>
<div class="kpi-value"><%= Number(dunningStats.paid_total).toFixed(2).replace('.', ',') %> €</div>
<div class="kpi-sub">Aktueller Satz: <%= Number(dunningFee).toFixed(2).replace('.', ',') %> €</div>
</div>
<div class="kpi-card kpi-yellow">
<div class="kpi-label">Auslaufende Verträge</div>
<div class="kpi-value"><%= expiringContracts.length %></div>
<div class="kpi-sub">In den nächsten 3 Monaten</div>
</div>
</div>
<!-- ===== TABS ===== -->
<div class="finance-tabs">
<button class="ftab active" onclick="showTab('chart', this)">📈 Umsatzverlauf</button>
<button class="ftab" onclick="showTab('open', this)">🔴 Offene Posten</button>
<button class="ftab" onclick="showTab('chargebacks', this)">↩️ Rückläufer</button>
<button class="ftab" onclick="showTab('dunning', this)">📬 Mahngebühren</button>
<button class="ftab" onclick="showTab('expiring', this)">⏳ Auslaufende Verträge</button>
<button class="ftab" onclick="showTab('cancelled', this)">🚫 Storniert</button>
<button class="ftab" onclick="showTab('settings', this)">⚙️ Einstellungen</button>
</div>
<!-- ===== TAB: UMSATZVERLAUF ===== -->
<div class="ftab-content active" id="tab-chart">
<div class="finance-card">
<h3>Monatlicher Umsatz (letzte 12 Monate)</h3>
<canvas id="revenueChart" height="80"></canvas>
</div>
<div class="table-wrap" style="margin-top:16px">
<table class="admin-table">
<thead><tr><th>Periode</th><th>Rechnungen</th><th>Bezahlt</th><th>Offen</th><th>Gesamt</th></tr></thead>
<tbody>
<% monthlyRevenue.slice().reverse().forEach(m => { %>
<tr>
<td><strong><%= m.period %></strong></td>
<td><%= m.count %></td>
<td style="color:var(--success)"><%= Number(m.paid).toFixed(2).replace('.', ',') %> €</td>
<td style="color:var(--error)"><%= Number(m.open_amount).toFixed(2).replace('.', ',') %> €</td>
<td><strong><%= Number(m.total).toFixed(2).replace('.', ',') %> €</strong></td>
</tr>
<% }) %>
<% if (monthlyRevenue.length === 0) { %>
<tr><td colspan="5" class="no-data">Noch keine Abrechnungsdaten.</td></tr>
<% } %>
</tbody>
</table>
</div>
</div>
<!-- ===== TAB: OFFENE POSTEN ===== -->
<div class="ftab-content" id="tab-open">
<div class="finance-card">
<div class="section-header">
<h3>Offene Posten (<%= openInvoices.length %>)</h3>
</div>
<% if (openInvoices.length === 0) { %>
<p class="karte-empty">✅ Keine offenen Posten!</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead><tr><th>Mitglied</th><th>Tarif</th><th>Periode</th><th>Betrag</th><th>Rechnung Nr.</th><th>Aktion</th></tr></thead>
<tbody>
<% openInvoices.forEach(inv => { %>
<tr>
<td>
<strong><%= inv.last_name %>, <%= inv.first_name %></strong><br>
<small class="text-muted"><%= inv.email %></small>
</td>
<td><%= inv.tariff_name || '' %></td>
<td><%= inv.period %></td>
<td style="color:var(--error)"><strong><%= Number(inv.amount).toFixed(2).replace('.', ',') %> €</strong></td>
<td class="invoice-nr">PF24-<%= String(inv.id).padStart(6,'0') %></td>
<td>
<a href="/admin/billing?period=<%= inv.period %>" class="btn btn-sm btn-outline">
Zur Abrechnung
</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: RÜCKLÄUFER ===== -->
<div class="ftab-content" id="tab-chargebacks">
<div class="finance-card">
<div class="section-header">
<h3>Rückläufer</h3>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-warning btn-sm" onclick="toggleModal('allDunningModal')">📬 Alle mahnen</button>
<button class="btn btn-outline btn-sm" onclick="toggleModal('addChargebackModal')">+ Manuell eintragen</button>
<button class="btn btn-outline btn-sm" onclick="toggleModal('importChargebackModal')">📥 CSV Import</button>
<a href="/admin/finance/chargebacks/sepa-export" class="btn btn-primary btn-sm">📥 SEPA Nachforderung</a>
</div>
</div>
<% if (chargebacks.length === 0) { %>
<p class="karte-empty">Keine Rückläufer vorhanden.</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead><tr><th>Datum</th><th>Mitglied</th><th>Periode</th><th>Betrag</th><th>Grund</th><th>Status</th><th>Aktion</th></tr></thead>
<tbody>
<% chargebacks.forEach(c => { %>
<tr>
<td><%= new Date(c.chargeback_date).toLocaleDateString('de-DE') %></td>
<td><strong><%= c.last_name %>, <%= c.first_name %></strong></td>
<td><%= c.period %></td>
<td style="color:var(--error)"><strong><%= Number(c.amount).toFixed(2).replace('.', ',') %> €</strong></td>
<td><%= c.reason || '' %></td>
<td>
<span class="invoice-status <%= c.status === 'resolved' ? 'paid' : 'open' %>">
<%= c.status === 'resolved' ? '✅ Erledigt' : '🔴 Offen' %>
</span>
</td>
<td>
<div style="display:flex;gap:6px;flex-wrap:wrap">
<% if (c.status === 'open') { %>
<button type="button" class="btn btn-sm btn-warning"
onclick="openDunningModal(<%= c.id %>, '<%= c.last_name %>, <%= c.first_name %>', <%= c.amount %>, '<%= c.period %>');event.stopPropagation()">
📬 Mahnen
</button>
<form method="POST" action="/admin/finance/chargebacks/<%= c.id %>/resolve" style="display:inline">
<button type="submit" class="btn btn-sm btn-success">✅ Erledigt</button>
</form>
<% } %>
</div>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: MAHNGEBÜHREN ===== -->
<div class="ftab-content" id="tab-dunning">
<div class="finance-card">
<div class="section-header">
<h3>Mahngebühren</h3>
<button class="btn btn-primary btn-sm" onclick="toggleModal('addDunningModal')">+ Mahngebühr eintragen</button>
</div>
<% if (dunnings.length === 0) { %>
<p class="karte-empty">Keine Mahngebühren eingetragen.</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead><tr><th>Datum</th><th>Mitglied</th><th>Betrag</th><th>Grund</th><th>Status</th><th>Aktion</th></tr></thead>
<tbody>
<% dunnings.forEach(d => { %>
<tr>
<td><%= new Date(d.issued_date).toLocaleDateString('de-DE') %></td>
<td><strong><%= d.last_name %>, <%= d.first_name %></strong></td>
<td><strong><%= Number(d.amount).toFixed(2).replace('.', ',') %> €</strong></td>
<td><%= d.reason %></td>
<td>
<span class="invoice-status <%= d.status === 'paid' ? 'paid' : d.status === 'cancelled' ? 'cancelled' : 'open' %>">
<%= d.status === 'paid' ? '✅ Bezahlt' : d.status === 'cancelled' ? '❌ Storniert' : '🔴 Offen' %>
</span>
<% if (d.paid_at) { %><br><small class="text-muted"><%= new Date(d.paid_at).toLocaleDateString('de-DE') %></small><% } %>
</td>
<td>
<div style="display:flex;gap:6px">
<% if (d.status === 'open') { %>
<form method="POST" action="/admin/finance/dunning/<%= d.id %>/paid" style="display:inline">
<button type="submit" class="btn btn-sm btn-success">✅</button>
</form>
<form method="POST" action="/admin/finance/dunning/<%= d.id %>/cancel" style="display:inline"
onsubmit="return confirm('Mahngebühr stornieren?')">
<button type="submit" class="btn btn-sm btn-danger">✕</button>
</form>
<% } %>
</div>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: AUSLAUFENDE VERTRÄGE ===== -->
<div class="ftab-content" id="tab-expiring">
<div class="finance-card">
<h3>Auslaufende Verträge nächste 3 Monate (<%= expiringContracts.length %>)</h3>
<% if (expiringContracts.length === 0) { %>
<p class="karte-empty">Keine auslaufenden Verträge in den nächsten 3 Monaten.</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead><tr><th>Mitglied</th><th>Tarif</th><th>Vertragsende</th><th>Restlaufzeit</th><th>Monatsbeitrag</th><th>Aktion</th></tr></thead>
<tbody>
<% expiringContracts.forEach(m => { %>
<%
const endDate = new Date(m.effective_end);
const today = new Date();
const diffDays = Math.ceil((endDate - today) / (1000*60*60*24));
const urgency = diffDays <= 30 ? 'urgent' : diffDays <= 60 ? 'warning' : 'normal';
%>
<tr>
<td>
<strong><%= m.last_name %>, <%= m.first_name %></strong><br>
<small class="text-muted"><%= m.email %></small>
</td>
<td><%= m.tariff_name %></td>
<td><strong><%= endDate.toLocaleDateString('de-DE') %></strong></td>
<td>
<span class="expiry-badge expiry-<%= urgency %>">
noch <%= diffDays %> Tage
</span>
</td>
<td><%= Number(m.price_monthly).toFixed(2).replace('.', ',') %> €</td>
<td>
<div style="display:flex;gap:6px">
<a href="/admin/members/<%= m.id %>" class="btn btn-sm btn-outline">Karteikarte</a>
<form method="POST" action="/admin/send-renewal/<%= m.id %>" style="display:inline">
<button type="submit" class="btn btn-sm btn-primary" title="Verlängerungs-E-Mail senden">📧 E-Mail</button>
</form>
</div>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: STORNIERT ===== -->
<div class="ftab-content" id="tab-cancelled">
<div class="finance-card">
<h3>Stornierte Rechnungen (<%= cancelledInvoices.length %>)</h3>
<% if (cancelledInvoices.length === 0) { %>
<p class="karte-empty">Keine stornierten Rechnungen vorhanden.</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>Nr.</th>
<th>Mitglied</th>
<th>Tarif</th>
<th>Periode</th>
<th>Betrag</th>
<th>Storniert am</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<% cancelledInvoices.forEach(inv => { %>
<tr>
<td class="invoice-nr">PF24-<%= String(inv.id).padStart(6,'0') %></td>
<td>
<strong><%= inv.last_name %>, <%= inv.first_name %></strong><br>
<small class="text-muted"><%= inv.email %></small>
</td>
<td><%= inv.tariff_name || '' %></td>
<td><%= inv.period %></td>
<td><span style="text-decoration:line-through;color:var(--text-muted)">
<%= Number(inv.amount).toFixed(2).replace('.', ',') %> €
</span></td>
<td><%= new Date(inv.created_at).toLocaleDateString('de-DE') %></td>
<td>
<a href="/admin/billing?period=<%= inv.period %>" class="btn btn-sm btn-outline">
Zur Abrechnung
</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: EINSTELLUNGEN ===== -->
<div class="ftab-content" id="tab-settings">
<div class="finance-card" style="max-width:400px">
<h3>Finanz-Einstellungen</h3>
<form method="POST" action="/admin/finance/settings">
<div class="form-group" style="margin-top:16px">
<label>Mahngebühr (€)</label>
<div class="input-wrap">
<input type="number" name="dunning_fee" step="0.01" min="0"
value="<%= Number(dunningFee).toFixed(2) %>" class="form-control">
</div>
</div>
<button type="submit" class="btn btn-primary" style="margin-top:12px">💾 Speichern</button>
</form>
</div>
</div>
</main>
</div>
<!-- Modal: Rückläufer manuell -->
<div class="modal-overlay hidden" id="addChargebackModal">
<div class="modal">
<div class="modal-header">
<h3>Rückläufer eintragen</h3>
<button onclick="toggleModal('addChargebackModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/finance/chargebacks/add">
<div class="form-group">
<label>Mitglied *</label>
<select name="membership_id" class="form-control" required onchange="loadMemberInvoices(this.value)">
<option value=""> Mitglied wählen </option>
<% members.forEach(m => { %>
<option value="<%= m.id %>"><%= m.last_name %>, <%= m.first_name %></option>
<% }) %>
</select>
</div>
<div class="form-group">
<label>Rechnung (optional)</label>
<select name="invoice_id" class="form-control" id="invoiceSelect">
<option value=""> Rechnung wählen </option>
<% openInvoicesDropdown.forEach(i => { %>
<option value="<%= i.id %>" data-member="<%= i.membership_id %>">
<%= i.period %> <%= Number(i.amount).toFixed(2).replace('.', ',') %> € (<%= i.last_name %>, <%= i.first_name %>)
</option>
<% }) %>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label>Periode *</label>
<input type="month" name="period" class="form-control" required>
</div>
<div class="form-group">
<label>Betrag (€) *</label>
<input type="number" name="amount" step="0.01" min="0" class="form-control" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Datum *</label>
<input type="date" name="chargeback_date" class="form-control" required
value="<%= new Date().toISOString().split('T')[0] %>">
</div>
<div class="form-group">
<label>Grund</label>
<input type="text" name="reason" class="form-control" value="SEPA Rücklastschrift">
</div>
</div>
<div class="form-group">
<label>Notizen</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('addChargebackModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-primary">Eintragen</button>
</div>
</form>
</div>
</div>
<!-- Modal: CSV Import Rückläufer -->
<div class="modal-overlay hidden" id="importChargebackModal">
<div class="modal">
<div class="modal-header">
<h3>Rückläufer CSV Import</h3>
<button onclick="toggleModal('importChargebackModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/finance/chargebacks/import"
enctype="multipart/form-data">
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:16px">
Format pro Zeile: <code>IBAN;Betrag;Datum;Grund</code><br>
Beispiel: <code>DE89370400440532013000;29,95;2026-04-05;Rücklastschrift</code>
</p>
<!-- Option 1: Datei -->
<div class="form-group">
<label>📁 CSV-Datei hochladen</label>
<div class="file-upload-wrap">
<input type="file" name="csv_file" id="csvFile" accept=".csv,.txt"
onchange="showFileName(this)">
<label for="csvFile" class="file-upload-label">
<span id="fileNameDisplay">Datei auswählen...</span>
</label>
</div>
</div>
<div class="import-divider"><span>oder</span></div>
<!-- Option 2: Textfeld -->
<div class="form-group">
<label>📋 Daten einfügen</label>
<textarea name="csv_data" class="form-control" rows="5"
placeholder="IBAN;Betrag;Datum;Grund&#10;DE89370400440532013000;29,95;2026-04-05;Rücklastschrift"></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('importChargebackModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-primary">📥 Importieren</button>
</div>
</form>
</div>
</div>
<!-- Modal: Mahngebühr eintragen -->
<div class="modal-overlay hidden" id="addDunningModal">
<div class="modal">
<div class="modal-header">
<h3>Mahngebühr eintragen</h3>
<button onclick="toggleModal('addDunningModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/finance/dunning/add">
<div class="form-group">
<label>Mitglied *</label>
<select name="membership_id" class="form-control" required>
<option value=""> Mitglied wählen </option>
<% members.forEach(m => { %>
<option value="<%= m.id %>"><%= m.last_name %>, <%= m.first_name %></option>
<% }) %>
</select>
</div>
<div class="form-group">
<label>Rechnung (optional)</label>
<select name="invoice_id" class="form-control">
<option value=""> Rechnung wählen </option>
<% openInvoicesDropdown.forEach(i => { %>
<option value="<%= i.id %>">
<%= i.period %> <%= Number(i.amount).toFixed(2).replace('.', ',') %> € (<%= i.last_name %>, <%= i.first_name %>)
</option>
<% }) %>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label>Betrag (€) *</label>
<input type="number" name="amount" step="0.01" min="0"
value="<%= Number(dunningFee).toFixed(2) %>" class="form-control" required>
</div>
<div class="form-group">
<label>Datum *</label>
<input type="date" name="issued_date" class="form-control" required
value="<%= new Date().toISOString().split('T')[0] %>">
</div>
</div>
<div class="form-group">
<label>Grund</label>
<input type="text" name="reason" class="form-control" value="Mahngebühr">
</div>
<div class="form-group">
<label>Notizen</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('addDunningModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-primary">Eintragen</button>
</div>
</form>
</div>
</div>
<!-- Modal: Einzelne Mahngebühr für Rückläufer -->
<div class="modal-overlay hidden" id="singleDunningModal">
<div class="modal">
<div class="modal-header">
<h3>Mahngebühr zuweisen</h3>
<button onclick="toggleModal('singleDunningModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/finance/dunning/add-from-chargeback">
<input type="hidden" name="chargeback_id" id="dunning_membership_id">
<input type="hidden" name="invoice_id" value="">
<div class="form-group">
<label>Mitglied</label>
<input type="text" id="dunning_member_name" class="form-control" disabled>
</div>
<div class="form-row">
<div class="form-group">
<label>Mahngebühr (€) *</label>
<input type="number" name="amount" id="dunning_amount" step="0.01" min="0"
class="form-control" required>
</div>
<div class="form-group">
<label>Datum *</label>
<input type="date" name="issued_date" class="form-control" required
value="<%= new Date().toISOString().split('T')[0] %>">
</div>
</div>
<div class="form-group">
<label>Grund</label>
<input type="text" name="reason" class="form-control" value="Mahngebühr Rücklastschrift">
</div>
<div class="form-group">
<label>Notizen</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('singleDunningModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-primary">📬 Mahngebühr eintragen</button>
</div>
</form>
</div>
</div>
<!-- Modal: Alle offenen Rückläufer mahnen -->
<div class="modal-overlay hidden" id="allDunningModal">
<div class="modal">
<div class="modal-header">
<h3>Alle offenen Rückläufer mahnen</h3>
<button onclick="toggleModal('allDunningModal')" class="modal-close">✕</button>
</div>
<form method="POST" action="/admin/finance/chargebacks/dunning-all">
<p style="font-size:0.9rem;margin-bottom:16px;color:var(--text-muted)">
Trägt für alle offenen Rückläufer automatisch eine Mahngebühr ein.
</p>
<div class="form-row">
<div class="form-group">
<label>Mahngebühr pro Rückläufer (€) *</label>
<input type="number" name="amount" step="0.01" min="0"
value="<%= Number(dunningFee).toFixed(2) %>" class="form-control" required>
</div>
<div class="form-group">
<label>Datum *</label>
<input type="date" name="issued_date" class="form-control" required
value="<%= new Date().toISOString().split('T')[0] %>">
</div>
</div>
<div class="form-group">
<label>Grund</label>
<input type="text" name="reason" class="form-control" value="Mahngebühr Rücklastschrift">
</div>
<div class="modal-footer">
<button type="button" onclick="toggleModal('allDunningModal')" class="btn btn-outline">Abbrechen</button>
<button type="submit" class="btn btn-warning"
onclick="return confirm('Für alle offenen Rückläufer eine Mahngebühr eintragen?')">
📬 Alle mahnen
</button>
</div>
</form>
</div>
</div>
<script>
const dunningDefaultFee = <%= Number(dunningFee).toFixed(2) %>;
// Chart
const ctx = document.getElementById('revenueChart').getContext('2d');
const chartData = <%- JSON.stringify(monthlyRevenue) %>;
new Chart(ctx, {
type: 'bar',
data: {
labels: chartData.map(m => m.period),
datasets: [
{
label: 'Bezahlt (€)',
data: chartData.map(m => parseFloat(m.paid) || 0),
backgroundColor: 'rgba(22, 163, 74, 0.7)',
borderRadius: 6
},
{
label: 'Offen (€)',
data: chartData.map(m => parseFloat(m.open_amount) || 0),
backgroundColor: 'rgba(220, 38, 38, 0.5)',
borderRadius: 6
}
]
},
options: {
responsive: true,
plugins: { legend: { position: 'top' } },
scales: { y: { beginAtZero: true, ticks: { callback: v => v + ' €' } } }
}
});
// Tabs
const tabMap = {
chart: 0, open: 1, chargebacks: 2, dunning: 3, expiring: 4, cancelled: 5, settings: 6
};
function showTab(name, el) {
document.querySelectorAll('.ftab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.ftab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
const btn = el || document.querySelectorAll('.ftab')[tabMap[name]];
if (btn) btn.classList.add('active');
history.replaceState(null, '', '#' + name);
}
// Beim Laden Hash auswerten
document.addEventListener('DOMContentLoaded', () => {
const hash = window.location.hash.replace('#', '');
if (hash && document.getElementById('tab-' + hash)) {
showTab(hash);
}
});
function toggleModal(id) {
document.getElementById(id).classList.toggle('hidden');
}
function showFileName(input) {
const display = document.getElementById('fileNameDisplay');
display.textContent = input.files.length > 0 ? input.files[0].name : 'Datei auswählen...';
}
function openDunningModal(chargebackId, memberName, amount, period) {
document.getElementById('dunning_member_name').value = memberName;
document.getElementById('dunning_amount').value = dunningDefaultFee;
document.getElementById('dunning_membership_id').value = chargebackId;
toggleModal('singleDunningModal');
}
</script>
</body>
</html>