NFC hinzugefügt
This commit is contained in:
parent
f6b60af78b
commit
d5a1536bb5
@ -1343,3 +1343,36 @@ body:not(.admin-body) > * {
|
||||
}
|
||||
.invoice-history-row:last-child td { border-bottom: none; }
|
||||
.invoice-history-row:hover td { background: #f8f8ff; cursor: default; }
|
||||
|
||||
/* ---- NFC / Zugangskarte ---- */
|
||||
.token-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #f1f0ff;
|
||||
border: 1.5px solid #c4b5fd;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.token-display {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
letter-spacing: 1px;
|
||||
color: #4c1d95;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
.token-regen-btn { flex-shrink: 0; }
|
||||
|
||||
.nfc-info-box {
|
||||
margin-top: 14px;
|
||||
background: #f0fdf4;
|
||||
border: 1.5px solid #bbf7d0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 0.82rem;
|
||||
color: #166534;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.nfc-info-box strong { display: block; margin-bottom: 6px; }
|
||||
.nfc-info-box ol { padding-left: 18px; }
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const bcrypt = require('bcryptjs');
|
||||
const db = require('../config/database');
|
||||
@ -289,4 +290,32 @@ router.post('/members/:id/pauses/:pauseId/delete', requireAdmin, async (req, res
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ===== NFC / ZUGANGSKARTE =====
|
||||
router.post('/members/:id/regenerate-token', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const token = crypto.randomBytes(16).toString('hex');
|
||||
await db.query(
|
||||
'UPDATE memberships SET access_token = ? WHERE id = ?',
|
||||
[token, req.params.id]
|
||||
);
|
||||
res.redirect(`/admin/members/${req.params.id}?success=Neuer+Token+generiert`);
|
||||
} catch (err) {
|
||||
res.redirect(`/admin/members/${req.params.id}?error=Fehler+beim+Generieren`);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/members/:id/update-nfc', requireAdmin, async (req, res) => {
|
||||
const { nfc_uid } = req.body;
|
||||
try {
|
||||
await db.query(
|
||||
'UPDATE memberships SET nfc_uid = ?, card_issued = 1, card_issued_at = NOW() WHERE id = ?',
|
||||
[nfc_uid ? nfc_uid.trim().toUpperCase() : null, req.params.id]
|
||||
);
|
||||
res.redirect(`/admin/members/${req.params.id}?success=NFC+UID+gespeichert`);
|
||||
} catch (err) {
|
||||
res.redirect(`/admin/members/${req.params.id}?error=Fehler+beim+Speichern`);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const dns = require('dns').promises;
|
||||
const db = require('../config/database');
|
||||
@ -118,20 +119,23 @@ router.post('/submit-membership', async (req, res) => {
|
||||
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
|
||||
}
|
||||
|
||||
// Zugangstoken generieren
|
||||
const access_token = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
// In DB speichern
|
||||
await db.query(`
|
||||
INSERT INTO memberships
|
||||
(tariff_id, salutation, title, first_name, last_name, birth_date, email, phone,
|
||||
street, address_addition, zip, city, bank_name, account_holder, iban,
|
||||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct, guardian_consent, is_minor)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct, guardian_consent, is_minor, access_token)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
tariff_id, salutation, title || '', first_name, last_name, birth_date,
|
||||
email, phone || '', street, address_addition || '', zip, city,
|
||||
bank_name || '', account_holder || '', iban || '',
|
||||
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
|
||||
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
|
||||
guardian_consent ? 1 : 0, is_minor
|
||||
guardian_consent ? 1 : 0, is_minor, access_token
|
||||
]);
|
||||
|
||||
res.json({ success: true });
|
||||
|
||||
@ -365,6 +365,79 @@
|
||||
</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>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user