docs(15): UI design contract
This commit is contained in:
@@ -0,0 +1,225 @@
|
|||||||
|
---
|
||||||
|
phase: 15
|
||||||
|
slug: modul-berechtigungen-gruppen-user-grants
|
||||||
|
status: draft
|
||||||
|
shadcn_initialized: false
|
||||||
|
preset: none
|
||||||
|
created: 2026-08-04
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 15 — UI Design Contract
|
||||||
|
|
||||||
|
> Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design System
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Tool | none — kein `components.json`, keine shadcn-CLI im Repo (verifiziert: `find` liefert keinen Treffer) |
|
||||||
|
| Preset | not applicable |
|
||||||
|
| Component library | none — keine Radix-/Base-UI-Abhängigkeit im Projekt (`grep radix\|lucide\|cva\|clsx` in `apps/web/package.json` liefert nichts). Alle fünf bestehenden Admin-Seiten (`ldap`, `users`, `modules`, `tenants`, `smtp`) sind handgebaute Tailwind-Komponenten mit einem konsistenten, projekteigenen Vokabular (Tabellen, Modal-Dialoge, Toggle-Switches, Badge-Chips) |
|
||||||
|
| Icon library | none — Icons sind handgeschriebene Inline-`<svg>` im Lucide-Stil (`viewBox="0 0 24 24"`, `stroke="currentColor"`, `strokeWidth="2"`, `strokeLinecap="round"`, `strokeLinejoin="round"`, kein `fill`), siehe `apps/web/src/components/admin/admin-sidebar.tsx` |
|
||||||
|
| Font | Inter via `--font-sans` (`apps/web/src/app/globals.css:33`), system-ui-Fallback |
|
||||||
|
|
||||||
|
**Shadcn-Gate-Entscheidung:** Kein `components.json` gefunden, Stack ist Next.js — der Gate würde normalerweise fragen, ob shadcn initialisiert werden soll. Diese Frage ist hier bereits beantwortet: der `<hard_constraints>`-Auftrag verlangt explizit "Reuse existing components and tokens; do not introduce a new component library or a second styling approach", und 14 vorherige Phasen haben ein durchgängiges, funktionierendes handgebautes System etabliert (5 Admin-Seiten, Marketplace, Modul-Shells). shadcn jetzt einzuführen würde einen zweiten, inkonsistenten Styling-Ansatz neben den bestehenden ~40 Seiten schaffen. **Tool bleibt `none`, Registry-Safety-Gate ist damit nicht anwendbar.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spacing Scale
|
||||||
|
|
||||||
|
Declared values (must be multiples of 4), identisch mit dem bereits im Projekt etablierten Raster (verifiziert in allen fünf Admin-Seiten):
|
||||||
|
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| xs | 4px | Icon-Text-Abstand in Badges/Chips (`gap-1`), Checkbox-zu-Label-Feinabstand |
|
||||||
|
| sm | 8px | Kompakte Element-Abstände (`gap-2`), Badge-Innenpolsterung (`px-2 py-0.5`) |
|
||||||
|
| md | 16px | Standard-Formularabstand (`space-y-4`), Tabellenzellen-Polsterung (`px-4 py-3`) |
|
||||||
|
| lg | 24px | Card-/Section-Innenpolsterung (`p-6`), Abstand zwischen Seitenabschnitten (`space-y-6`/`space-y-8`) |
|
||||||
|
| xl | 32px | reserviert, in dieser Phase nicht benötigt |
|
||||||
|
| 2xl | 48px | reserviert, in dieser Phase nicht benötigt |
|
||||||
|
| 3xl | 64px | reserviert, in dieser Phase nicht benötigt |
|
||||||
|
|
||||||
|
**Exceptions:**
|
||||||
|
- Icon-only Aktionsbuttons in Tabellenzeilen (Standardgruppen-Stern-Toggle, Mitglied-Entfernen-Button) übernehmen das bestehende Projektmaß `px-2 py-1` (kein 44px-Touch-Target) — Admin-Oberflächen sind Desktop-Maus-Bedienung, identisch zu den bereits ausgelieferten "Bearbeiten"/"Löschen"-Buttons in `admin/users/page.tsx`. Kein neuer Präzedenzfall, reine Fortführung.
|
||||||
|
- Matrix-Checkboxen (Permission-Matrix-Seite) nutzen die native `<input type="checkbox">`-Größe ohne zusätzliches Padding, identisch zum bestehenden `groupFilterDns`-Checkbox-Muster in `admin/ldap/page.tsx`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
Übernommene, bereits im Projekt etablierte Werte (nicht neu erfunden — verifiziert aus allen fünf Admin-Seiten). Vier Größen und vier Gewichte sind bereits Teil des bestehenden Systems; diese Phase führt **keine neue Größe und kein neues Gewicht ein**, sondern verwendet ausschließlich die vorhandenen vier Kombinationen:
|
||||||
|
|
||||||
|
| Role | Size | Weight | Line Height |
|
||||||
|
|------|------|--------|-------------|
|
||||||
|
| Label / Badge | 12px (`text-xs`) | 500 medium | 1.4 |
|
||||||
|
| Body / Table / Form | 14px (`text-sm`) | 400 regular (Fließtext, Tabellenzellen) | 1.5 |
|
||||||
|
| Section Heading | 18px (`text-lg`) | 600 semibold | 1.3 |
|
||||||
|
| Page Heading | 24px (`text-2xl`) | 700 bold | 1.2 |
|
||||||
|
|
||||||
|
**Exception (dokumentierte Abweichung vom UI-SPEC-Standardvokabular "2 Gewichte max"):** Formular-Labels, Tabellen-Kopfzeilen und Buttons nutzen zusätzlich `font-medium` (500) auf 14px-Basis — z. B. `<label className="text-sm font-medium text-foreground">`. Das ist kein fünftes Gewicht, sondern dieselbe 500er-Stufe wie bei Labels/Badges, nur auf 14px statt 12px angewendet. Diese vier Größe-Gewicht-Paare sind das etablierte Tessera-Admin-Vokabular seit Phase 2 — der Planner/Executor übernimmt sie unverändert, erfindet keine neue Kombination.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color
|
||||||
|
|
||||||
|
| Role | Value | Usage |
|
||||||
|
|------|-------|-------|
|
||||||
|
| Dominant (60%) | `--background` / `--card` (OKLCH, hell `0.99 0 0` / `1 0 0`, dunkel `0.23 0.01 260` / `0.27 0.01 260`) | Seitenhintergrund, Card-/Section-Flächen |
|
||||||
|
| Secondary (30%) | `--secondary` / `--muted` / `--sidebar` (OKLCH `0.96 0 0` hell, `0.30 0.01 260` dunkel) | Tabellenkopf (`bg-muted/50`), Admin-Sidebar, sekundäre Buttons (`border border-border`) |
|
||||||
|
| Accent (10%) | `--primary` (OKLCH `0.91 0.19 102`, gelb) | **ausschließlich**: primäre CTA-Buttons ("Gruppe erstellen", "Sofort freigeben", "Speichern"), aktive Toggle-Switch-Füllung (Modul-Aktivierung, Matrix-Häkchen-Fokusring), aktiver Zustand im Admin-Sidebar-Nav-Item (`sidebar-accent-foreground`), Fokus-Ring (`ring-ring`) |
|
||||||
|
| Destructive | `--destructive` (OKLCH `0.55 0.2 27`, rot) | **ausschließlich**: "Löschen"/"Entfernen"/"Freigabe entziehen"-Buttons, Bestätigungs-Button in Lösch-Dialogen, Fehlermeldungstext |
|
||||||
|
|
||||||
|
Accent reserved for: Primär-Buttons (Gruppe erstellen, Speichern, Sofort freigeben), aktiver Toggle-Zustand, aktives Sidebar-Nav-Item, Fokus-Ring. **Niemals** für Badges, Tabellenzeilen-Hover oder informative Hinweise — dafür gilt die bestehende informelle Badge-Palette unten.
|
||||||
|
|
||||||
|
**Bestehende informelle Badge-Palette (kein Token, aber projektweit etabliertes Muster — jede Instanz trägt Light- und Dark-Variante):**
|
||||||
|
|
||||||
|
| Badge-Zweck | Klassen | Bereits verwendet in |
|
||||||
|
|---|---|---|
|
||||||
|
| Positiv/Aktiv | `bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400` | Modul-Status "Aktiviert", User-Status "Aktiv" |
|
||||||
|
| Neutral/Inaktiv | `bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-500` bzw. `bg-muted text-muted-foreground` | User-Status "Inaktiv", Marketplace "Verfügbar" |
|
||||||
|
| Info/System | `bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400` | Rolle ADMIN, "System"-Modul-Badge |
|
||||||
|
| Kritisch (Rolle) | `bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400` | Rolle SUPER_ADMIN |
|
||||||
|
| **NEU in dieser Phase — Eingeschränkt** | `bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400` | Marketplace-Badge "Kein Zugriff" (D-08). Bewusst **nicht** `destructive` (rot) — es ist kein Fehlerzustand, sondern ein Informationszustand, konsistent mit der bestehenden Palette-Familie um eine fünfte, bisher ungenutzte Farbe erweitert (amber ist in Tailwinds Standardpalette bereits verfügbar, kein neues Token nötig) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Copywriting Contract
|
||||||
|
|
||||||
|
| Element | Copy |
|
||||||
|
|---------|------|
|
||||||
|
| Primary CTA — Gruppenverwaltung | "Gruppe erstellen" (`admin.groups.create`) |
|
||||||
|
| Primary CTA — Matrix-Zugriff | "Freigaben-Matrix" (Link/Button auf `/admin/modules`, `adminModules.grantsLink`) |
|
||||||
|
| Primary CTA — Modul-Aktivierung mit Grant | "Sofort freigeben" (`adminModules.activationDialog.grantNow`) |
|
||||||
|
| Sekundär-CTA — Modul-Aktivierung ohne Grant | "Später konfigurieren" (`adminModules.activationDialog.configureLater`) |
|
||||||
|
| Empty state heading — Gruppenliste | "Keine Gruppen vorhanden" (`admin.groups.noGroups`) |
|
||||||
|
| Empty state body — Gruppenliste | "Lege deine erste Gruppe an, um Modulzugriff gezielt zu vergeben." (`admin.groups.noGroupsBody`) |
|
||||||
|
| Empty state — Matrix ohne aktive Module | "Es sind noch keine Module für diesen Mandanten aktiviert. Aktiviere zuerst ein Modul unter Module." (`adminModules.grants.emptyModules`, mit Link zu `/admin/modules`) |
|
||||||
|
| Empty state — User-Detail ohne Gruppenmitgliedschaft | "Dieser Benutzer ist keiner Gruppe zugeordnet." (`admin.users.grants.noGroups`) |
|
||||||
|
| Error state — Grant-Speicherfehler | "Freigabe konnte nicht gespeichert werden. Bitte erneut versuchen." + Statuscode-Anzeige wie im bestehenden `adminModules`-Fehlermuster (`error`-Div mit `border-destructive/50 bg-destructive/10`) |
|
||||||
|
| 403-Seite Titel (D-07) | "Kein Zugriff auf dieses Modul" (`modules.accessDenied.title`) |
|
||||||
|
| 403-Seite Body (D-07) | "Du hast für dieses Modul keine Freigabe. Wende dich an deinen Administrator." (`modules.accessDenied.body`) — Formulierung ist durch D-07 wörtlich vorgegeben, keine Umformulierung |
|
||||||
|
| 403-Seite Rücklink | "Zur Startseite" → `/` (Dashboard), nicht "Zurück zur Kategorie" — der Benutzer hat für dieses Modul keinen Zugriff, ein Rücksprung in die Modul-Kategorie ist kein sinnvoller nächster Schritt (`modules.accessDenied.backToDashboard`) |
|
||||||
|
| Marketplace-Badge "Kein Zugriff" (D-08) | "Kein Zugriff" (`marketplace.statusNoAccess`) |
|
||||||
|
| Marketplace-Klick-Feedback bei gesperrter Karte | Toast: "Kein Zugriff auf dieses Modul — wende dich an deinen Administrator." (identischer Wortlaut wie 403-Seite, `marketplace.toastNoAccess`) — nutzt die bestehende `Toast.tsx`-Komponente |
|
||||||
|
| Destructive confirmation — Gruppe löschen (D-17, PERM-01) | Titel: "Gruppe löschen"; Body: "Diese Gruppe hat **{memberCount} Mitglieder** und **{grantCount} Modul-Freigaben**. Betroffene Benutzer verlieren den Zugriff, sofern sie ihn nicht anderweitig haben. Diese Aktion kann nicht rückgängig gemacht werden." (`admin.groups.deleteConfirm.title` / `.body`, Zahlen als next-intl-Interpolationsparameter, keine generische Warnung ohne Zahlen) |
|
||||||
|
| Destructive confirmation — Freigabe entziehen (Matrix/User-Detail) | Kein Dialog nötig — Häkchen-Toggle ist sofort wirksam und sofort umkehrbar (kein Datenverlust, reine Klick-Umkehrung), analog zum bestehenden Modul-Aktivierungs-Toggle-Verhalten. Kein Bestätigungsdialog für einzelne Grant-Toggles. |
|
||||||
|
| Destructive confirmation — Mitglied aus Gruppe entfernen | Kein Dialog — sofortiger Klick-Entfernen-Button analog zum bestehenden `userExcludeList`-Chip-Entfernen-Muster (kein irreversibler Datenverlust, Mitglied kann sofort wieder hinzugefügt werden) |
|
||||||
|
| AD-Bindung — Bindungs-Auswahl-Hinweis | "Wähle eine AD-Gruppe aus der Liste, um die Mitgliedschaft automatisch zu synchronisieren." (`admin.groups.ldapBind.hint`) |
|
||||||
|
| AD-Bindung — bereits gebunden | "Gebunden an: {ldapDn}" mit "Bindung entfernen"-Link (`admin.groups.ldapBind.bound` / `.unbind`) |
|
||||||
|
| Standardgruppen-Markierung | Icon-Button-Tooltip/`aria-label`: "Als Standardgruppe festlegen" bzw. "Standardgruppe" (wenn bereits gesetzt) — `admin.groups.setDefault` / `admin.groups.isDefault` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Surface Contracts
|
||||||
|
|
||||||
|
### 1. `/admin/groups` — Gruppenverwaltung (PERM-01, D-14, D-17)
|
||||||
|
|
||||||
|
Sechster Eintrag im `AdminSidebar` (`apps/web/src/components/admin/admin-sidebar.tsx`), zwischen Module und LDAP oder ans Ende — Reihenfolge liegt bei Executor, Icon muss sich vom bestehenden `admin.users`-Personen-Icon unterscheiden (Vorschlag: ein Roster-/Listen-Icon, 24×24, `stroke-width 2`, kein Fill — kein bestehendes Icon im Projekt duplizieren).
|
||||||
|
|
||||||
|
Seitenaufbau identisch zu `admin/users/page.tsx`:
|
||||||
|
- `h1` "Gruppen" (24px/700) + primärer Button "Gruppe erstellen" rechtsbündig im Header (identisches Markup wie `openCreate`-Button in `AdminUsersPage`).
|
||||||
|
- Tabelle (`rounded-md border border-border`, `thead bg-muted/50`, `tbody divide-y divide-border`) mit Spalten: Name, AD-Bindung (Badge "AD-gebunden" info-blau wenn `ldapDn` gesetzt, sonst Badge "Manuell" neutral-grau), Standardgruppe (Stern-Icon-Toggle-Button, gefüllt wenn `isDefault`), Mitglieder (Zahl, `{count}`), Aktionen (Bearbeiten · Mitglieder · Löschen als drei Text-Buttons im bestehenden `px-2 py-1 text-xs`-Stil).
|
||||||
|
- Create/Rename-Modal: identisches Overlay-Muster (`fixed inset-0 bg-black/50` → `max-w-md rounded-lg border bg-card p-6 shadow-lg`) wie das User-Create-Modal. Feld "Name" (Pflicht), darunter Abschnitt "AD-Bindung (optional)": Suchfeld + Ergebnisliste im 1:1-Muster der bestehenden `groupFilter.discover`-Sektion aus `admin/ldap/page.tsx` (Zeilen 705–746), aber mit **Radio**-Auswahl statt Checkbox-Mehrfachauswahl (genau eine AD-Gruppe pro Tessera-Gruppe, D-05).
|
||||||
|
- Mitglieder-Modal (separater Dialog, Button "Mitglieder" aus der Tabellenzeile): zwei Abschnitte — (a) aktuelle Mitglieder als Chip-Liste mit Herkunfts-Badge (`MANUAL`/`LDAP`) und Entfernen-Button, identisches Chip-Markup wie `userExcludeList` in `admin/ldap/page.tsx` (Zeilen 957–974); LDAP-Mitgliedschaften zeigen keinen Entfernen-Button (D-19: nur der Sync entfernt sie) — stattdessen ein deaktivierter Zustand mit Tooltip "Wird über AD-Sync verwaltet". (b) Suchfeld + Checkbox-Liste bestehender Tenant-Benutzer zum manuellen Hinzufügen, identisches Markup wie die `userSearch`-Sektion (Zeilen 841–877), Ziel ist hier aber `GroupMembership`, nicht LDAP-Import.
|
||||||
|
- Lösch-Dialog: identisches Overlay-Muster wie `deleteConfirm` in `AdminUsersPage`, aber mit den unter Copywriting Contract festgelegten konkreten Zahlen (D-17) statt der generischen Ein-Satz-Warnung.
|
||||||
|
|
||||||
|
### 2. Permission-Matrix — Module × Gruppen (PERM-03, D-15)
|
||||||
|
|
||||||
|
Erreichbar über einen Button "Freigaben-Matrix" im Header von `/admin/modules` (kein siebter Sidebar-Eintrag — bleibt Unterseite von Modules, analog zu `dkv-fleet/settings`). Route: `/admin/modules/grants`.
|
||||||
|
|
||||||
|
- Suchfeld oben (filtert Modul- **und** Gruppennamen client-seitig), identisches Eingabefeld-Markup wie `discoverSearch`.
|
||||||
|
- Tabelle mit `sticky` erster Spalte (Modulname, `sticky left-0 bg-card z-10`) und `sticky` Kopfzeile (Gruppennamen, `sticky top-0 bg-card z-10`) in einem `overflow-x-auto overflow-y-auto max-h-[70vh] rounded-md border border-border`-Container — löst die Claude's-Discretion-Frage "Scrollverhalten bei vielen Modulen/Gruppen" aus RESEARCH.md.
|
||||||
|
- Zeilen nach Modul-Kategorie gruppiert: volle-Breite-Zwischenzeile `bg-muted/50` mit Kategorienamen (identisch zum bestehenden Kategorie-Badge-Text aus `AdminModulesPage`), darunter die Modulzeilen dieser Kategorie.
|
||||||
|
- Jede Zelle: natives `<input type="checkbox">`, sofortiges Toggle bei Klick (optimistisches UI, PATCH im Hintergrund — identisches Verhalten zum bestehenden Modul-Aktivierungs-Switch in `AdminModulesPage.toggleModule`), kleiner Inline-Spinner nur in der aktiven Zelle während des Requests (kein Vollseiten-Ladezustand).
|
||||||
|
- Fußnote unter der Tabelle (12px, `text-muted-foreground`): "ADMIN und SUPER_ADMIN haben immer Zugriff auf alle aktiven Module — diese Matrix betrifft nur die Rolle USER." (D-03 transparent machen, verhindert Verwirrung, warum Admin-Accounts hier nicht auftauchen).
|
||||||
|
- Leerer Zustand (kein aktives Modul im Mandanten): siehe Copywriting Contract "Empty state — Matrix ohne aktive Module".
|
||||||
|
|
||||||
|
### 3. User-Detail-Grants in `/admin/users` (PERM-03, D-16)
|
||||||
|
|
||||||
|
`AdminUsersPage` bekommt pro Zeile einen vierten Aktions-Button "Details" (neben Bearbeiten/Löschen, gleicher `px-2 py-1 text-xs`-Stil), der ein breiteres Modal öffnet (`max-w-2xl` statt `max-w-md` — mehr Inhalt als das einfache Edit-Formular, aber weiterhin das etablierte Overlay-Muster, kein neues Slide-over-Primitiv).
|
||||||
|
|
||||||
|
Modal-Inhalt, Titel "{username} — Zugriff":
|
||||||
|
1. Abschnitt "Gruppenmitgliedschaften" — read-only Chip-Liste (identisches Chip-Markup wie `userExcludeList`, aber ohne Entfernen-Button — Bearbeitung geschieht ausschließlich unter `/admin/groups`, keine zweite CRUD-Oberfläche für dieselbe Beziehung). Leerzustand siehe Copywriting Contract.
|
||||||
|
2. Abschnitt "Modul-Zugriff" — Tabelle mit Spalten: Modul | Über Gruppe(n) (Chip-Liste der Gruppennamen, die dieses Modul gewähren, oder "–") | Direkt (Checkbox-Toggle für einen direkten `ModuleGrant`, identisches Sofort-Toggle-Verhalten wie die Matrix). Nur mandantenweit aktive Module werden gelistet — inaktive Module sind hier nicht wählbar, da ein Grant ohne Aktivierung wirkungslos ist (D-02).
|
||||||
|
|
||||||
|
### 4. 403-Seite für gesperrtes Modul (PERM-04, D-07)
|
||||||
|
|
||||||
|
`apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx` wird zur `async` Server Component (Umbau lt. RESEARCH.md Pattern 5); der bisherige Client-Inhalt wandert unverändert in eine neue `module-shell.tsx`-Client-Component.
|
||||||
|
|
||||||
|
Visuelles Muster identisch zum bestehenden "not found"-Zustand derselben Datei (Zeilen 30–65: zentrierter Block, `rounded-lg bg-muted p-4`-Icon-Container, `h2` 18px/600, `p` 14px muted, Link darunter) — **nur** Icon und Copy ändern sich:
|
||||||
|
- Icon: Schloss-Symbol (`lock`, nicht das bestehende Kreis-mit-Schrägstrich-"nicht gefunden"-Icon — visuelle Unterscheidung zwischen "existiert nicht" und "gesperrt" ist bewusst, verhindert Verwechslung der beiden Zustände).
|
||||||
|
- Titel/Body: siehe Copywriting Contract, wörtlich D-07.
|
||||||
|
- Kein `notFound()`, kein Redirect (D-07 explizit) — die Seite rendert das 403-Markup direkt als Server-Response.
|
||||||
|
- Rücklink führt zur Startseite (`/`), nicht zur Modul-Kategorie (siehe Copywriting Contract Begründung).
|
||||||
|
|
||||||
|
### 5. Marketplace-Katalog — "Kein Zugriff"-Badge (PERM-04, D-08)
|
||||||
|
|
||||||
|
`MarketplaceCard.tsx` bekommt einen dritten Status neben `isActive`/`!isActive`: `hasGrant` (nur relevant, wenn `isActive === true` und Rolle `USER`). Wenn `isActive && !hasGrant`:
|
||||||
|
- Zusätzliches Badge "Kein Zugriff" (amber, siehe Color-Tabelle) direkt neben dem bestehenden Status-Badge in derselben Badge-Reihe (`flex items-center gap-2`).
|
||||||
|
- Karte bekommt `opacity-60 cursor-not-allowed` auf dem äußeren `div` (kein `hover:shadow-md`/`hover:border-primary/30` in diesem Zustand — visuelles Feedback, dass die Karte nicht interaktiv ist).
|
||||||
|
- Klick auf die Karte navigiert **nicht** zum Moduldetail, sondern löst den Toast "Kein Zugriff …" aus (bestehende `Toast.tsx`-Komponente aus dem Marketplace-Ordner wiederverwenden, kein neues Toast-System).
|
||||||
|
- ADMIN/SUPER_ADMIN sehen dieses Badge nie (Bypass, D-03) — für sie ist jede aktivierte Karte normal klickbar.
|
||||||
|
|
||||||
|
### 6. Modul-Aktivierungs-Dialog in `/admin/modules` (PERM-03, D-10)
|
||||||
|
|
||||||
|
`AdminModulesPage.toggleModule` verzweigt: Deaktivierung bleibt unverändert (bestehendes `ActivationDialog.tsx`-Warnmuster, falls vorhanden/nachgezogen). **Aktivierung** öffnet neu ein Dialog im identischen Overlay-Stil wie `ActivationDialog.tsx` (`fixed inset-0 bg-black/50` → `max-w-sm rounded-lg border bg-card p-6 shadow-lg`), aber mit drei Aktionen statt zwei:
|
||||||
|
|
||||||
|
- Titel: "Modul aktivieren" + Modulname interpoliert.
|
||||||
|
- Body: Frage, ob sofort für die Standardgruppe freigegeben werden soll (siehe D-10-Wortlaut in `15-CONTEXT.md`).
|
||||||
|
- Buttons, von links nach rechts: "Abbrechen" (Border-Button, bricht komplett ab, Modul bleibt inaktiv) — "Später konfigurieren" (Border-Button, aktiviert das Modul OHNE Grant, Admin konfiguriert Freigaben separat über Matrix/Gruppen) — "Sofort freigeben" (Primary-Button `bg-primary`, aktiviert das Modul UND erzeugt einen `ModuleGrant` für die als Standard markierte Gruppe des Mandanten).
|
||||||
|
- Falls der Mandant keine als Standard markierte Gruppe besitzt (D-13: Markierung kann abgeschaltet sein): "Sofort freigeben" ist `disabled` mit Hinweistext darunter ("Keine Standardgruppe markiert — lege zuerst eine unter Gruppen fest oder konfiguriere die Freigabe manuell.").
|
||||||
|
|
||||||
|
### 7. Sidebar (`apps/web/src/components/layout/sidebar.tsx`)
|
||||||
|
|
||||||
|
**Explizit keine visuelle Änderung.** Die Komponente fetcht weiterhin `GET /modules/active` unverändert und rendert exakt dasselbe Markup — der Endpoint liefert nach dieser Phase serverseitig weniger Module für nicht-freigegebene Benutzer, das ist reine Datenänderung, keine UI-Änderung. Kein neuer Sidebar-Zustand ("gesperrtes Modul ausgegraut anzeigen" o. ä.) — nicht freigegebene Module fehlen schlicht in der Liste, identisch zum bestehenden Verhalten für nicht-aktivierte Module.
|
||||||
|
|
||||||
|
### Dashboard-Widgets (PERM-07, D-22)
|
||||||
|
|
||||||
|
Ebenfalls keine neue UI-Komponente: `DashboardService.getWidgets()` filtert serverseitig, das Frontend rendert nur die gefilterte Liste — identisch zum bestehenden Grid-Rendering-Pfad. Kein Platzhalter/"gesperrtes Widget"-Zustand, das Widget verschwindet ersatzlos (D-22 verlangt kein Hinweis-Kachel).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Considerations
|
||||||
|
|
||||||
|
Applicable state considerations resolved: 9 covered, 3 backstop, 0 unresolved
|
||||||
|
|
||||||
|
| Category | Element(s) | Status | Resolution / Reason |
|
||||||
|
|----------|------------|--------|---------------------|
|
||||||
|
| empty | Gruppenliste (`/admin/groups`) | ✅ covered | Leerer Zustand rendert die dokumentierte "Keine Gruppen vorhanden"-Copy mit CTA, identisches Muster zu `AdminUsersPage`'s `noUsers`-Zustand |
|
||||||
|
| empty | Permission-Matrix ohne aktive Module | ✅ covered | Dokumentierte Empty-State-Copy mit Link zu `/admin/modules` |
|
||||||
|
| empty | User-Detail ohne Gruppenmitgliedschaft | ✅ covered | Dokumentierte Empty-State-Copy, Chip-Liste einfach leer |
|
||||||
|
| empty | User-Detail-Modul-Zugriff-Tabelle, wenn Mandant keine aktiven Module hat | 🧪 backstop | Kein explizit dokumentierter Text — Tabelle rendert 0 Zeilen; Verifikation prüft, dass kein leerer `<table>`-Rahmen ohne jeden Hinweis erscheint |
|
||||||
|
| loading | Matrix-Zellen-Toggle, Gruppen-Modal-Speichern | ✅ covered | Identisches Inline-Spinner-Muster wie bestehender `isToggling`-Zustand in `AdminModulesPage` — kein neues Ladeverhalten |
|
||||||
|
| error | Grant-Speicherfehler | ✅ covered | Dokumentierte Fehlermeldung im bestehenden `error`-Div-Muster |
|
||||||
|
| error | Gruppe löschen schlägt fehl (z. B. Netzwerkfehler) | 🧪 backstop | Kein bestehendes Netzwerkfehler-Muster für Lösch-Dialoge im Projekt (aktuelle Lösch-Dialoge scheitern "silently" laut Code-Kommentar) — Planner muss entscheiden, ob dieser Präzedenzfall (stiller Fail) hier ebenfalls gilt oder eine sichtbare Fehlermeldung nötig ist, da PERM-01 konkrete Zahlen verspricht, die bei Fehlschlag nicht veralten dürfen |
|
||||||
|
| populated | Matrix mit vielen Modulen/Gruppen | ✅ covered | Sticky Header + sticky erste Spalte + Kategorie-Gruppierung + Suchfeld, siehe Surface Contract 2 |
|
||||||
|
| overflow | Lange Gruppennamen in der Matrix-Kopfzeile | 🧪 backstop | Kein expliziter Truncate-/Tooltip-Mechanismus dokumentiert — Planner sollte `truncate` + `title`-Attribut auf den Spaltenkopf anwenden, analog zum bestehenden `truncate`-Einsatz in `MarketplaceCard.tsx` (`h3.truncate`) |
|
||||||
|
| zero-one-many | AD-Bindung: Gruppe ohne/mit gebundener AD-Gruppe | ✅ covered | Zwei explizite Zustände dokumentiert (ungebunden zeigt Auswahl-UI, gebunden zeigt Chip + Entfernen-Link) |
|
||||||
|
| zero-one-many | Standardgruppe: keine/eine markierte Gruppe pro Mandant | ✅ covered | D-13 erzwingt DB-seitig genau 0 oder 1 — UI zeigt Stern-Toggle je Zeile, "Sofort freigeben" wird `disabled`, wenn keine Standardgruppe existiert (Surface Contract 6) |
|
||||||
|
| long-text | Modulnamen in der Matrix-ersten-Spalte | ✅ covered | `sticky left-0`-Spalte erhält feste Mindestbreite + `truncate` mit `title`-Attribut, identisch zur Overflow-Behandlung in Zeile darüber |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registry Safety
|
||||||
|
|
||||||
|
| Registry | Blocks Used | Safety Gate |
|
||||||
|
|----------|-------------|--------------|
|
||||||
|
| shadcn official | keine — Tool ist `none` | not required |
|
||||||
|
| Drittanbieter | keine | not required |
|
||||||
|
|
||||||
|
Kein Registry-Zugriff in dieser Phase — alle Komponenten sind handgebaut nach den in "Surface Contracts" referenzierten, bereits im Repo vorhandenen Mustern.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checker Sign-Off
|
||||||
|
|
||||||
|
- [ ] Dimension 1 Copywriting: PASS
|
||||||
|
- [ ] Dimension 2 Visuals: PASS
|
||||||
|
- [ ] Dimension 3 Color: PASS
|
||||||
|
- [ ] Dimension 4 Typography: PASS
|
||||||
|
- [ ] Dimension 5 Spacing: PASS
|
||||||
|
- [ ] Dimension 6 Registry Safety: PASS
|
||||||
|
|
||||||
|
**Approval:** pending
|
||||||
Reference in New Issue
Block a user