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

271 lines
No EOL
19 KiB
PHP

<?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 md:grid-cols-3 gap-4">
<div>
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Notiz / Empfänger</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 (optional)</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>
<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">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">Aktion</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/60 bg-slate-900/30">
<?php if (empty($invitations)): ?>
<tr><td colspan="7" 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 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">
<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'; ?>