philippurbschat.de/home/views/admin/users.php

298 lines
No EOL
22 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php require_once __DIR__ . '/../inc/admin_header.php'; ?>
<?php
$baseUrl = rtrim(Config::get('BASE_URL', 'https://philippurbschat.de/'), '/');
$currentTab = $activeTab ?? ($_GET['tab'] ?? 'users');
?>
<div class="max-w-7xl w-full mx-auto flex flex-col md:flex-row gap-6 p-4 sm:p-6">
<!-- Sidebar Navigation -->
<aside class="w-full md:w-56 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-4 h-fit shrink-0">
<div class="mb-4 pb-3 border-b border-slate-800">
<h2 class="text-xs font-mono uppercase tracking-[0.2em] text-slate-400 font-bold">Navigation</h2>
</div>
<nav class="flex flex-col gap-1.5 font-mono text-xs">
<a href="<?= $baseUrl ?>/admin" class="px-3 py-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800/60 rounded-xl transition-all">Dashboard</a>
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="px-3 py-2 <?= $currentTab === 'users' ? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/30' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60' ?> rounded-xl transition-all">Benutzer (<?= count($users ?? []) ?>)</a>
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="px-3 py-2 <?= $currentTab === 'invites' ? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/30' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60' ?> rounded-xl transition-all">Einladungslinks (<?= count($invitations ?? []) ?>)</a>
<a href="<?= $baseUrl ?>/admin/settings" class="px-3 py-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800/60 rounded-xl transition-all">Einstellungen</a>
</nav>
</aside>
<!-- Main Content Area -->
<main class="flex-1 min-w-0 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-3xl p-6 sm:p-8 shadow-2xl relative overflow-hidden flex flex-col">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-cyan-500/40 to-transparent"></div>
<!-- Top Header & Tabs -->
<div class="flex flex-wrap items-center justify-between gap-4 mb-8 pb-6 border-b border-slate-800">
<div>
<h1 class="text-xl sm:text-2xl font-black text-slate-100 font-mono tracking-tight">
<span class="text-cyan-400">/</span> <?= $currentTab === 'invites' ? 'Einladungslinks' : 'Benutzerverwaltung' ?>
</h1>
<p class="text-slate-400 text-xs font-mono mt-1">
<?= $currentTab === 'invites' ? 'Geheime Registrierungs-Links mit vorkonfigurierten Modul-Rechten.' : 'Verwalte Benutzerkonten und individuelle Modul-Zugriffe.' ?>
</p>
</div>
<div class="flex items-center gap-2">
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="text-xs font-mono px-3.5 py-2 rounded-xl border transition-all <?= $currentTab === 'users' ? 'bg-cyan-500/10 text-cyan-400 border-cyan-500/40' : 'bg-slate-950/60 text-slate-400 border-slate-800 hover:text-slate-200' ?>">
Benutzer
</a>
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="text-xs font-mono px-3.5 py-2 rounded-xl border transition-all <?= $currentTab === 'invites' ? 'bg-cyan-500/10 text-cyan-400 border-cyan-500/40' : 'bg-slate-950/60 text-slate-400 border-slate-800 hover:text-slate-200' ?>">
Einladungslinks
</a>
<a href="<?= $baseUrl ?>/admin/user_form" class="text-xs font-mono bg-cyan-500/10 text-cyan-400 border border-cyan-500/30 px-3.5 py-2 rounded-xl hover:bg-cyan-500/20 transition-all font-bold whitespace-nowrap">
+ Neuer Benutzer
</a>
</div>
</div>
<?php if ($currentTab === 'invites'): ?>
<!-- ================= TAB: EINLADUNGSLINKS ================= -->
<!-- Link Generator -->
<div class="bg-slate-950/80 border border-slate-800 rounded-2xl p-5 sm:p-6 mb-8 relative group">
<h2 class="text-xs font-mono uppercase tracking-wider text-cyan-400 font-bold mb-4">
Neuen Einladungslink generieren
</h2>
<form action="<?= $baseUrl ?>/admin/invite_create" method="POST" class="space-y-4">
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Empfänger E-Mail (optional)</label>
<input type="email" name="recipient_email" placeholder="name@beispiel.de" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
</div>
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Notiz / Name</label>
<input type="text" name="note" placeholder="z.B. Für Alex" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
</div>
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Max. Nutzungen</label>
<input type="number" name="max_uses" value="1" min="1" max="100" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
</div>
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Gültigkeit in Tagen</label>
<input type="number" name="days_valid" placeholder="z.B. 7 (leer = unbegrenzt)" min="1" max="365" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
</div>
</div>
<div class="pt-1">
<label class="flex items-center gap-2 text-xs font-mono text-slate-300 cursor-pointer select-none">
<input type="checkbox" name="send_email_now" value="1" checked class="accent-cyan-500">
<span>Einladungs-E-Mail direkt versenden (wenn Empfänger-E-Mail angegeben)</span>
</label>
</div>
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-2">Automatisch freizuschaltende Module</label>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2.5">
<?php foreach ($allModules as $slug => $mod): ?>
<label class="flex items-center gap-2.5 p-3 bg-slate-900/80 border border-slate-800 hover:border-cyan-500/40 rounded-xl cursor-pointer transition-colors text-xs font-mono">
<input type="checkbox" name="projects[]" value="<?= htmlspecialchars($slug) ?>" class="accent-cyan-500">
<span class="text-slate-300 uppercase tracking-wider text-[11px] font-bold"><?= htmlspecialchars($mod['name']) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="pt-2">
<button type="submit" class="bg-cyan-500/10 border border-cyan-500/30 hover:bg-cyan-500/20 text-cyan-400 font-mono text-xs uppercase tracking-wider py-3 px-5 rounded-xl transition-all font-bold cursor-pointer">
Link generieren →
</button>
</div>
</form>
</div>
<!-- Existing Invites Table -->
<div class="overflow-x-auto border border-slate-800 rounded-2xl">
<table class="w-full text-left font-mono text-xs text-slate-400">
<thead class="bg-slate-950 text-cyan-400 uppercase text-[10px] tracking-wider">
<tr>
<th class="px-4 py-3 border-b border-slate-800">Token & Link</th>
<th class="px-4 py-3 border-b border-slate-800">Empfänger</th>
<th class="px-4 py-3 border-b border-slate-800">Notiz</th>
<th class="px-4 py-3 border-b border-slate-800">Module</th>
<th class="px-4 py-3 border-b border-slate-800">Nutzungen</th>
<th class="px-4 py-3 border-b border-slate-800">Gültig bis</th>
<th class="px-4 py-3 border-b border-slate-800">Status</th>
<th class="px-4 py-3 border-b border-slate-800 text-right">Aktionen</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/60 bg-slate-900/30">
<?php if (empty($invitations)): ?>
<tr><td colspan="8" class="px-4 py-6 text-center text-slate-500">Noch keine Einladungslinks erstellt.</td></tr>
<?php else: foreach ($invitations as $inv):
$fullUrl = $baseUrl . '/register?token=' . $inv['token'];
?>
<tr class="hover:bg-slate-800/30 transition-colors">
<td class="px-4 py-3.5">
<div class="flex items-center gap-2">
<span class="text-slate-300 font-mono text-[11px] select-all"><?= substr($inv['token'], 0, 10) ?>...</span>
<button type="button" onclick="copyInviteLink('<?= $fullUrl ?>', this)" class="px-2.5 py-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/30 rounded-lg text-[10px] transition-colors cursor-pointer tracking-wider uppercase">
Kopieren
</button>
</div>
</td>
<td class="px-4 py-3.5">
<?php if (!empty($inv['recipient_email'])): ?>
<span class="text-cyan-400 font-medium"><?= htmlspecialchars($inv['recipient_email']) ?></span>
<?php else: ?>
<span class="text-slate-600"></span>
<?php endif; ?>
</td>
<td class="px-4 py-3.5 text-slate-200"><?= htmlspecialchars($inv['note'] ?: '-') ?></td>
<td class="px-4 py-3.5">
<div class="flex flex-wrap gap-1">
<?php if (empty($inv['assigned_projects'])): ?>
<span class="text-slate-600">-</span>
<?php else: foreach ($inv['assigned_projects'] as $slug):
$mod = $allModules[$slug] ?? null;
?>
<span class="px-2 py-0.5 bg-slate-950 border border-slate-800 rounded-md text-[10px] text-slate-300 uppercase">
<?= htmlspecialchars($mod['name'] ?? $slug) ?>
</span>
<?php endforeach; endif; ?>
</div>
</td>
<td class="px-4 py-3.5 text-slate-300"><?= $inv['uses_count'] ?> / <?= $inv['max_uses'] > 0 ? $inv['max_uses'] : '∞' ?></td>
<td class="px-4 py-3.5 text-slate-400"><?= $inv['expires_at'] ? date('d.m.Y H:i', strtotime($inv['expires_at'])) : 'Unbegrenzt' ?></td>
<td class="px-4 py-3.5">
<?php if ($inv['is_valid']): ?>
<span class="px-2 py-0.5 bg-emerald-500/10 text-emerald-400 border border-emerald-500/30 rounded-md text-[10px] uppercase">Aktiv</span>
<?php else: ?>
<span class="px-2 py-0.5 bg-rose-500/10 text-rose-400 border border-rose-500/30 rounded-md text-[10px] uppercase">Inaktiv</span>
<?php endif; ?>
</td>
<td class="px-4 py-3.5 text-right whitespace-nowrap">
<?php if (!empty($inv['recipient_email']) && $inv['is_valid']): ?>
<form action="<?= $baseUrl ?>/admin/invite_resend/<?= $inv['id'] ?>" method="POST" class="inline">
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
<button type="submit" class="px-2 py-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/30 rounded-lg text-[10px] uppercase font-mono mr-2 cursor-pointer transition-colors" title="Einladung erneut per E-Mail senden">
E-Mail senden
</button>
</form>
<?php endif; ?>
<form action="<?= $baseUrl ?>/admin/invite_delete/<?= $inv['id'] ?>" method="POST" class="inline" onsubmit="return confirm('Diesen Einladungslink wirklich löschen?');">
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
<button type="submit" class="text-rose-400 hover:text-rose-300 text-xs cursor-pointer">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
<?php else: ?>
<!-- ================= TAB: BENUTZERVERWALTUNG ================= -->
<div class="overflow-x-auto border border-slate-800 rounded-2xl">
<table class="w-full text-left font-mono text-xs text-slate-400">
<thead class="bg-slate-950 text-cyan-400 uppercase text-[10px] tracking-wider">
<tr>
<th class="px-4 py-3.5 border-b border-slate-800">ID</th>
<th class="px-4 py-3.5 border-b border-slate-800">Name</th>
<th class="px-4 py-3.5 border-b border-slate-800">Email</th>
<th class="px-4 py-3.5 border-b border-slate-800">Rolle</th>
<th class="px-4 py-3.5 border-b border-slate-800">Freigeschaltete Module</th>
<th class="px-4 py-3.5 border-b border-slate-800">Status</th>
<th class="px-4 py-3.5 border-b border-slate-800 text-right">Aktionen</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/60 bg-slate-900/30">
<?php if (empty($users)): ?>
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">Keine Benutzer vorhanden.</td></tr>
<?php else: foreach ($users as $u): ?>
<tr class="hover:bg-slate-800/30 transition-colors">
<td class="px-4 py-3.5 text-slate-500">#<?= $u['id'] ?></td>
<td class="px-4 py-3.5 text-slate-200 font-bold"><?= htmlspecialchars($u['name']) ?></td>
<td class="px-4 py-3.5 text-slate-400 break-all"><?= htmlspecialchars($u['email']) ?></td>
<td class="px-4 py-3.5">
<?php if (!empty($u['is_admin'])): ?>
<span class="px-2 py-0.5 bg-fuchsia-500/10 text-fuchsia-400 border border-fuchsia-500/30 rounded-md text-[10px] font-bold uppercase tracking-wider">Admin</span>
<?php else: ?>
<span class="text-slate-500 uppercase tracking-wider text-[10px]">User</span>
<?php endif; ?>
</td>
<td class="px-4 py-3.5">
<div class="flex flex-wrap gap-1 max-w-xs">
<?php
$proj = $u['projects'] ?? [];
if (!empty($u['is_admin'])): ?>
<span class="px-2 py-0.5 bg-fuchsia-950/60 border border-fuchsia-800/40 rounded-md text-[10px] text-fuchsia-300 uppercase tracking-wider">
Alle Module (Vollzugriff)
</span>
<?php elseif (empty($proj)): ?>
<span class="text-slate-600 italic text-[10px]">Keine</span>
<?php else: foreach ($proj as $slug):
$mod = $allModules[$slug] ?? null;
?>
<span class="px-2 py-0.5 bg-slate-950 border border-slate-800 rounded-md text-[10px] text-slate-300 uppercase">
<?= htmlspecialchars($mod['name'] ?? $slug) ?>
</span>
<?php endforeach; endif; ?>
</div>
</td>
<td class="px-4 py-3.5">
<form action="<?= $baseUrl ?>/admin/user_toggle_status/<?= $u['id'] ?>" method="POST" class="inline">
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
<button type="submit" class="px-2.5 py-1 rounded-lg text-[10px] font-mono border transition-all cursor-pointer uppercase tracking-wider <?= $u['status'] === 'Active' ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/20' : 'bg-rose-500/10 text-rose-400 border-rose-500/30 hover:bg-rose-500/20' ?>" title="Klicken zum Umschalten">
<?= $u['status'] === 'Active' ? 'Aktiv' : 'Inaktiv' ?>
</button>
</form>
</td>
<td class="px-4 py-3.5 text-right whitespace-nowrap">
<div class="flex justify-end items-center gap-3">
<a href="<?= $baseUrl ?>/admin/user_form/<?= $u['id'] ?>" class="text-cyan-400 hover:text-cyan-300">Bearbeiten</a>
<form action="<?= $baseUrl ?>/admin/user_delete/<?= $u['id'] ?>" method="POST" class="inline" onsubmit="return confirm('Benutzer <?= htmlspecialchars(addslashes($u['name'])) ?> wirklich löschen?');">
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
<button type="submit" class="text-rose-400 hover:text-rose-300">Löschen</button>
</form>
</div>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</main>
</div>
<script>
function copyInviteLink(url, btn) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(url).then(showSuccess, fallback);
} else {
fallback();
}
function fallback() {
var textArea = document.createElement("textarea");
textArea.value = url;
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
showSuccess();
} catch (err) {
alert('Link konnte nicht kopiert werden: ' + url);
}
document.body.removeChild(textArea);
}
function showSuccess() {
var originalText = btn.innerHTML;
btn.innerHTML = 'Kopiert';
btn.classList.remove('bg-cyan-500/10', 'text-cyan-400');
btn.classList.add('bg-cyan-500', 'text-slate-950', 'font-bold');
setTimeout(function() {
btn.innerHTML = originalText;
btn.classList.remove('bg-cyan-500', 'text-slate-950', 'font-bold');
btn.classList.add('bg-cyan-500/10', 'text-cyan-400');
}, 2000);
}
}
</script>
<?php require_once __DIR__ . '/../inc/admin_footer.php'; ?>