let currentUser = null; let users = []; initPage({ onAuth: (user) => { currentUser = user; if (!user || user.role !== 'admin') { document.getElementById('usersTable').innerHTML = 'Zugriff verweigert'; document.getElementById('btnShowCreate').classList.add('d-none'); return; } loadUsers(); } }); // ── Toast ──────────────────────────────────────────── function showToast(message, type = 'success') { const el = document.getElementById('adminToast'); el.className = `alert alert-${type} py-1 small mb-2`; el.textContent = message; setTimeout(() => el.classList.add('d-none'), 4000); } function showModalAlert(id, message, type = 'danger') { const el = document.getElementById(id); el.className = `alert alert-${type} py-1 small`; el.textContent = message; } function hideModalAlert(id) { document.getElementById(id).classList.add('d-none'); } // ── Password generator ─────────────────────────────── function generatePassword(length = 12) { const chars = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%'; let pw = ''; const arr = new Uint8Array(length); crypto.getRandomValues(arr); for (let i = 0; i < length; i++) pw += chars[arr[i] % chars.length]; return pw; } // ── Load & Render ──────────────────────────────────── async function loadUsers() { try { const resp = await fetch('/api/admin/users'); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); users = await resp.json(); renderUsers(); } catch (e) { document.getElementById('usersTable').innerHTML = 'Fehler beim Laden'; } } function renderUsers() { const tbody = document.getElementById('usersTable'); if (users.length === 0) { tbody.innerHTML = 'Keine Benutzer'; return; } tbody.innerHTML = users.map(user => { const roleBadge = user.role === 'admin' ? 'Admin' : 'Mitarbeiter'; const verifiedIcon = user.is_verified ? '' : ''; const created = user.created_at ? new Date(user.created_at * 1000).toLocaleDateString('de-DE') : '-'; const isSelf = currentUser && currentUser.id === user.id; let actions = ''; // Edit button (always) actions += ``; // Reset password actions += ``; // Send info email actions += ``; if (!isSelf) { // Verify (if not verified) if (!user.is_verified) { actions += ``; } // Delete actions += ``; } return ` ${escapeHtml(user.name)} ${escapeHtml(user.email)} ${roleBadge} ${verifiedIcon} ${created} ${actions} `; }).join(''); } // ── Invite Mitarbeiter ─────────────────────────────── const inviteModal = new bootstrap.Modal(document.getElementById('inviteModal')); document.getElementById('btnShowInvite').addEventListener('click', () => { document.getElementById('inviteName').value = ''; document.getElementById('inviteEmail').value = ''; hideModalAlert('inviteAlert'); inviteModal.show(); }); document.getElementById('btnSendInvite').addEventListener('click', async () => { const name = document.getElementById('inviteName').value.trim(); const email = document.getElementById('inviteEmail').value.trim(); if (!name || !email) { showModalAlert('inviteAlert', 'Name und E-Mail erforderlich'); return; } try { const resp = await fetch('/api/admin/invite', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email }) }); if (resp.ok) { users = await resp.json(); renderUsers(); inviteModal.hide(); showToast(`Einladung an "${name}" gesendet`); } else { const data = await resp.json(); showModalAlert('inviteAlert', data.error || 'Fehler beim Einladen'); } } catch (e) { showModalAlert('inviteAlert', 'Verbindungsfehler'); } }); // ── Edit User ──────────────────────────────────────── const editModal = new bootstrap.Modal(document.getElementById('editModal')); function openEditModal(userId) { const user = users.find(u => u.id === userId); if (!user) return; document.getElementById('editUserId').value = userId; document.getElementById('editName').value = user.name; document.getElementById('editEmail').value = user.email; document.getElementById('editRole').value = user.role; hideModalAlert('editAlert'); editModal.show(); } document.getElementById('btnSaveEdit').addEventListener('click', async () => { const userId = document.getElementById('editUserId').value; const name = document.getElementById('editName').value.trim(); const email = document.getElementById('editEmail').value.trim(); const role = document.getElementById('editRole').value; if (!name || !email) { showModalAlert('editAlert', 'Name und E-Mail erforderlich'); return; } try { const resp = await fetch(`/api/admin/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, role }) }); if (resp.ok) { users = await resp.json(); renderUsers(); editModal.hide(); showToast('Benutzer aktualisiert'); } else { const data = await resp.json(); showModalAlert('editAlert', data.error || 'Fehler beim Speichern'); } } catch (e) { showModalAlert('editAlert', 'Verbindungsfehler'); } }); // ── Reset Password ─────────────────────────────────── const resetPwModal = new bootstrap.Modal(document.getElementById('resetPwModal')); function openResetPwModal(userId) { const user = users.find(u => u.id === userId); if (!user) return; document.getElementById('resetPwUserId').value = userId; document.getElementById('resetPwUserName').textContent = user.name; document.getElementById('resetPwPassword').value = generatePassword(); document.getElementById('resetPwSendEmail').checked = true; hideModalAlert('resetPwAlert'); resetPwModal.show(); } document.getElementById('btnGenerateResetPw').addEventListener('click', () => { document.getElementById('resetPwPassword').value = generatePassword(); }); document.getElementById('btnResetPassword').addEventListener('click', async () => { const userId = document.getElementById('resetPwUserId').value; const password = document.getElementById('resetPwPassword').value; const sendEmail = document.getElementById('resetPwSendEmail').checked; if (!password || password.length < 8) { showModalAlert('resetPwAlert', 'Passwort muss mindestens 8 Zeichen lang sein'); return; } try { const resp = await fetch(`/api/admin/users/${userId}/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password, send_email: sendEmail }) }); if (resp.ok) { users = await resp.json(); renderUsers(); resetPwModal.hide(); showToast('Passwort zurueckgesetzt'); } else { const data = await resp.json(); showModalAlert('resetPwAlert', data.error || 'Fehler'); } } catch (e) { showModalAlert('resetPwAlert', 'Verbindungsfehler'); } }); // ── Send Info Email ────────────────────────────────── async function sendInfoEmail(userId) { const user = users.find(u => u.id === userId); if (!user) return; if (!confirm(`Info-Mail an "${user.name}" (${user.email}) senden?`)) return; try { const resp = await fetch(`/api/admin/users/${userId}/send-info`, { method: 'POST' }); const data = await resp.json(); if (resp.ok) { showToast(data.message || 'Info-Mail gesendet'); } else { showToast(data.error || 'Fehler beim Senden', 'danger'); } } catch (e) { showToast('Verbindungsfehler', 'danger'); } } // ── Verify / Delete ────────────────────────────────── async function verifyUser(id) { try { const resp = await fetch(`/api/admin/users/${id}/verify`, { method: 'POST' }); if (resp.ok) { users = await resp.json(); renderUsers(); showToast('Benutzer verifiziert'); } } catch (e) { console.error('Verify failed:', e); } } async function deleteUser(id, name) { if (!confirm(`Benutzer "${name}" wirklich loeschen?`)) return; try { const resp = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' }); if (resp.ok) { users = await resp.json(); renderUsers(); showToast('Benutzer geloescht'); } } catch (e) { console.error('Delete failed:', e); } } // ── Helpers ──────────────────────────────────────────