UI redesign step 7 (Benutzer-Verwaltung): Stitch style

- Header gets the same title/subtitle pair + primary "Neuer Benutzer"
  pill on the right that the other tabs use, with the person_add
  glyph.
- Table shell now matches Übersicht/Provisionen: bordered white card,
  surface-container-high header band with label-caps column titles,
  zebra rows, hover highlight.
- "Admin" column renders a verified_user icon for admins and a long
  dash otherwise instead of the previous green check. The username
  column shows a small primary "Sie" badge for the logged-in user.
- Action column collapses to two icon-only buttons (edit / delete);
  delete is rendered with reduced opacity and disabled when it is
  the logged-in user's row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kerimatilganandClaude Opus 4.7 committed 2026-05-15 20:27:21 +02:00
1 parent 968cb183f2
commit ff0d7398db
2 files changed
+57 -22

No files matched your search

+28 -11
View File
@@ -1815,23 +1815,40 @@
<!-- Benutzer-Verwaltung Tab (admin only) -->
<div id="users" class="tab-content">
<h2>Benutzer-Verwaltung</h2>
<div style="margin-bottom: 20px;">
<button type="button" class="btn-success" onclick="openUserModal()">+ Neuer Benutzer</button>
<div class="flex flex-col gap-lg">
<!-- Page header -->
<div class="flex justify-between items-end flex-wrap gap-md">
<div>
<h1 class="text-headline-md text-on-background mb-xs">Benutzer-Verwaltung</h1>
<p class="text-body-sm text-on-surface-variant">Verwalten Sie die Anmeldedaten und Berechtigungen der Anwender.</p>
</div>
<table class="data-table" style="width:100%; border-collapse: collapse;">
<thead>
<button type="button" onclick="openUserModal()"
class="bg-primary-container text-on-primary hover:bg-primary transition-colors rounded-lg px-md py-sm text-body-sm flex items-center gap-sm shadow-sm">
<span class="material-symbols-outlined text-[18px]">person_add</span>
Neuer Benutzer
</button>
</div>
<!-- Data table -->
<div class="bg-surface-container-lowest border border-outline-variant rounded-lg shadow-[0_2px_4px_rgba(0,0,0,0.04)] overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead class="bg-surface-container-high border-b-2 border-outline-variant">
<tr>
<th>Username</th>
<th>Anzeigename</th>
<th>Admin</th>
<th>Erstellt</th>
<th>Aktionen</th>
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Username</th>
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Anzeigename</th>
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase text-center w-[80px]">Admin</th>
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase w-[180px]">Erstellt</th>
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase text-center w-[120px]">Aktionen</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
<tbody id="usersTableBody" class="text-body-sm text-on-surface"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal: Benutzer anlegen / bearbeiten -->
<div id="userModal" class="app-modal" style="display:none;">
+29 -11
View File
@@ -3979,29 +3979,47 @@ document.addEventListener('DOMContentLoaded', function() {
async function loadUsers() {
const tbody = document.getElementById('usersTableBody');
if (!tbody) return;
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#666;">Lade...</td></tr>';
tbody.innerHTML = '<tr><td colspan="5" class="px-md py-lg text-center text-body-sm text-on-surface-variant">Lade…</td></tr>';
try {
const users = await apiRequest('/api/users');
tbody.innerHTML = '';
if (!users.length) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#666;">Keine Benutzer</td></tr>';
tbody.innerHTML = '<tr><td colspan="5" class="px-md py-lg text-center text-body-sm text-on-surface-variant">Keine Benutzer</td></tr>';
return;
}
users.forEach(u => {
const tr = document.createElement('tr');
const isSelf = currentUser && currentUser.username === u.username;
const tr = document.createElement('tr');
tr.className = 'border-b border-outline-variant/30 even:bg-surface-container-low/50 hover:bg-surface-variant/30 transition-colors';
tr.innerHTML = `
<td>${escapeHtml(u.username)}${isSelf ? ' <small style="color:#3498db;">(Sie)</small>' : ''}</td>
<td>${escapeHtml(u.displayName || '')}</td>
<td>${u.isAdmin ? '<span style="color:#27ae60;">✓</span>' : ''}</td>
<td>${escapeHtml(formatDateTime(u.createdAt))}</td>
<td>
<button data-user-action="edit" data-uuid="${escapeHtml(u.uuid)}" class="btn-success" style="margin-right:5px;">Bearbeiten</button>
<button data-user-action="delete" data-uuid="${escapeHtml(u.uuid)}" data-username="${escapeHtml(u.username)}" class="btn-danger" ${isSelf ? 'disabled title="Eigener Account kann nicht gelöscht werden"' : ''}>Löschen</button>
<td class="px-md py-xs">
<span class="font-semibold text-on-background">${escapeHtml(u.username)}</span>
${isSelf ? '<span class="ml-xs text-label-caps text-primary">Sie</span>' : ''}
</td>
<td class="px-md py-xs text-on-surface">${escapeHtml(u.displayName || '–')}</td>
<td class="px-md py-xs text-center">
${u.isAdmin
? '<span class="material-symbols-outlined text-primary text-[20px]" title="Administrator">verified_user</span>'
: '<span class="text-on-surface-variant">—</span>'}
</td>
<td class="px-md py-xs font-label-numeric text-label-numeric text-on-surface-variant">${escapeHtml(formatDateTime(u.createdAt))}</td>
<td class="px-md py-xs">
<div class="flex items-center justify-center gap-sm">
<button data-user-action="edit" data-uuid="${escapeHtml(u.uuid)}" title="Bearbeiten"
class="text-outline hover:text-primary transition-colors">
<span class="material-symbols-outlined text-[18px]">edit</span>
</button>
<button data-user-action="delete" data-uuid="${escapeHtml(u.uuid)}" data-username="${escapeHtml(u.username)}" title="${isSelf ? 'Eigener Account kann nicht gelöscht werden' : 'Löschen'}"
class="text-outline hover:text-error transition-colors ${isSelf ? 'opacity-40 cursor-not-allowed' : ''}"
${isSelf ? 'disabled' : ''}>
<span class="material-symbols-outlined text-[18px]">delete</span>
</button>
</div>
</td>
`;
tr.querySelectorAll('button[data-user-action]').forEach(btn => {
btn.addEventListener('click', () => {
if (btn.disabled) return;
const action = btn.dataset.userAction;
const uuid = btn.dataset.uuid;
if (action === 'edit') openUserModal(u);
@@ -4011,7 +4029,7 @@ async function loadUsers() {
tbody.appendChild(tr);
});
} catch (err) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#c0392b;">Fehler beim Laden</td></tr>';
tbody.innerHTML = '<tr><td colspan="5" class="px-md py-lg text-center text-body-sm text-error">Fehler beim Laden</td></tr>';
}
}