import React, { useEffect, useState } from 'react'; import { Shield, Users, Server, Mail, Plus, Trash2, KeyRound, Copy, Check, ArrowLeft, Share2, Send, AlertCircle, Building2, UserPlus, } from 'lucide-react'; import { Organization, SystemSettings, Team, User, UserInvite, UserRole, } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; interface AdminSettingsViewProps { currentUser: User; onBack: () => void; } type AdminSection = 'users' | 'orgs' | 'server' | 'smtp'; export const AdminSettingsView: React.FC = ({ currentUser, onBack, }) => { const isServerAdmin = currentUser.role === 'admin'; const [section, setSection] = useState('users'); // --- Users & Invites State --- const [users, setUsers] = useState([]); const [invites, setInvites] = useState([]); const [newUserName, setNewUserName] = useState(''); const [newUserEmail, setNewUserEmail] = useState(''); const [newUserPassword, setNewUserPassword] = useState(''); const [newUserRole, setNewUserRole] = useState('member'); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('member'); const [inviteTeamId, setInviteTeamId] = useState(''); const [copiedInviteId, setCopiedInviteId] = useState(null); const [resetPwdUserId, setResetPwdUserId] = useState(null); const [resetPwdVal, setResetPwdVal] = useState(''); // --- Organizations & Teams State --- const [orgs, setOrgs] = useState([]); const [allTeams, setAllTeams] = useState([]); const [newOrgName, setNewOrgName] = useState(''); const [selectedOrgId, setSelectedOrgId] = useState(''); const [newTeamName, setNewTeamName] = useState(''); const [newTeamDesc, setNewTeamDesc] = useState(''); const [memberSelectByTeam, setMemberSelectByTeam] = useState>({}); const [memberRoleByTeam, setMemberRoleByTeam] = useState>({}); // --- Server & SMTP Settings State --- const [settings, setSettings] = useState({ sharingMode: 'teams_only', allowRegistration: false, instanceName: 'MindSift', smtpHost: '', smtpPort: 587, smtpUser: '', smtpPass: '', smtpFrom: '', smtpTls: true, smtpConfiguredViaEnv: false, }); const [testSmtpEmail, setTestSmtpEmail] = useState(currentUser.email || ''); const [testingSmtp, setTestingSmtp] = useState(false); const [statusMsg, setStatusMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); const loadAll = async () => { try { const [uList, invList, orgList, teamList] = await Promise.all([ api.adminGetUsers(), api.adminGetInvites(), api.getOrganizations(), api.getUserTeams(), ]); setUsers(uList); setInvites(invList); setOrgs(orgList); setAllTeams(teamList); if (orgList.length > 0 && !selectedOrgId) { setSelectedOrgId(orgList[0].id); } if (isServerAdmin) { const sys = await api.adminGetSettings(); setSettings({ sharingMode: sys.sharingMode || 'teams_only', allowRegistration: Boolean(sys.allowRegistration), instanceName: sys.instanceName || 'MindSift', smtpHost: sys.smtpHost || '', smtpPort: sys.smtpPort || 587, smtpUser: sys.smtpUser || '', smtpPass: sys.smtpPass || '', smtpFrom: sys.smtpFrom || '', smtpTls: sys.smtpTls ?? true, smtpConfiguredViaEnv: Boolean(sys.smtpConfiguredViaEnv), }); } } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Laden der Administrationsdaten' }); } }; useEffect(() => { loadAll(); }, []); // ============================================================================ // HANDLERS: USERS & INVITES // ============================================================================ const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); setStatusMsg(null); triggerHaptic(15); try { const created = await api.adminCreateUser({ email: newUserEmail.trim(), password: newUserPassword, displayName: newUserName.trim() || newUserEmail.split('@')[0], role: newUserRole, }); setUsers((prev) => [...prev, created]); setNewUserName(''); setNewUserEmail(''); setNewUserPassword(''); setNewUserRole('member'); setStatusMsg({ type: 'ok', text: `Benutzer "${created.displayName}" wurde angelegt.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Anlegen des Benutzers' }); } }; const handleCreateInvite = async (e: React.FormEvent) => { e.preventDefault(); setStatusMsg(null); triggerHaptic(15); try { const inv = await api.adminCreateInvite({ email: inviteEmail.trim(), role: inviteRole, teamId: inviteTeamId, }); setInvites((prev) => [inv, ...prev]); setInviteEmail(''); setStatusMsg({ type: 'ok', text: `Einladungscode ${inv.code} erfolgreich erstellt.`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Erstellen der Einladung' }); } }; const handleDeleteInvite = async (id: string) => { try { await api.adminDeleteInvite(id); setInvites((prev) => prev.filter((i) => i.id !== id)); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleCopyInviteCode = (inv: UserInvite) => { navigator.clipboard.writeText(inv.code); setCopiedInviteId(inv.id); setTimeout(() => setCopiedInviteId(null), 2000); }; const handleChangeUserRole = async (u: User, role: UserRole) => { setStatusMsg(null); try { const updated = await api.adminUpdateUser(u.id, { role }); setUsers((prev) => prev.map((item) => (item.id === u.id ? updated : item))); setStatusMsg({ type: 'ok', text: `Rolle für ${updated.displayName} auf "${role}" aktualisiert.`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleToggleUserActive = async (u: User) => { setStatusMsg(null); try { const updated = await api.adminUpdateUser(u.id, { isActive: !u.isActive }); setUsers((prev) => prev.map((item) => (item.id === u.id ? updated : item))); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleConfirmResetPassword = async (userId: string) => { if (!resetPwdVal.trim()) return; setStatusMsg(null); try { await api.adminResetPassword(userId, resetPwdVal.trim()); setResetPwdUserId(null); setResetPwdVal(''); setStatusMsg({ type: 'ok', text: 'Passwort erfolgreich zurückgesetzt.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleDeleteUser = async (u: User) => { if ( !window.confirm( `Benutzer "${u.displayName}" (${u.email}) wirklich unwiderruflich löschen?` ) ) return; setStatusMsg(null); try { await api.adminDeleteUser(u.id); setUsers((prev) => prev.filter((item) => item.id !== u.id)); setStatusMsg({ type: 'ok', text: 'Benutzer gelöscht.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; // ============================================================================ // HANDLERS: ORGANIZATIONS & TEAMS // ============================================================================ const handleCreateOrg = async (e: React.FormEvent) => { e.preventDefault(); if (!newOrgName.trim()) return; setStatusMsg(null); try { const created = await api.createOrganization(newOrgName.trim()); setNewOrgName(''); setSelectedOrgId(created.id); await loadAll(); setStatusMsg({ type: 'ok', text: `Organisation "${created.name}" angelegt.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleDeleteOrg = async (org: Organization) => { if (!window.confirm(`Organisation "${org.name}" inklusive aller zugehörigen Teams löschen?`)) return; setStatusMsg(null); try { await api.deleteOrganization(org.id); await loadAll(); setStatusMsg({ type: 'ok', text: `Organisation "${org.name}" gelöscht.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleCreateTeam = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedOrgId || !newTeamName.trim()) return; setStatusMsg(null); try { const created = await api.createOrgTeam( selectedOrgId, newTeamName.trim(), newTeamDesc.trim() ); setNewTeamName(''); setNewTeamDesc(''); await loadAll(); setStatusMsg({ type: 'ok', text: `Team "${created.name}" wurde erstellt.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleDeleteTeam = async (team: Team) => { if (!window.confirm(`Team "${team.name}" wirklich löschen?`)) return; setStatusMsg(null); try { await api.deleteTeam(team.id); await loadAll(); setStatusMsg({ type: 'ok', text: `Team "${team.name}" gelöscht.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleAddMemberToTeam = async (teamId: string) => { const selectedUserId = memberSelectByTeam[teamId]; if (!selectedUserId) return; const role = memberRoleByTeam[teamId] || 'member'; setStatusMsg(null); try { await api.addTeamMember(teamId, { userId: selectedUserId, role }); await loadAll(); setStatusMsg({ type: 'ok', text: 'Teammitglied hinzugefügt.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleRemoveMemberFromTeam = async (teamId: string, userId: string) => { setStatusMsg(null); try { await api.removeTeamMember(teamId, userId); await loadAll(); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; // ============================================================================ // HANDLERS: SERVER & SMTP SETTINGS // ============================================================================ const handleSaveSystemSettings = async (patch?: Partial) => { setStatusMsg(null); const next = { ...settings, ...(patch || {}) }; setSettings(next); try { const saved = await api.adminUpdateSettings(next); setSettings((prev) => ({ ...prev, ...saved })); setStatusMsg({ type: 'ok', text: 'Server-Einstellungen erfolgreich gespeichert.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Speichern' }); } }; const handleTestSmtp = async () => { if (!testSmtpEmail.trim()) return; setTestingSmtp(true); setStatusMsg(null); try { await api.adminUpdateSettings(settings); await api.adminTestSMTP(testSmtpEmail.trim()); setStatusMsg({ type: 'ok', text: `Test-E-Mail erfolgreich an ${testSmtpEmail.trim()} gesendet!`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: `SMTP-Test fehlgeschlagen: ${err.message}`, }); } finally { setTestingSmtp(false); } }; // Collect all teams across orgs for invite dropdown const flattenedTeams: Team[] = orgs.flatMap((o) => o.teams || []).length > 0 ? orgs.flatMap((o) => o.teams || []) : allTeams; return (
{/* Top Page Header */}

Administration & Team-Steuerung

Benutzerverwaltung, Einladungscodes, Organisationen, Teams, Isolationsmodus & Mail-Server

{isServerAdmin ? 'Server-Administrator' : 'Team-Administrator'}
{statusMsg && (
{statusMsg.type === 'ok' ? ( ) : ( )} {statusMsg.text}
)} {/* Main 2-Column Layout */}
{/* Left Navigation Sidebar */} {/* Right Content Area */}
{/* ================================================================= */} {/* SECTION 1: BENUTZER, ROLLEN & EINLADUNGEN */} {/* ================================================================= */} {section === 'users' && (

Benutzersteuerung, Rollen & Einladungen

Lege Benutzerkonten direkt an oder erzeuge Einladungscodes, mit denen sich eingeladene Personen selbst registrieren können, auch wenn die öffentliche Registrierung gesperrt ist.

{/* Direct User Creation Form (Fixed Layout: 4 clean columns with min-w-0, no overflow!) */}

Neues Benutzerkonto direkt anlegen

setNewUserName(e.target.value)} placeholder="z.B. Anna Beispiel" required className="w-full min-w-0 h-8 px-2.5 input-field text-white" />
setNewUserEmail(e.target.value)} placeholder="anna@beispiel.de" required className="w-full min-w-0 h-8 px-2.5 input-field text-white" />
setNewUserPassword(e.target.value)} placeholder="Mind. 4 Zeichen" minLength={4} required className="w-full min-w-0 h-8 px-2.5 input-field text-white" />
{/* Invite Code Generator */}

