Ändern der Pause in Auszeit

This commit is contained in:
cay 2026-03-27 11:49:41 +00:00
parent b7c1c5df37
commit f91e2f78ca
3 changed files with 198 additions and 100 deletions

View File

@ -1027,3 +1027,45 @@ body {
border-color: var(--error) !important;
background: #fff5f5 !important;
}
/* ---- Auszeit-Bereich in Karte 3 ---- */
.auszeit-section {
margin-top: 20px;
border-top: 1.5px solid var(--border);
padding-top: 16px;
}
.auszeit-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.auszeit-title {
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
}
.neue-auszeit {
margin-top: 16px;
background: #f8f8ff;
border: 1.5px dashed var(--primary);
border-radius: 10px;
padding: 16px;
}
.neue-auszeit-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 12px;
}
.auszeit-form-row {
display: flex;
gap: 12px;
align-items: flex-start;
flex-wrap: wrap;
}
.auszeit-form-row .karte-field {
min-width: 130px;
}

View File

@ -197,8 +197,7 @@ router.post('/members/:id/update', requireAdmin, async (req, res) => {
const {
salutation, title, first_name, last_name, birth_date,
email, phone, street, address_addition, zip, city,
bank_name, account_holder, iban, tariff_id, status,
pause_from, pause_until
bank_name, account_holder, iban, tariff_id, status
} = req.body;
try {
await db.query(`
@ -219,4 +218,61 @@ router.post('/members/:id/update', requireAdmin, async (req, res) => {
res.redirect(`/admin/members/${req.params.id}?error=Fehler+beim+Speichern`);
}
});
// ===== AUSZEITEN =====
router.post('/members/:id/pauses/add', requireAdmin, async (req, res) => {
const { pause_start, pause_end, pause_months, reason } = req.body;
const memberId = req.params.id;
try {
if (!pause_start || !pause_end || !pause_months) {
return res.redirect(`/admin/members/${memberId}?error=Bitte+alle+Pflichtfelder+ausfüllen`);
}
if (new Date(pause_end) <= new Date(pause_start)) {
return res.redirect(`/admin/members/${memberId}?error=Enddatum+muss+nach+Startdatum+liegen`);
}
// Auszeit eintragen
await db.query(
'INSERT INTO membership_pauses (membership_id, pause_start, pause_end, pause_months, reason) VALUES (?, ?, ?, ?, ?)',
[memberId, pause_start, pause_end, pause_months, reason || null]
);
// pause_months_total und effective_end aktualisieren
await db.query(`
UPDATE memberships
SET pause_months_total = (
SELECT COALESCE(SUM(pause_months), 0) FROM membership_pauses WHERE membership_id = ?
),
effective_end = DATE_ADD(contract_end, INTERVAL (
SELECT COALESCE(SUM(pause_months), 0) FROM membership_pauses WHERE membership_id = ?
) MONTH)
WHERE id = ?
`, [memberId, memberId, memberId]);
res.redirect(`/admin/members/${memberId}?success=Auszeit+eingetragen`);
} catch (err) {
console.error(err);
res.redirect(`/admin/members/${memberId}?error=Fehler+beim+Eintragen+der+Auszeit`);
}
});
router.post('/members/:id/pauses/:pauseId/delete', requireAdmin, async (req, res) => {
const { id: memberId, pauseId } = req.params;
try {
await db.query('DELETE FROM membership_pauses WHERE id = ? AND membership_id = ?', [pauseId, memberId]);
// Summe neu berechnen
await db.query(`
UPDATE memberships
SET pause_months_total = (
SELECT COALESCE(SUM(pause_months), 0) FROM membership_pauses WHERE membership_id = ?
),
effective_end = DATE_ADD(contract_end, INTERVAL (
SELECT COALESCE(SUM(pause_months), 0) FROM membership_pauses WHERE membership_id = ?
) MONTH)
WHERE id = ?
`, [memberId, memberId, memberId]);
res.redirect(`/admin/members/${memberId}?success=Auszeit+gelöscht`);
} catch (err) {
console.error(err);
res.redirect(`/admin/members/${memberId}?error=Fehler+beim+Löschen`);
}
});
module.exports = router;

View File

@ -10,7 +10,6 @@
<body class="admin-body">
<div class="admin-layout">
<!-- Sidebar -->
<aside class="admin-sidebar">
<div class="logo admin-logo">Plusfit<span>24</span></div>
<nav class="admin-nav">
@ -25,17 +24,15 @@
</div>
</aside>
<!-- Hauptinhalt -->
<main class="admin-main">
<!-- Kopfzeile -->
<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' : 'inactive' %>">
<%= member.status === 'active' ? '✅ Aktiv' : '❌ Inaktiv' %>
<span class="status-badge <%= member.status === 'active' ? 'active' : (member.status === 'paused' ? 'warning' : 'inactive') %>">
<%= member.status === 'active' ? '✅ Aktiv' : member.status === 'paused' ? '⏸ Pausiert' : '❌ Inaktiv' %>
</span>
<% if (member.is_minor) { %>
<span class="status-badge warning">⚠️ Minderjährig</span>
@ -52,23 +49,19 @@
<% 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-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>
<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">
@ -107,10 +100,7 @@
<!-- ===== KARTE 2: Adresse ===== -->
<div class="karte">
<div class="karte-header">
<span class="karte-icon">📍</span>
<h3>Adresse</h3>
</div>
<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">
@ -138,12 +128,10 @@
</div>
<!-- ===== KARTE 3: Vertrag ===== -->
<div class="karte">
<div class="karte-header">
<span class="karte-icon">📄</span>
<h3>Vertrag</h3>
</div>
<div class="karte karte-full">
<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>
@ -156,6 +144,7 @@
</select>
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Abschlussdatum</label>
@ -165,8 +154,6 @@
<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">
@ -176,6 +163,7 @@
<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>
@ -185,8 +173,6 @@
<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">
@ -200,50 +186,92 @@
<input type="text" value="<%= member.pause_months_total || 0 %> Monat(e)" disabled class="karte-input karte-readonly">
</div>
</div>
<div class="karte-row">
<div class="karte-field">
<label>Pausiert von</label>
<input type="date" name="pause_from"
value="<%= member.pause_from ? new Date(member.pause_from).toISOString().split('T')[0] : '' %>"
disabled class="karte-input">
<!-- Auszeiten Tabelle -->
<div class="auszeit-section">
<div class="auszeit-header">
<span class="auszeit-title">⏸ Auszeiten</span>
</div>
<div class="karte-field">
<label>Pausiert bis</label>
<input type="date" name="pause_until"
value="<%= member.pause_until ? new Date(member.pause_until).toISOString().split('T')[0] : '' %>"
disabled class="karte-input">
<% 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>
<form method="POST" action="/admin/members/<%= member.id %>/pauses/add" class="auszeit-form">
<div class="auszeit-form-row">
<div class="karte-field">
<label>Von *</label>
<input type="date" name="pause_start" required class="karte-input">
</div>
<div class="karte-field">
<label>Bis *</label>
<input type="date" name="pause_end" required class="karte-input">
</div>
<div class="karte-field" style="max-width:120px">
<label>Monate *</label>
<input type="number" name="pause_months" min="1" max="12" required class="karte-input" placeholder="z.B. 2">
</div>
<div class="karte-field">
<label>Grund</label>
<input type="text" name="reason" 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" class="btn btn-primary">Speichern</button>
</div>
</div>
</form>
</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 karte-full">
<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 karte-field-full">
<div class="karte-field">
<label>Geldinstitut</label>
<input type="text" name="bank_name" value="<%= member.bank_name || '' %>" disabled class="karte-input">
</div>
</div>
<div class="karte-row">
<div class="karte-field karte-field-full">
<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 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>
<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">
@ -253,6 +281,15 @@
<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">
@ -264,42 +301,6 @@
</div>
</div>
<!-- ===== KARTE 5: Auszeiten ===== -->
<div class="karte karte-full">
<div class="karte-header">
<span class="karte-icon">⏸</span>
<h3>Auszeiten</h3>
</div>
<div class="karte-body">
<% if (pauses.length === 0) { %>
<p class="karte-empty">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>
</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>
</tr>
<% }) %>
</tbody>
</table>
<% } %>
</div>
</div>
</div><!-- end karteikarte-grid -->
</form>
@ -312,20 +313,19 @@
function enableEdit() {
editableFields.forEach(f => f.removeAttribute('disabled'));
// IBAN Validierung aktivieren
const ibanInput = document.getElementById('ibanInput');
const ibanMessage = document.getElementById('ibanMessage');
if (ibanInput) {
attachIBANValidation(ibanInput, null, ibanMessage);
}
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() {
// Seite neu laden = alle Änderungen verwerfen
window.location.reload();
}
</script>