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 ──────────────────────────────────────────