djmdy
This commit is contained in:
parent
b59f256e7b
commit
f6b60af78b
@ -1301,3 +1301,45 @@ body:not(.admin-body) > * {
|
|||||||
background: #fee2e2;
|
background: #fee2e2;
|
||||||
border-color: var(--error);
|
border-color: var(--error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Rechnungshistorie in Mitglied-Detail ---- */
|
||||||
|
.karte-header-sub {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.invoice-history-wrap {
|
||||||
|
max-height: 272px; /* ca. 6 Zeilen */
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 0 0 14px 14px;
|
||||||
|
}
|
||||||
|
.invoice-history-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
.invoice-history-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: var(--bg);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border-bottom: 1.5px solid var(--border);
|
||||||
|
}
|
||||||
|
.invoice-history-table td {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.invoice-history-row:last-child td { border-bottom: none; }
|
||||||
|
.invoice-history-row:hover td { background: #f8f8ff; cursor: default; }
|
||||||
|
|||||||
@ -175,6 +175,11 @@ router.get('/members/:id', requireAdmin, async (req, res) => {
|
|||||||
'SELECT * FROM membership_pauses WHERE membership_id = ? ORDER BY pause_start DESC',
|
'SELECT * FROM membership_pauses WHERE membership_id = ? ORDER BY pause_start DESC',
|
||||||
[req.params.id]
|
[req.params.id]
|
||||||
);
|
);
|
||||||
|
const [invoices] = await db.query(`
|
||||||
|
SELECT * FROM invoices
|
||||||
|
WHERE membership_id = ?
|
||||||
|
ORDER BY period DESC, created_at DESC
|
||||||
|
`, [req.params.id]);
|
||||||
const [tariffs] = await db.query(
|
const [tariffs] = await db.query(
|
||||||
'SELECT * FROM tariffs WHERE active = 1 ORDER BY name ASC'
|
'SELECT * FROM tariffs WHERE active = 1 ORDER BY name ASC'
|
||||||
);
|
);
|
||||||
@ -183,6 +188,7 @@ router.get('/members/:id', requireAdmin, async (req, res) => {
|
|||||||
member: rows[0],
|
member: rows[0],
|
||||||
pauses,
|
pauses,
|
||||||
tariffs,
|
tariffs,
|
||||||
|
invoices,
|
||||||
admin: req.session.adminUser,
|
admin: req.session.adminUser,
|
||||||
success: req.query.success || null,
|
success: req.query.success || null,
|
||||||
error: req.query.error || null
|
error: req.query.error || null
|
||||||
|
|||||||
@ -303,6 +303,68 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ===== KARTE 5: Rechnungshistorie ===== -->
|
||||||
|
<div class="karte karte-full">
|
||||||
|
<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>
|
||||||
|
|
||||||
</div><!-- end karteikarte-grid -->
|
</div><!-- end karteikarte-grid -->
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user