750 lines
34 KiB
HTML
750 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Benutzerverwaltung – Design Demo</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
:root {
|
||
--primary: #3fa3a3;
|
||
--primary-dark: #008487;
|
||
--bg: #0f172a;
|
||
--bg2: #1e293b;
|
||
--bg3: #334155;
|
||
--text: #f1f5f9;
|
||
--text2: #94a3b8;
|
||
--text3: #64748b;
|
||
--border: #334155;
|
||
--success: #10b981;
|
||
--danger: #ef4444;
|
||
--warning: #f59e0b;
|
||
--info: #3b82f6;
|
||
--radius: 10px;
|
||
--shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||
}
|
||
|
||
body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
|
||
|
||
/* ── Demo Selector Bar ── */
|
||
.demo-bar {
|
||
background: #0a1628;
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 12px 24px;
|
||
display: flex; align-items: center; gap: 16px;
|
||
position: sticky; top: 0; z-index: 100;
|
||
}
|
||
.demo-bar-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--text3); }
|
||
.demo-tabs { display: flex; gap: 6px; }
|
||
.demo-tab {
|
||
padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500;
|
||
border: 1px solid var(--border); background: transparent; color: var(--text2);
|
||
cursor: pointer; transition: all 0.15s;
|
||
}
|
||
.demo-tab:hover { border-color: var(--primary); color: var(--primary); }
|
||
.demo-tab.active { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||
.demo-tab.recommended::after { content: ' ★'; font-size: 11px; }
|
||
|
||
/* ── Page Container ── */
|
||
.page { display: none; padding: 28px 32px; max-width: 1300px; margin: 0 auto; }
|
||
.page.visible { display: block; }
|
||
|
||
/* ── Page Header ── */
|
||
.page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
|
||
.page-title { font-size: 22px; font-weight: 700; }
|
||
.btn-group { display: flex; gap: 8px; }
|
||
.btn {
|
||
padding: 9px 18px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||
border: none; cursor: pointer; transition: all 0.15s; font-family: inherit;
|
||
}
|
||
.btn-primary { background: var(--primary); color: #fff; }
|
||
.btn-primary:hover { background: var(--primary-dark); }
|
||
.btn-secondary { background: var(--bg3); color: var(--text); border: 1px solid var(--border); }
|
||
.btn-secondary:hover { background: var(--bg2); border-color: var(--primary); }
|
||
|
||
/* ── Stats Cards ── */
|
||
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 20px; }
|
||
.stat-card {
|
||
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
|
||
padding: 16px 20px; display: flex; align-items: center; gap: 14px;
|
||
transition: border-color 0.15s;
|
||
}
|
||
.stat-card:hover { border-color: var(--primary); }
|
||
.stat-icon {
|
||
width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center;
|
||
justify-content: center; font-size: 18px; flex-shrink: 0;
|
||
}
|
||
.stat-icon.blue { background: rgba(59,130,246,0.15); }
|
||
.stat-icon.green { background: rgba(16,185,129,0.15); }
|
||
.stat-icon.red { background: rgba(239,68,68,0.15); }
|
||
.stat-icon.yellow { background: rgba(245,158,11,0.15); }
|
||
.stat-num { font-size: 24px; font-weight: 700; line-height: 1; }
|
||
.stat-label { font-size: 12px; color: var(--text2); margin-top: 3px; }
|
||
|
||
/* ── Filter Bar ── */
|
||
.filter-bar { display: flex; gap: 10px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
|
||
.search-input {
|
||
background: var(--bg2); border: 1px solid var(--border); color: var(--text);
|
||
border-radius: 8px; padding: 9px 14px; font-size: 13px; font-family: inherit;
|
||
outline: none; width: 280px; transition: border-color 0.15s;
|
||
}
|
||
.search-input:focus { border-color: var(--primary); }
|
||
.filter-chips { display: flex; gap: 6px; flex-wrap: wrap; }
|
||
.chip {
|
||
padding: 5px 12px; border-radius: 20px; font-size: 12px; font-weight: 500;
|
||
border: 1px solid var(--border); background: var(--bg2); color: var(--text2);
|
||
cursor: pointer; transition: all 0.12s; white-space: nowrap;
|
||
}
|
||
.chip:hover { border-color: var(--primary); color: var(--primary); }
|
||
.chip.active { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||
.chip.active-red { background: var(--danger); border-color: var(--danger); color: #fff; }
|
||
.chip.active-green { background: var(--success); border-color: var(--success); color: #fff; }
|
||
.chip-sep { width: 1px; height: 24px; background: var(--border); margin: 0 2px; }
|
||
|
||
/* ── Table ── */
|
||
.card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
thead th {
|
||
background: var(--bg3); text-align: left; padding: 11px 16px;
|
||
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px;
|
||
color: var(--text3); border-bottom: 1px solid var(--border); white-space: nowrap;
|
||
}
|
||
tbody tr { border-bottom: 1px solid rgba(51,65,85,0.5); transition: background 0.12s; }
|
||
tbody tr:last-child { border-bottom: none; }
|
||
tbody tr:hover { background: rgba(63,163,163,0.04); }
|
||
td { padding: 12px 16px; font-size: 13px; vertical-align: middle; }
|
||
|
||
/* ── Avatar ── */
|
||
.user-cell { display: flex; align-items: center; gap: 10px; }
|
||
.avatar {
|
||
width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center;
|
||
justify-content: center; font-size: 12px; font-weight: 700; flex-shrink: 0;
|
||
color: #fff;
|
||
}
|
||
.user-name { font-weight: 500; font-size: 13px; }
|
||
.user-username { font-size: 11px; color: var(--text3); }
|
||
|
||
/* ── Badges ── */
|
||
.badge {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 600;
|
||
text-transform: uppercase; letter-spacing: 0.4px;
|
||
}
|
||
.badge-dot { width: 6px; height: 6px; border-radius: 50%; }
|
||
.badge-active { background: rgba(16,185,129,0.15); color: var(--success); }
|
||
.badge-inactive { background: rgba(239,68,68,0.1); color: var(--danger); }
|
||
.role-badge {
|
||
padding: 3px 9px; border-radius: 6px; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px;
|
||
}
|
||
|
||
/* ── Action buttons (old style for Option A) ── */
|
||
.actions-old { display: flex; gap: 6px; }
|
||
.btn-sm { padding: 5px 12px; border-radius: 6px; font-size: 11px; font-weight: 600; border: none; cursor: pointer; font-family: inherit; }
|
||
.btn-sm-edit { background: rgba(63,163,163,0.15); color: var(--primary); }
|
||
.btn-sm-edit:hover { background: rgba(63,163,163,0.25); }
|
||
.btn-sm-toggle { background: rgba(148,163,184,0.1); color: var(--text2); }
|
||
.btn-sm-toggle:hover { background: rgba(148,163,184,0.2); }
|
||
.btn-sm-del { background: rgba(239,68,68,0.1); color: var(--danger); }
|
||
.btn-sm-del:hover { background: rgba(239,68,68,0.2); }
|
||
|
||
/* ── 3-dot menu (Option B+C) ── */
|
||
.menu-wrap { position: relative; display: inline-block; }
|
||
.menu-btn {
|
||
width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--border);
|
||
background: transparent; color: var(--text2); cursor: pointer; font-size: 18px;
|
||
display: flex; align-items: center; justify-content: center; transition: all 0.12s;
|
||
line-height: 1;
|
||
}
|
||
.menu-btn:hover { background: var(--bg3); color: var(--text); border-color: var(--primary); }
|
||
.dropdown {
|
||
position: absolute; right: 0; top: calc(100% + 4px); background: var(--bg2);
|
||
border: 1px solid var(--border); border-radius: 8px; min-width: 160px;
|
||
box-shadow: var(--shadow); z-index: 50; overflow: hidden; display: none;
|
||
}
|
||
.dropdown.open { display: block; }
|
||
.dropdown-item {
|
||
padding: 9px 14px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px;
|
||
transition: background 0.1s; color: var(--text);
|
||
}
|
||
.dropdown-item:hover { background: var(--bg3); }
|
||
.dropdown-item.danger { color: var(--danger); }
|
||
.dropdown-item.danger:hover { background: rgba(239,68,68,0.1); }
|
||
.dropdown-sep { height: 1px; background: var(--border); margin: 3px 0; }
|
||
|
||
/* Inline role dropdown */
|
||
.role-select {
|
||
background: var(--bg3); border: 1px solid var(--border); color: var(--text);
|
||
border-radius: 6px; padding: 4px 8px; font-size: 12px; font-family: inherit;
|
||
cursor: pointer; outline: none; transition: border-color 0.12s;
|
||
}
|
||
.role-select:focus { border-color: var(--primary); }
|
||
|
||
/* ── Relative time ── */
|
||
.time-abs { font-size: 12px; color: var(--text2); }
|
||
.time-rel { font-size: 11px; color: var(--text3); }
|
||
.time-never { color: var(--text3); font-style: italic; }
|
||
|
||
/* ── Slideover (Option C) ── */
|
||
.slideover-layout { display: flex; gap: 0; position: relative; }
|
||
.table-area { flex: 1; min-width: 0; transition: margin-right 0.3s; }
|
||
.table-area.panel-open { margin-right: 0; }
|
||
.slideover {
|
||
width: 360px; flex-shrink: 0; background: var(--bg2); border: 1px solid var(--border);
|
||
border-radius: var(--radius); margin-left: 16px;
|
||
animation: slideIn 0.25s ease;
|
||
overflow-y: auto; max-height: calc(100vh - 200px);
|
||
}
|
||
@keyframes slideIn { from { opacity: 0; transform: translateX(30px); } to { opacity: 1; transform: translateX(0); } }
|
||
|
||
.panel-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
|
||
.panel-avatar { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: #fff; flex-shrink: 0; }
|
||
.panel-name { font-weight: 700; font-size: 15px; }
|
||
.panel-email { font-size: 12px; color: var(--text2); }
|
||
.panel-close { margin-left: auto; background: none; border: none; color: var(--text2); font-size: 20px; cursor: pointer; padding: 4px; border-radius: 6px; }
|
||
.panel-close:hover { background: var(--bg3); color: var(--text); }
|
||
|
||
.panel-section { padding: 16px 20px; border-bottom: 1px solid var(--border); }
|
||
.panel-section:last-child { border-bottom: none; }
|
||
.panel-section-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--text3); margin-bottom: 12px; }
|
||
.panel-field { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 13px; }
|
||
.panel-field:last-child { margin-bottom: 0; }
|
||
.panel-field-label { color: var(--text2); }
|
||
.panel-actions { padding: 14px 20px; display: flex; gap: 8px; flex-direction: column; }
|
||
.btn-full { width: 100%; padding: 9px; text-align: center; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; border: none; font-family: inherit; transition: all 0.12s; }
|
||
.btn-full-primary { background: var(--primary); color: #fff; }
|
||
.btn-full-primary:hover { background: var(--primary-dark); }
|
||
.btn-full-warn { background: rgba(245,158,11,0.12); color: var(--warning); border: 1px solid rgba(245,158,11,0.3); }
|
||
.btn-full-warn:hover { background: rgba(245,158,11,0.2); }
|
||
.btn-full-danger { background: rgba(239,68,68,0.1); color: var(--danger); border: 1px solid rgba(239,68,68,0.25); }
|
||
.btn-full-danger:hover { background: rgba(239,68,68,0.18); }
|
||
|
||
/* Row clickable for C */
|
||
.clickable-row { cursor: pointer; }
|
||
.clickable-row.selected-row { background: rgba(63,163,163,0.08) !important; }
|
||
.clickable-row.selected-row td { border-left: 2px solid var(--primary); }
|
||
.clickable-row.selected-row td:not(:first-child) { border-left: none; }
|
||
|
||
/* ── Last login ── */
|
||
td .last-login { display: flex; flex-direction: column; }
|
||
|
||
/* ── Tooltip style note ── */
|
||
.design-note {
|
||
background: rgba(63,163,163,0.08); border: 1px solid rgba(63,163,163,0.2);
|
||
border-radius: 8px; padding: 10px 16px; margin-bottom: 16px;
|
||
font-size: 12px; color: var(--text2); line-height: 1.6;
|
||
}
|
||
.design-note strong { color: var(--primary); }
|
||
|
||
.combo-badge {
|
||
background: var(--primary); color: #fff; font-size: 10px; font-weight: 700;
|
||
padding: 2px 8px; border-radius: 20px; margin-left: 8px; text-transform: uppercase;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Demo Switcher Bar -->
|
||
<div class="demo-bar">
|
||
<span class="demo-bar-label">Design-Option:</span>
|
||
<div class="demo-tabs">
|
||
<button class="demo-tab" onclick="show('a')">A – Stats + Filter-Chips</button>
|
||
<button class="demo-tab" onclick="show('b')">B – Avatar + 3-Punkte-Menü</button>
|
||
<button class="demo-tab" onclick="show('c')">C – Slideover-Panel</button>
|
||
<button class="demo-tab recommended active" onclick="show('combo')">Kombiniert (Empfehlung)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ════════════════════════════════════════════════════════
|
||
OPTION A – Stats + Filter Chips
|
||
════════════════════════════════════════════════════════ -->
|
||
<div id="page-a" class="page">
|
||
<div class="page-header">
|
||
<h1 class="page-title">Benutzerverwaltung</h1>
|
||
<div class="btn-group">
|
||
<button class="btn btn-secondary">☁️ Aus Azure importieren</button>
|
||
<button class="btn btn-primary">+ Neuer Benutzer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="design-note">
|
||
<strong>Option A:</strong> Statistik-Kacheln oben + Filter-Chips statt nur Textsuche. Tabelle bleibt gleich. Kleinster Aufwand, sofortiger Informationsgewinn.
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="stat-icon blue">👥</div>
|
||
<div><div class="stat-num">14</div><div class="stat-label">Benutzer gesamt</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon green">✅</div>
|
||
<div><div class="stat-num">11</div><div class="stat-label">Aktiv</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon red">🚫</div>
|
||
<div><div class="stat-num">3</div><div class="stat-label">Deaktiviert</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon yellow">⏱️</div>
|
||
<div><div class="stat-num">4</div><div class="stat-label">Noch nie eingeloggt</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter Bar -->
|
||
<div class="filter-bar">
|
||
<input class="search-input" type="text" placeholder="🔍 Suche nach Name, E-Mail, Rolle…" oninput="filterA(this.value)">
|
||
<div class="chip-sep"></div>
|
||
<div class="filter-chips" id="role-chips-a">
|
||
<span class="chip active" onclick="chipClick(this,'a')">Alle Rollen</span>
|
||
<span class="chip" onclick="chipClick(this,'a')">👑 Super Admin</span>
|
||
<span class="chip" onclick="chipClick(this,'a')">🛡️ Admin</span>
|
||
<span class="chip" onclick="chipClick(this,'a')">🎧 Support</span>
|
||
<span class="chip" onclick="chipClick(this,'a')">🔧 Bearbeiter</span>
|
||
<span class="chip" onclick="chipClick(this,'a')">👤 Benutzer</span>
|
||
</div>
|
||
<div class="chip-sep"></div>
|
||
<span class="chip active-green active" onclick="statusChip(this,'aktiv')">● Aktiv</span>
|
||
<span class="chip" onclick="statusChip(this,'inaktiv')">● Inaktiv</span>
|
||
</div>
|
||
|
||
<!-- Table (same as current, just with search) -->
|
||
<div class="card">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzername</th>
|
||
<th>E-Mail</th>
|
||
<th>Name</th>
|
||
<th>Rolle</th>
|
||
<th>Status</th>
|
||
<th>Letzter Login</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody-a"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ════════════════════════════════════════════════════════
|
||
OPTION B – Avatar + 3-Punkte-Menü
|
||
════════════════════════════════════════════════════════ -->
|
||
<div id="page-b" class="page">
|
||
<div class="page-header">
|
||
<h1 class="page-title">Benutzerverwaltung</h1>
|
||
<div class="btn-group">
|
||
<button class="btn btn-secondary">☁️ Aus Azure importieren</button>
|
||
<button class="btn btn-primary">+ Neuer Benutzer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="design-note">
|
||
<strong>Option B:</strong> Avatar-Initialen in der ersten Spalte + Aktionen als 3-Punkte-Menü (spart viel Platz, besonders bei langen Buttons). Rolle inline per Dropdown änderbar.
|
||
</div>
|
||
|
||
<div class="filter-bar">
|
||
<input class="search-input" type="text" placeholder="🔍 Suche nach Name, E-Mail, Rolle…">
|
||
</div>
|
||
|
||
<div class="card">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzer</th>
|
||
<th>E-Mail</th>
|
||
<th>Rolle</th>
|
||
<th>Status</th>
|
||
<th>Letzter Login</th>
|
||
<th style="width:50px">···</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody-b"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ════════════════════════════════════════════════════════
|
||
OPTION C – Slideover-Panel
|
||
════════════════════════════════════════════════════════ -->
|
||
<div id="page-c" class="page">
|
||
<div class="page-header">
|
||
<h1 class="page-title">Benutzerverwaltung</h1>
|
||
<div class="btn-group">
|
||
<button class="btn btn-secondary">☁️ Aus Azure importieren</button>
|
||
<button class="btn btn-primary">+ Neuer Benutzer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="design-note">
|
||
<strong>Option C:</strong> Klick auf eine Zeile öffnet ein Detailpanel rechts. Tabelle bleibt links sichtbar. Kein Modal-Overlay. → <em>Klick auf eine Zeile zum Testen!</em>
|
||
</div>
|
||
|
||
<div class="filter-bar">
|
||
<input class="search-input" type="text" placeholder="🔍 Suche nach Name, E-Mail, Rolle…">
|
||
</div>
|
||
|
||
<div class="slideover-layout" id="c-layout">
|
||
<div class="table-area card" id="c-table-area">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzer</th>
|
||
<th>E-Mail</th>
|
||
<th>Rolle</th>
|
||
<th>Status</th>
|
||
<th>Letzter Login</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody-c"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="slideover" id="c-panel" style="display:none"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ════════════════════════════════════════════════════════
|
||
KOMBINATION (Empfehlung): Stats + Filter + Avatar + 3-Punkte + Slideover
|
||
════════════════════════════════════════════════════════ -->
|
||
<div id="page-combo" class="page visible">
|
||
<div class="page-header">
|
||
<div>
|
||
<h1 class="page-title">Benutzerverwaltung <span class="combo-badge">Empfehlung</span></h1>
|
||
</div>
|
||
<div class="btn-group">
|
||
<button class="btn btn-secondary">☁️ Aus Azure importieren</button>
|
||
<button class="btn btn-primary">+ Neuer Benutzer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="design-note">
|
||
<strong>Kombination aus A + B + C:</strong> Stats-Kacheln · Filter-Chips · Avatar-Initialen · 3-Punkte-Menü · Slideover-Panel bei Klick auf eine Zeile. → <em>Klick auf eine Zeile öffnet das Detail-Panel!</em>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="stat-icon blue">👥</div>
|
||
<div><div class="stat-num">14</div><div class="stat-label">Benutzer gesamt</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon green">✅</div>
|
||
<div><div class="stat-num">11</div><div class="stat-label">Aktiv</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon red">🚫</div>
|
||
<div><div class="stat-num">3</div><div class="stat-label">Deaktiviert</div></div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon yellow">⏱️</div>
|
||
<div><div class="stat-num">4</div><div class="stat-label">Noch nie eingeloggt</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter -->
|
||
<div class="filter-bar">
|
||
<input class="search-input" type="text" placeholder="🔍 Suche nach Name, E-Mail, Rolle…">
|
||
<div class="chip-sep"></div>
|
||
<div class="filter-chips">
|
||
<span class="chip active" onclick="chipClick(this,'combo')">Alle Rollen</span>
|
||
<span class="chip" onclick="chipClick(this,'combo')">👑 Super Admin</span>
|
||
<span class="chip" onclick="chipClick(this,'combo')">🛡️ Admin</span>
|
||
<span class="chip" onclick="chipClick(this,'combo')">🎧 Support</span>
|
||
<span class="chip" onclick="chipClick(this,'combo')">👤 Benutzer</span>
|
||
</div>
|
||
<div class="chip-sep"></div>
|
||
<span class="chip active-green active">● Aktiv</span>
|
||
<span class="chip">● Inaktiv</span>
|
||
</div>
|
||
|
||
<div class="slideover-layout" id="combo-layout">
|
||
<div class="table-area card" id="combo-table-area">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Benutzer</th>
|
||
<th>E-Mail</th>
|
||
<th>Rolle</th>
|
||
<th>Status</th>
|
||
<th>Letzter Login</th>
|
||
<th style="width:50px">···</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbody-combo"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="slideover" id="combo-panel" style="display:none"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ── Mock Data ──
|
||
const ROLE_META = {
|
||
super_admin: { icon: '👑', color: '#ef4444', label: 'Super Admin', avatarBg: '#7f1d1d' },
|
||
admin: { icon: '🛡️', color: '#f97316', label: 'Admin', avatarBg: '#7c2d12' },
|
||
support: { icon: '🎧', color: '#3b82f6', label: 'IT-Support', avatarBg: '#1e3a8a' },
|
||
bearbeiter: { icon: '🔧', color: '#8b5cf6', label: 'Bearbeiter', avatarBg: '#4c1d95' },
|
||
benutzer: { icon: '👤', color: '#64748b', label: 'Benutzer', avatarBg: '#1e293b' },
|
||
hr_personal: { icon: '🧑💼', color: '#10b981', label: 'HR', avatarBg: '#064e3b' },
|
||
buchhaltung: { icon: '💶', color: '#a78bfa', label: 'Buchhaltung', avatarBg: '#3b0764' },
|
||
};
|
||
|
||
const AVATAR_COLORS = ['#1d4ed8','#0f766e','#b45309','#be185d','#7c3aed','#0369a1','#15803d','#c2410c'];
|
||
function avatarColor(name) {
|
||
let h = 0; for (let c of name) h = (h * 31 + c.charCodeAt(0)) & 0xffff;
|
||
return AVATAR_COLORS[h % AVATAR_COLORS.length];
|
||
}
|
||
function initials(u) {
|
||
if (u.first_name && u.last_name) return (u.first_name[0]+u.last_name[0]).toUpperCase();
|
||
return u.username.slice(0,2).toUpperCase();
|
||
}
|
||
|
||
const USERS = [
|
||
{ id:1, username:'gruessing', email:'gruessing@cereda-systems.de', first_name:'Simon', last_name:'Grüssing', role:'super_admin', active:true, last_login:'2026-05-27T14:32:00' },
|
||
{ id:2, username:'smueller', email:'smueller@cereda-systems.de', first_name:'Stefan', last_name:'Müller', role:'admin', active:true, last_login:'2026-05-26T09:11:00' },
|
||
{ id:3, username:'jschmidt', email:'jschmidt@cereda-systems.de', first_name:'Julia', last_name:'Schmidt', role:'support', active:true, last_login:'2026-05-27T08:45:00' },
|
||
{ id:4, username:'tbauer', email:'tbauer@cereda-systems.de', first_name:'Thomas', last_name:'Bauer', role:'support', active:true, last_login:'2026-05-25T16:20:00' },
|
||
{ id:5, username:'mweber', email:'mweber@cereda-systems.de', first_name:'Maria', last_name:'Weber', role:'bearbeiter', active:true, last_login:'2026-05-22T11:00:00' },
|
||
{ id:6, username:'fkoch', email:'fkoch@cereda-systems.de', first_name:'Felix', last_name:'Koch', role:'benutzer', active:true, last_login:'2026-05-20T09:00:00' },
|
||
{ id:7, username:'krichter', email:'krichter@cereda-systems.de', first_name:'Klaus', last_name:'Richter', role:'benutzer', active:true, last_login:'2026-05-15T14:00:00' },
|
||
{ id:8, username:'aschneider', email:'aschneider@cereda-systems.de', first_name:'Anna', last_name:'Schneider', role:'hr_personal', active:true, last_login:'2026-05-24T10:30:00' },
|
||
{ id:9, username:'benders', email:'benders@cereda-systems.de', first_name:'Björn', last_name:'Enders', role:'benutzer', active:true, last_login:'2026-05-19T07:55:00' },
|
||
{ id:10, username:'mfischer', email:'mfischer@cereda-systems.de', first_name:'Markus', last_name:'Fischer', role:'buchhaltung', active:true, last_login:'2026-05-23T13:00:00' },
|
||
{ id:11, username:'lbraun', email:'lbraun@cereda-systems.de', first_name:'Lena', last_name:'Braun', role:'benutzer', active:true, last_login:null },
|
||
{ id:12, username:'konferenzraum', email:'konferenzraum@cereda-systems.de', first_name:null, last_name:null, role:'benutzer', active:true, last_login:null },
|
||
{ id:13, username:'poolfahrzeug', email:'poolfahrzeug@cereda-systems.de', first_name:null, last_name:null, role:'benutzer', active:false, last_login:null },
|
||
{ id:14, username:'einkauf', email:'einkauf@cereda-systems.de', first_name:null, last_name:null, role:'benutzer', active:false, last_login:null },
|
||
];
|
||
|
||
function relativeTime(iso) {
|
||
if (!iso) return '<span class="time-never">Nie</span>';
|
||
const diff = Date.now() - new Date(iso).getTime();
|
||
const mins = Math.floor(diff/60000), hrs = Math.floor(mins/60), days = Math.floor(hrs/24);
|
||
let rel = days > 0 ? `vor ${days} Tag${days>1?'en':''}` : hrs > 0 ? `vor ${hrs} Std.` : mins > 0 ? `vor ${mins} Min.` : 'gerade eben';
|
||
const abs = new Date(iso).toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric'});
|
||
return `<div class="last-login"><span class="time-abs">${abs}</span><span class="time-rel">${rel}</span></div>`;
|
||
}
|
||
|
||
function roleBadge(role) {
|
||
const m = ROLE_META[role] || { icon:'🔵', color:'#64748b', label: role };
|
||
return `<span class="role-badge" style="background:${m.color}18;color:${m.color};">${m.icon} ${m.label}</span>`;
|
||
}
|
||
|
||
function statusBadge(active) {
|
||
return active
|
||
? `<span class="badge badge-active"><span class="badge-dot" style="background:var(--success)"></span>Aktiv</span>`
|
||
: `<span class="badge badge-inactive"><span class="badge-dot" style="background:var(--danger)"></span>Inaktiv</span>`;
|
||
}
|
||
|
||
function avatarHtml(u, size=34, fontSize=12) {
|
||
const bg = avatarColor(u.username);
|
||
const ini = initials(u);
|
||
return `<div class="avatar" style="width:${size}px;height:${size}px;background:${bg};font-size:${fontSize}px">${ini}</div>`;
|
||
}
|
||
|
||
// ── Option A ──
|
||
function renderA() {
|
||
const rows = USERS.map(u => `
|
||
<tr>
|
||
<td>${u.username}</td>
|
||
<td style="color:var(--text2);font-size:12px">${u.email}</td>
|
||
<td>${u.first_name || u.last_name ? (u.first_name||'') + ' ' + (u.last_name||'') : '–'}</td>
|
||
<td>${roleBadge(u.role)}</td>
|
||
<td>${statusBadge(u.active)}</td>
|
||
<td>${relativeTime(u.last_login)}</td>
|
||
<td>
|
||
<div class="actions-old">
|
||
<button class="btn-sm btn-sm-edit">Bearbeiten</button>
|
||
<button class="btn-sm btn-sm-toggle">${u.active?'Deaktivieren':'Aktivieren'}</button>
|
||
<button class="btn-sm btn-sm-del">Löschen</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
document.getElementById('tbody-a').innerHTML = rows;
|
||
}
|
||
|
||
// ── Option B ──
|
||
function renderB() {
|
||
const rows = USERS.map(u => `
|
||
<tr>
|
||
<td>
|
||
<div class="user-cell">
|
||
${avatarHtml(u)}
|
||
<div>
|
||
<div class="user-name">${u.first_name || u.last_name ? (u.first_name||'')+' '+(u.last_name||'') : u.username}</div>
|
||
<div class="user-username">@${u.username}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td style="color:var(--text2);font-size:12px">${u.email}</td>
|
||
<td>
|
||
<select class="role-select" title="Rolle direkt ändern">
|
||
${Object.entries(ROLE_META).map(([k,m]) => `<option value="${k}" ${k===u.role?'selected':''}>${m.icon} ${m.label}</option>`).join('')}
|
||
</select>
|
||
</td>
|
||
<td>${statusBadge(u.active)}</td>
|
||
<td>${relativeTime(u.last_login)}</td>
|
||
<td>
|
||
<div class="menu-wrap" onclick="event.stopPropagation()">
|
||
<button class="menu-btn" onclick="toggleMenu(this)">⋯</button>
|
||
<div class="dropdown">
|
||
<div class="dropdown-item">✏️ Bearbeiten</div>
|
||
<div class="dropdown-item">${u.active ? '🚫 Deaktivieren' : '✅ Aktivieren'}</div>
|
||
<div class="dropdown-item">🔑 Passwort zurücksetzen</div>
|
||
<div class="dropdown-sep"></div>
|
||
<div class="dropdown-item danger">🗑️ Löschen</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
document.getElementById('tbody-b').innerHTML = rows;
|
||
}
|
||
|
||
// ── Option C – Slideover ──
|
||
let selectedC = null;
|
||
function renderC() {
|
||
const rows = USERS.map((u,i) => `
|
||
<tr class="clickable-row" id="crow-${i}" onclick="openPanel('c', ${i})">
|
||
<td>
|
||
<div class="user-cell">
|
||
${avatarHtml(u)}
|
||
<div>
|
||
<div class="user-name">${u.first_name || u.last_name ? (u.first_name||'')+' '+(u.last_name||'') : u.username}</div>
|
||
<div class="user-username">@${u.username}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td style="color:var(--text2);font-size:12px">${u.email}</td>
|
||
<td>${roleBadge(u.role)}</td>
|
||
<td>${statusBadge(u.active)}</td>
|
||
<td>${relativeTime(u.last_login)}</td>
|
||
</tr>
|
||
`).join('');
|
||
document.getElementById('tbody-c').innerHTML = rows;
|
||
}
|
||
|
||
// ── Combo ──
|
||
let selectedCombo = null;
|
||
function renderCombo() {
|
||
const rows = USERS.map((u,i) => `
|
||
<tr class="clickable-row" id="combrow-${i}" onclick="openPanel('combo', ${i})">
|
||
<td>
|
||
<div class="user-cell">
|
||
${avatarHtml(u)}
|
||
<div>
|
||
<div class="user-name">${u.first_name || u.last_name ? (u.first_name||'')+' '+(u.last_name||'') : u.username}</div>
|
||
<div class="user-username">@${u.username}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td style="color:var(--text2);font-size:12px">${u.email}</td>
|
||
<td>
|
||
<select class="role-select" onclick="event.stopPropagation()" title="Rolle direkt ändern">
|
||
${Object.entries(ROLE_META).map(([k,m]) => `<option value="${k}" ${k===u.role?'selected':''}>${m.icon} ${m.label}</option>`).join('')}
|
||
</select>
|
||
</td>
|
||
<td>${statusBadge(u.active)}</td>
|
||
<td>${relativeTime(u.last_login)}</td>
|
||
<td>
|
||
<div class="menu-wrap" onclick="event.stopPropagation()">
|
||
<button class="menu-btn" onclick="toggleMenu(this)">⋯</button>
|
||
<div class="dropdown">
|
||
<div class="dropdown-item">✏️ Bearbeiten</div>
|
||
<div class="dropdown-item">${u.active ? '🚫 Deaktivieren' : '✅ Aktivieren'}</div>
|
||
<div class="dropdown-item">🔑 Passwort zurücksetzen</div>
|
||
<div class="dropdown-sep"></div>
|
||
<div class="dropdown-item danger">🗑️ Löschen</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
document.getElementById('tbody-combo').innerHTML = rows;
|
||
}
|
||
|
||
// ── Panel Content ──
|
||
function panelHtml(u, prefix) {
|
||
const m = ROLE_META[u.role] || {};
|
||
const displayName = u.first_name || u.last_name ? `${u.first_name||''} ${u.last_name||''}`.trim() : u.username;
|
||
return `
|
||
<div class="panel-header">
|
||
${avatarHtml(u, 48, 18)}
|
||
<div>
|
||
<div class="panel-name">${displayName}</div>
|
||
<div class="panel-email">${u.email}</div>
|
||
</div>
|
||
<button class="panel-close" onclick="closePanel('${prefix}')">×</button>
|
||
</div>
|
||
<div class="panel-section">
|
||
<div class="panel-section-label">Account</div>
|
||
<div class="panel-field"><span class="panel-field-label">Benutzername</span><span>@${u.username}</span></div>
|
||
<div class="panel-field"><span class="panel-field-label">Rolle</span>${roleBadge(u.role)}</div>
|
||
<div class="panel-field"><span class="panel-field-label">Status</span>${statusBadge(u.active)}</div>
|
||
</div>
|
||
<div class="panel-section">
|
||
<div class="panel-section-label">Aktivität</div>
|
||
<div class="panel-field"><span class="panel-field-label">Letzter Login</span>${u.last_login ? new Date(u.last_login).toLocaleString('de-DE') : '<span style="color:var(--text3);font-style:italic">Noch nie</span>'}</div>
|
||
<div class="panel-field"><span class="panel-field-label">Konto-ID</span><span style="color:var(--text3)">#${u.id}</span></div>
|
||
</div>
|
||
<div class="panel-actions">
|
||
<button class="btn-full btn-full-primary">✏️ Bearbeiten</button>
|
||
<button class="btn-full btn-full-warn">${u.active ? '🚫 Deaktivieren' : '✅ Aktivieren'}</button>
|
||
<button class="btn-full btn-full-danger">🗑️ Löschen</button>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function openPanel(prefix, idx) {
|
||
const u = USERS[idx];
|
||
const panel = document.getElementById(`${prefix}-panel`);
|
||
panel.innerHTML = panelHtml(u, prefix);
|
||
panel.style.display = 'block';
|
||
|
||
// highlight row
|
||
document.querySelectorAll(`#tbody-${prefix} tr`).forEach((r,i) => r.classList.toggle('selected-row', i===idx));
|
||
}
|
||
|
||
function closePanel(prefix) {
|
||
document.getElementById(`${prefix}-panel`).style.display = 'none';
|
||
document.querySelectorAll(`#tbody-${prefix} tr`).forEach(r => r.classList.remove('selected-row'));
|
||
}
|
||
|
||
// ── Menu toggle ──
|
||
function toggleMenu(btn) {
|
||
const dd = btn.nextElementSibling;
|
||
const wasOpen = dd.classList.contains('open');
|
||
// close all
|
||
document.querySelectorAll('.dropdown.open').forEach(d => d.classList.remove('open'));
|
||
if (!wasOpen) dd.classList.add('open');
|
||
}
|
||
document.addEventListener('click', () => {
|
||
document.querySelectorAll('.dropdown.open').forEach(d => d.classList.remove('open'));
|
||
});
|
||
|
||
// ── Filter chips ──
|
||
function chipClick(el, page) {
|
||
el.closest('.filter-chips').querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
|
||
el.classList.add('active');
|
||
}
|
||
|
||
// ── Page switcher ──
|
||
function show(id) {
|
||
document.querySelectorAll('.page').forEach(p => p.classList.remove('visible'));
|
||
document.getElementById('page-' + id).classList.add('visible');
|
||
document.querySelectorAll('.demo-tab').forEach(t => t.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
}
|
||
|
||
// ── Init ──
|
||
renderA();
|
||
renderB();
|
||
renderC();
|
||
renderCombo();
|
||
</script>
|
||
</body>
|
||
</html>
|