import { apiAction, toast } from './api.js'; const state = { settings: {}, rotate: { bridge: false, sender: false, external: false }, users: [], userMap: new Map(), currentTab: 'profile', loading: false, }; let avatarBtn; let profileForm; let passwordForm; let settingsForm; let teamTable; let userForm; export function initUserPanel() { avatarBtn = document.getElementById('btn-user'); updateAvatar(); } export function initAccountPage() { profileForm = document.getElementById('profileForm'); passwordForm = document.getElementById('passwordForm'); settingsForm = document.getElementById('settingsForm'); teamTable = document.getElementById('teamTable'); userForm = document.getElementById('userForm'); document.getElementById('btn-user-add')?.addEventListener('click', () => openUserForm()); document.getElementById('userFormCancel')?.addEventListener('click', () => closeUserForm()); userForm?.addEventListener('submit', submitUserForm); profileForm?.addEventListener('submit', submitProfileForm); passwordForm?.addEventListener('submit', submitPasswordForm); settingsForm?.addEventListener('submit', submitSettingsForm); teamTable?.addEventListener('click', handleTeamTableClick); document.querySelectorAll('[data-user-tab]').forEach(btn => { btn.addEventListener('click', () => switchTab(btn.getAttribute('data-user-tab'))); }); settingsForm?.querySelectorAll('button[data-rotate]').forEach(btn => { btn.addEventListener('click', () => { const type = btn.getAttribute('data-rotate'); if (type && state.rotate[type] !== undefined) { state.rotate[type] = true; toast('Token wird nach dem Speichern erneuert.', true, { duration: 2000 }); } }); }); settingsForm?.querySelectorAll('button[data-download]').forEach(btn => { btn.addEventListener('click', () => { const type = btn.getAttribute('data-download'); if (type) downloadFile(type); }); }); switchTab(state.currentTab); loadAccountData(); } function isOwner() { return (window.__currentUser?.role || '').toLowerCase() === 'owner'; } function updateAvatar() { const target = document.getElementById('userAvatar'); if (!target) return; const name = window.__currentUser?.name || window.__currentUser?.email || ''; target.textContent = name ? name.trim().charAt(0).toUpperCase() : 'U'; } function updateOwnerVisibility() { document.querySelectorAll('.owner-only').forEach(el => { el.classList.toggle('hidden', !isOwner()); }); } function switchTab(tab) { if (!tab) return; state.currentTab = tab; document.querySelectorAll('[data-user-panel]').forEach(panel => { panel.classList.toggle('hidden', panel.getAttribute('data-user-panel') !== tab); }); document.querySelectorAll('[data-user-tab]').forEach(btn => { const isActive = btn.getAttribute('data-user-tab') === tab; btn.classList.toggle('bg-sky-50', isActive); btn.classList.toggle('text-sky-700', isActive); }); } async function loadAccountData() { try { state.loading = true; const res = await apiAction('account.profile.get', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht geladen werden'); if (res.user) { window.__currentUser = res.user; updateAvatar(); updateOwnerVisibility(); } fillProfileForm(res.user); fillSettingsForm(res.settings || {}); if (isOwner()) { await loadUsers(); } } catch (err) { console.error(err); toast(err.message || 'Fehler beim Laden', false); } finally { state.loading = false; } } function fillProfileForm(user) { if (!profileForm) return; profileForm.name.value = user?.name || ''; profileForm.email.value = user?.email || ''; } function fillSettingsForm(settings) { state.settings = settings; if (!settingsForm) return; settingsForm.bridge_url.value = settings.bridge_url || ''; settingsForm.bridge_token.value = settings.bridge_token || ''; settingsForm.sender_token.value = settings.sender_token || ''; settingsForm.external_api_token.value = settings.external_api_token || ''; state.rotate = { bridge: false, sender: false, external: false }; } async function submitProfileForm(ev) { ev.preventDefault(); const data = { name: profileForm.name.value.trim(), email: profileForm.email.value.trim(), }; try { const res = await apiAction('account.profile.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Profil konnte nicht gespeichert werden'); window.__currentUser = res.user; updateAvatar(); toast('Profil aktualisiert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function submitPasswordForm(ev) { ev.preventDefault(); const data = { current_password: passwordForm.current_password.value, new_password: passwordForm.new_password.value, }; try { const res = await apiAction('account.password.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Passwort konnte nicht geändert werden'); passwordForm.reset(); toast('Passwort aktualisiert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function submitSettingsForm(ev) { ev.preventDefault(); const data = { bridge_url: settingsForm.bridge_url.value.trim(), bridge_token: settingsForm.bridge_token.value.trim(), sender_token: settingsForm.sender_token.value.trim(), external_api_token: settingsForm.external_api_token.value.trim(), rotate_bridge_token: state.rotate.bridge ? 1 : 0, rotate_sender_token: state.rotate.sender ? 1 : 0, rotate_external_token: state.rotate.external ? 1 : 0, }; try { const res = await apiAction('account.settings.update', { method: 'POST', data }); if (!res?.ok) throw new Error(res?.error || 'Einstellungen konnten nicht gespeichert werden'); fillSettingsForm(res.settings || {}); toast('Integrationen gespeichert', true); } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function downloadFile(type) { try { const action = type === 'sender' ? 'downloads.sender' : 'downloads.bridge'; const res = await apiAction(action, { method: 'POST', data: {} }); if (!res?.ok || !res.content) throw new Error(res?.error || 'Download fehlgeschlagen'); const bytes = atob(res.content); const buffer = new Uint8Array(bytes.length); for (let i = 0; i < bytes.length; i++) buffer[i] = bytes.charCodeAt(i); const blob = new Blob([buffer], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = res.file_name || `${type}.php`; document.body.appendChild(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (err) { toast(err.message || 'Download fehlgeschlagen', false); } } async function loadUsers() { try { const res = await apiAction('account.users.list', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Team konnte nicht geladen werden'); state.users = res.items || []; state.userMap = new Map(state.users.map(u => [u.id, u])); renderUserList(); } catch (err) { toast(err.message || 'Fehler beim Laden der Nutzer', false); } } function renderUserList() { if (!teamTable) return; const tbody = teamTable.querySelector('tbody'); if (!tbody) return; if (!state.users.length) { tbody.innerHTML = 'Keine Nutzer vorhanden.'; return; } tbody.innerHTML = state.users.map(user => { const badge = user.is_active ? 'Aktiv' : 'Inaktiv'; return ` ${escapeHtml(user.name)} ${escapeHtml(user.email)} ${escapeHtml(user.role)} ${badge} `; }).join(''); } function handleTeamTableClick(ev) { const btn = ev.target.closest('button[data-user-action]'); if (!btn) return; const id = Number(btn.getAttribute('data-user-id')); const action = btn.getAttribute('data-user-action'); const user = state.userMap.get(id); if (!user) return; if (action === 'edit') { openUserForm(user); } else if (action === 'delete') { if (confirm(`Soll ${user.name} wirklich entfernt werden?`)) deleteUser(id); } else if (action === 'reset') { openUserForm(user, true); } } function openUserForm(user = null, resetOnly = false) { if (!userForm) return; userForm.classList.remove('hidden'); userForm.user_id.value = user?.id || ''; userForm.name.value = user?.name || ''; userForm.email.value = user?.email || ''; userForm.role.value = user?.role || 'user'; userForm.is_active.checked = user ? !!user.is_active : true; const resetRow = userForm.querySelector('.reset-only'); if (resetRow) resetRow.classList.toggle('hidden', !user); if (resetRow) resetRow.querySelector('input').checked = resetOnly; } function closeUserForm() { if (!userForm) return; userForm.classList.add('hidden'); userForm.reset(); userForm.user_id.value = ''; const resetInput = userForm.querySelector('.reset-only input'); if (resetInput) resetInput.checked = false; } async function submitUserForm(ev) { ev.preventDefault(); const formData = new FormData(userForm); const payload = { name: formData.get('name')?.toString().trim() || '', email: formData.get('email')?.toString().trim() || '', role: formData.get('role')?.toString() || 'user', is_active: userForm.is_active.checked ? 1 : 0, }; const userId = formData.get('user_id')?.toString(); let action = 'account.users.create'; if (userId) { action = 'account.users.update'; payload.user_id = Number(userId); payload.reset_password = userForm.querySelector('input[name="reset_password"]').checked ? 1 : 0; } try { const res = await apiAction(action, { method: 'POST', data: payload }); if (!res?.ok) throw new Error(res?.error || 'Speichern fehlgeschlagen'); closeUserForm(); await loadUsers(); toast('Nutzer gespeichert', true); if (res.temp_password) { copyToClipboard(res.temp_password); alert(`Neues Passwort: ${res.temp_password}`); } } catch (err) { toast(err.message || 'Fehler beim Speichern', false); } } async function deleteUser(userId) { try { const res = await apiAction('account.users.delete', { method: 'POST', data: { user_id: userId } }); if (!res?.ok) throw new Error(res?.error || 'Löschen fehlgeschlagen'); await loadUsers(); toast('Nutzer gelöscht', true); } catch (err) { toast(err.message || 'Fehler beim Löschen', false); } } function copyToClipboard(value) { if (navigator.clipboard?.writeText) { navigator.clipboard.writeText(value).catch(() => {}); } } function escapeHtml(str) { return String(str || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }