fix(web): SUPER_ADMIN-Zeile bietet einem ADMIN keine Aktionsknoepfe mehr an

WINDOWS #36, Aufgabe 3/3: canManageRow spiegelt den Zielrollen-Riegel aus
apps/api/src/user/user.controller.ts (update/remove, WINDOWS #29) rein
ergonomisch — die Serverpruefung bleibt unveraendert und ist die einzige
wirksame Grenze. Bearbeiten und Loeschen entfallen jetzt in der Zeile
eines SUPER_ADMIN, wenn die angemeldete Person selbst keiner ist;
Details bleibt in jeder Zeile. Die Sperre gegen Selbstloeschung bleibt
unveraendert. Gesamtbestand apps/web: 66 Dateien / 459 Tests gruen,
type-check Exit 0, lint 5/5 erfolgreich, apps/api unangetastet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 07:33:48 +02:00
parent 51bff7564f
commit 13b70dfbe8
2 changed files with 124 additions and 19 deletions
+28 -16
View File
@@ -204,6 +204,14 @@ export default function AdminUsersPage() {
);
}
// Spiegelt den Zielrollen-Riegel aus `apps/api/src/user/user.controller.ts`
// (update/remove, WINDOWS #29): eine Zeile ist gesperrt, wenn ihre Person
// SUPER_ADMIN ist und die angemeldete Person es nicht ist. Rein
// ergonomisch — die Serverpruefung bleibt die einzige wirksame Grenze
// (D-04, T-A1D-02).
const canManageRow = (user: User) =>
!(user.role === 'SUPER_ADMIN' && currentUser?.role !== 'SUPER_ADMIN');
const roleBadgeClass = (role: string) => {
switch (role) {
case 'SUPER_ADMIN':
@@ -305,22 +313,26 @@ export default function AdminUsersPage() {
>
{t('grants.detailsButton')}
</button>
<button
onClick={() => openEdit(user)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{tCommon('edit')}
</button>
<button
onClick={() => {
setDeleteConfirm(user.id);
setDeleteError(null);
}}
disabled={user.id === currentUser?.id}
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors disabled:opacity-30 disabled:cursor-not-allowed disabled:pointer-events-none"
>
{tCommon('delete')}
</button>
{canManageRow(user) && (
<button
onClick={() => openEdit(user)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{tCommon('edit')}
</button>
)}
{canManageRow(user) && (
<button
onClick={() => {
setDeleteConfirm(user.id);
setDeleteError(null);
}}
disabled={user.id === currentUser?.id}
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors disabled:opacity-30 disabled:cursor-not-allowed disabled:pointer-events-none"
>
{tCommon('delete')}
</button>
)}
</div>
</td>
</tr>