Einladungscode erzeugen (gültig für 7 Tage)

setInviteEmail(e.target.value)} placeholder="Leer lassen für beliebige E-Mail" className="w-full min-w-0 h-8 px-2.5 input-field text-white" />
{/* Active Invites List */} {invites.length > 0 && (

Aktive & bisherige Einladungscodes:

{invites.map((inv) => (
{inv.code} Rolle: {inv.role} {inv.email ? ` · Für: ${inv.email}` : ''} {inv.usedBy ? ( Eingelöst ) : ( Offen )}
{!inv.usedBy && ( )}
))}
)}
{/* Users List */}

Registrierte Benutzer ({users.length})

{users.map((u) => (
{u.displayName} {!u.isActive && ( Gesperrt )}

{u.email}

{/* Role selector */} {isServerAdmin && ( <> {u.id !== currentUser.id && ( <> )} )}
{resetPwdUserId === u.id && (
setResetPwdVal(e.target.value)} placeholder="Neues Passwort eingeben…" className="flex-1 h-7 px-2 input-field text-white" />
)}
))}
)} {/* ================================================================= */} {/* SECTION 2: ORGANISATIONEN & TEAMS */} {/* ================================================================= */} {section === 'orgs' && (

Organisationen & Teams verwalten

Erstelle Organisationen und unterteile sie in Teams. Weise Benutzern Teams zu, damit sie im Modus "Teams isoliert" gemeinsam an Listen und Aufgaben arbeiten können.

{/* Create Organization & Team */}

1. Neue Organisation gründen

setNewOrgName(e.target.value)} placeholder="z.B. Homelab, Familie, Firma…" required className="flex-1 min-w-0 h-8 px-2.5 input-field text-white" />

2. Neues Team in Organisation anlegen

{orgs.length === 0 ? (

Bitte lege zuerst links eine Organisation an.

) : (
setNewTeamName(e.target.value)} placeholder="Team-Name (z.B. Infrastruktur)" required className="w-full min-w-0 h-8 px-2.5 input-field text-white" />
setNewTeamDesc(e.target.value)} placeholder="Kurzbeschreibung (optional)…" className="flex-1 min-w-0 h-8 px-2.5 input-field text-white" />
)}
{/* Organizations & Nested Teams Display */}
{orgs.length === 0 ? (
Noch keine Organisationen vorhanden.
) : ( orgs.map((org) => { const orgTeams = Array.isArray(org.teams) ? org.teams : []; return (
{org.name} ({orgTeams.length} {orgTeams.length === 1 ? 'Team' : 'Teams'})
{orgTeams.length === 0 ? (

Noch keine Teams in dieser Organisation.

) : ( orgTeams.map((team) => { const members = Array.isArray(team.members) ? team.members : []; return (
{team.name} {team.description && (

{team.description}

)}
{/* Team Members */}

Mitglieder ({members.length}):

{members.length === 0 ? (

Noch keine Mitglieder zugewiesen.

) : (
{members.map((m) => (
{m.displayName} ({m.role})
))}
)}
{/* Add User to Team */}
); }) )}
); }) )}
)} {/* ================================================================= */} {/* SECTION 3: SERVER & SHARING-ISOLATION (SERVER-ADMIN ONLY) */} {/* ================================================================= */} {section === 'server' && isServerAdmin && (

Server-Grundeinstellungen & Benutzer-Sichtbarkeit

Als Server-Admin bestimmst du zentral für alle Nutzer, ob sie komplett isoliert arbeiten, sich nur innerhalb ihrer Teams sehen oder ob alle Nutzer für alle sichtbar sind.

setSettings((prev) => ({ ...prev, instanceName: e.target.value })) } className="flex-1 h-9 px-3 input-field text-white" />
{/* Sharing Isolation Cards */}
{/* Public Registration Toggle */}

Öffentliche Selbstregistrierung erlauben

Wenn deaktiviert, können sich Fremde auf dem Login-Bildschirm nicht registrieren. Neue Benutzer benötigen zwingend einen Einladungscode oder müssen von einem Admin angelegt werden.

)} {/* ================================================================= */} {/* SECTION 4: E-MAIL / SMTP-SERVER (SERVER-ADMIN ONLY) */} {/* ================================================================= */} {section === 'smtp' && isServerAdmin && (

E-Mail / SMTP-Server für Aufgaben-Alarme

Konfiguriere den Postausgangsserver per Docker-Compose Environment-Variablen oder direkt hier über die Oberfläche (wird persistent in der SQLite-Datenbank gespeichert).

{settings.smtpConfiguredViaEnv && ( Docker Env erkannt )}
setSettings((prev) => ({ ...prev, smtpHost: e.target.value })) } placeholder="z.B. smtp.gmail.com oder mail.deinedomain.de" className="w-full h-9 px-3 input-field text-white font-mono" />
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) })) } placeholder="587" className="w-full h-9 px-3 input-field text-white font-mono" />
setSettings((prev) => ({ ...prev, smtpUser: e.target.value })) } placeholder="alerts@deinedomain.de" className="w-full h-9 px-3 input-field text-white font-mono" />
setSettings((prev) => ({ ...prev, smtpPass: e.target.value })) } placeholder="••••••••••••" className="w-full h-9 px-3 input-field text-white font-mono" />
setSettings((prev) => ({ ...prev, smtpFrom: e.target.value })) } placeholder="MindSift " className="w-full h-9 px-3 input-field text-white" />
{/* Test SMTP Delivery */}

SMTP-Verbindung testen

Sendet sofort eine Test-E-Mail mit den oben eingetragenen Daten.

setTestSmtpEmail(e.target.value)} placeholder="empfaenger@beispiel.de" className="flex-1 h-8 px-3 input-field text-white" />
)}
); };