fix(quick-260921-bi2): a11y - Administrationsbereich (Gruppen, LDAP, Module, Mandanten, Benutzer)

useButtonType: alle Schaltflaechen ausserhalb der drei echten Formulare
(Mandanten/Benutzer/LDAP) erhalten type="button"; die drei tatsaechlichen
Absende-Buttons behalten type="submit" (Zahl bleibt 1/1/2, siehe <verify>).

noSvgWithoutTitle: Symbole neben sichtbarem Text (Zurueck-Pfeil, Navigations-
Icons in der Admin-Seitenleiste) werden dekorativ (aria-hidden="true"); das
Schloss-Symbol der LDAP-Standardzuordnung traegt jetzt einen eigenen Titel
(admin.ldap.fieldMapping.defaultIcon), weil es ohne begleitenden Text pro
Tabellenzeile die ganze Bedeutung allein traegt.

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 09:21:09 +02:00
parent e76f3b8d84
commit 4cff3167ed
8 changed files with 33 additions and 6 deletions
@@ -114,12 +114,14 @@ export function DeleteGroupDialog({ group, onClose, onDeleted }: DeleteGroupDial
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="rounded-md border border-border px-4 py-2 text-sm text-foreground hover:bg-muted transition-colors"
>
{tCommon('cancel')}
</button>
<button
type="button"
onClick={handleDelete}
disabled={deleting || loadingImpact || impactError}
className="rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground hover:opacity-90 transition-opacity disabled:opacity-50"
@@ -126,6 +126,7 @@ export default function AdminGroupsPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-foreground">{t('title')}</h1>
<button
type="button"
onClick={openCreate}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity"
>
@@ -146,6 +147,7 @@ export default function AdminGroupsPage() {
<h2 className="text-lg font-semibold text-foreground mb-2">{t('noGroups')}</h2>
<p className="text-sm text-muted-foreground mb-6">{t('noGroupsBody')}</p>
<button
type="button"
onClick={openCreate}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity"
>
@@ -205,6 +207,7 @@ export default function AdminGroupsPage() {
}`}
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
@@ -223,18 +226,21 @@ export default function AdminGroupsPage() {
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-2">
<button
type="button"
onClick={() => openEdit(group)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{tCommon('edit')}
</button>
<button
type="button"
onClick={() => setMembersGroup(group)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{t('membersButton')}
</button>
<button
type="button"
onClick={() => setDeleteTarget(group)}
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors"
>
@@ -20,6 +20,7 @@ export default function AdminLayout({
className="mb-4 inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
@@ -677,6 +677,7 @@ export default function AdminLdapPage() {
{t('fieldMapping.title')}
</h2>
<button
type="button"
onClick={() => setShowMappingForm(true)}
className="rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:opacity-90 transition-opacity"
>
@@ -725,6 +726,7 @@ export default function AdminLdapPage() {
strokeLinejoin="round"
className="text-muted-foreground"
>
<title>{t('fieldMapping.defaultIcon')}</title>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
@@ -733,6 +735,7 @@ export default function AdminLdapPage() {
<td className="px-4 py-3 text-right">
{!mapping.isDefault && (
<button
type="button"
onClick={() => handleRemoveMapping(mapping.id)}
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors"
>
@@ -1281,6 +1284,7 @@ export default function AdminLdapPage() {
{/* Save interval changes */}
<button
type="button"
onClick={handleSave}
disabled={saving}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity disabled:opacity-50"
@@ -1302,6 +1306,7 @@ export default function AdminLdapPage() {
{/* Manual sync button (D-14) */}
<button
type="button"
onClick={handleSync}
disabled={syncing}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity disabled:opacity-50"
@@ -197,6 +197,7 @@ export default function AdminModulesPage() {
{isActive ? t('activated') : t('deactivated')}
</span>
<button
type="button"
onClick={() => {
if (isActive) {
// Deactivation is unchanged -- direct toggle, no dialog.
@@ -148,6 +148,7 @@ export default function AdminTenantsPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-foreground">{t('title')}</h1>
<button
type="button"
onClick={openCreate}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity"
>
@@ -211,18 +212,21 @@ export default function AdminTenantsPage() {
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-2">
<button
type="button"
onClick={() => openEdit(tenant)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{tCommon('edit')}
</button>
<button
type="button"
onClick={() => handleToggleActive(tenant)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
{tenant.isActive ? tCommon('inactive') : tCommon('active')}
</button>
<button
type="button"
onClick={() => setDeleteConfirm(tenant.id)}
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors"
>
@@ -306,12 +310,14 @@ export default function AdminTenantsPage() {
</p>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={() => setDeleteConfirm(null)}
className="rounded-md border border-border px-4 py-2 text-sm text-foreground hover:bg-muted transition-colors"
>
{tCommon('cancel')}
</button>
<button
type="button"
onClick={() => handleDelete(deleteConfirm)}
className="rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground hover:opacity-90 transition-opacity"
>
@@ -229,6 +229,7 @@ export default function AdminUsersPage() {
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-foreground">{t('title')}</h1>
<button
type="button"
onClick={openCreate}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity"
>
@@ -308,6 +309,7 @@ export default function AdminUsersPage() {
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-2">
<button
type="button"
onClick={() => setDetailsUser(user)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
@@ -315,6 +317,7 @@ export default function AdminUsersPage() {
</button>
{canManageRow(user) && (
<button
type="button"
onClick={() => openEdit(user)}
className="rounded px-2 py-1 text-xs text-foreground hover:bg-muted transition-colors"
>
@@ -323,6 +326,7 @@ export default function AdminUsersPage() {
)}
{canManageRow(user) && (
<button
type="button"
onClick={() => {
setDeleteConfirm(user.id);
setDeleteError(null);
@@ -479,12 +483,14 @@ export default function AdminUsersPage() {
)}
<div className="flex justify-end gap-3">
<button
type="button"
onClick={() => setDeleteConfirm(null)}
className="rounded-md border border-border px-4 py-2 text-sm text-foreground hover:bg-muted transition-colors"
>
{tCommon('cancel')}
</button>
<button
type="button"
onClick={() => handleDelete(deleteConfirm)}
className="rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground hover:opacity-90 transition-opacity"
>
@@ -17,7 +17,7 @@ export function AdminSidebar() {
href: '/admin/users',
show: true,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
@@ -30,7 +30,7 @@ export function AdminSidebar() {
href: '/admin/tenants',
show: isSuperAdmin,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
@@ -41,7 +41,7 @@ export function AdminSidebar() {
href: '/admin/modules',
show: true,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
@@ -53,7 +53,7 @@ export function AdminSidebar() {
href: '/admin/ldap',
show: true,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5" />
<path d="M2 12l10 5 10-5" />
@@ -65,7 +65,7 @@ export function AdminSidebar() {
href: '/admin/smtp',
show: true,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect width="20" height="16" x="2" y="4" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
@@ -76,7 +76,7 @@ export function AdminSidebar() {
href: '/admin/groups',
show: true,
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="8" x2="21" y1="6" y2="6" />
<line x1="8" x2="21" y1="12" y2="12" />
<line x1="8" x2="21" y1="18" y2="18" />