Ändern der Pause in Auszeit
This commit is contained in:
parent
b7c1c5df37
commit
f91e2f78ca
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user