E2 — Mitglieder

Kachel-Ansicht · Finale Spezifikation · Route: /members

screen: E2
journey: J7
variation: Kachel (V2)
version: 1.0
date: 2026-04-09

Die Mitgliederseite zeigt alle Haushaltsmitglieder als Kacheln. Der Planer kann Rollen ändern und Mitglieder entfernen über ein Kebab-Menü auf jeder Kachel. Eine Einladekachel ermöglicht das Generieren und Kopieren des Einlade-Links. Mitglieder sehen alle Kacheln nur lesend.

Backend-Lücken — vor Implementierung schließen

S1 — Standardansicht (Planer)
S1
Standardansicht — Planer sieht vollständige Kacheln
Alle Mitglieder als Kacheln, dahinter die Einladekachel. Kebab-Button erscheint on hover.
Desktop
Mitglieder
3 Mitglieder · Familie Raddatz
MR
Marcel R.
Planer
seit 02.04.2026
Du
SR
Sandra R.
Mitglied
seit 03.04.2026
LR
Lena R.
Mitglied
seit 05.04.2026
+
Mitglied einladen
Mobile
MR
Marcel R.
Planer
Du
SR
Sandra R.
Mitglied
LR
Lena R.
Mitglied
+
Einladen
📅
Planer
🍽
Rezepte
🛒
Einkauf
⚙️
Einstellungen
Notizen
  • Eigene Kachel (Du): grüner Kartenrahmen (border: var(--green-light)), "Du"-Badge statt Kebab
  • Kebab-Button (): immer im DOM, opacity:0 bis hover/focus, dann opacity:1. Auf Touch-Geräten immer sichtbar.
  • Avatar-Initialen: erste zwei Buchstaben des displayName. Planer = green-dark, Mitglied = blue
  • Kachel-Reihenfolge: eigene Kachel immer zuerst, dann joinedAt aufsteigend, Einladekachel immer zuletzt
  • Mobile: "+" Button in der Header-Zeile öffnet Einlade-Panel. Einladekachel bleibt zusätzlich im Grid.
S2 — Kebab-Menü offen
S2
Kebab-Menü geöffnet
Klick auf ⋯ öffnet Dropdown mit zwei Aktionen. Klick außerhalb schließt das Menü.
Desktop — Menü offen auf "Sandra R."
Mitglieder
3 Mitglieder · Familie Raddatz
MR
Marcel R.
Planer
seit 02.04.2026
Du
SR
Sandra R.
Mitglied
seit 03.04.2026
LR
Lena R.
Mitglied
seit 05.04.2026
+
Mitglied einladen
Notizen
  • Dropdown: position: absolute; top: 44px; right: 12px relativ zur Kachel
  • Zwei Einträge: "Rolle ändern" (neutrales Icon 🔄) und "Entfernen" (rot, Icon ✕)
  • Klick außerhalb des Dropdowns schließt diesen (click-away listener)
  • Nur ein Menü gleichzeitig offen. ESC schließt ebenfalls.
  • Mobile: Tap auf ⋯ öffnet Bottom Sheet mit denselben zwei Einträgen (44px min-height pro Eintrag)
S3 — Rolle ändern (inline auf der Kachel)
S3
Rolle ändern — Segmented Control erscheint
Wahl von "Rolle ändern" ersetzt das Rolle-Badge durch einen 2-Button-Schalter [Planer | Mitglied]. Aktive Rolle vorausgewählt. Bestätigung sofort mit PATCH-Request.
Desktop — Rolle-Control auf "Sandra R." aktiv
Mitglieder
3 Mitglieder · Familie Raddatz
MR
Marcel R.
Planer
seit 02.04.2026
Du
SR
Sandra R.
seit 03.04.2026
LR
Lena R.
Mitglied
seit 05.04.2026
+
Mitglied einladen
Notizen
  • Role-Control ersetzt das Badge in-place auf der Kachel. Kein Dialog, kein Page-Change.
  • Klick auf die inaktive Rolle → optimistisches Update → PATCH /v1/households/mine/members/{userId} { role }
  • Bei Erfolg: Role-Control durch neues Badge ersetzen
  • Bei Fehler: Rollback + Toast "Rolle konnte nicht geändert werden."
  • "Abbrechen" bringt ohne PATCH-Call das Badge zurück
  • Der Planer kann seinen eigenen Planer-Status nicht abgeben, solange er der einzige Planer ist
  • Kachel bekommt blauen Rahmen (border-color: #B5D4F4) als Editier-Indikator
S4 — Entfernen-Bestätigung (Dialog)
S4
Bestätigungsdialog "Mitglied entfernen"
Klick auf "Entfernen" im Dropdown öffnet einen modalen Dialog. Kein direktes Löschen ohne Bestätigung.
Desktop — Dialog über der Seite
Mitglieder
MR
Marcel R.
Planer
SR
Sandra R.
Mitglied
LR
Lena R.
Mitglied
+
Mitglied einladen
Mitglied entfernen?
Sandra R. wird aus dem Haushalt entfernt und verliert sofort den Zugang zu allen Plänen und Rezepten.
Mobile
MR
Marcel R.
Planer
SR
Sandra R.
Mitglied
Mitglied entfernen?
Sandra R. wird aus dem Haushalt entfernt.
Notizen
  • Dialog zeigt den displayName des Mitglieds explizit
  • Bestätigung → DELETE /v1/households/mine/members/{userId} → Kachel aus Grid entfernen
  • Planer kann sich nicht selbst entfernen (eigene Kachel hat kein Kebab-Menü)
  • Letzter verbleibender Planer kann nicht entfernt werden → Fehlermeldung im Dialog
  • Mobile: Dialog als Bottom Sheet (border-radius nur oben, kein max-width)
  • Hintergrund leicht gedimmt: rgba(28,28,24,.45), Klick außerhalb schließt nicht (explizite Bestätigung erforderlich)
S5 — Einladekachel: Einlade-Panel
S5
Einlade-Panel — nach Klick auf die Einladekachel
Kachel expandiert zum Panel unterhalb der Grid-Reihe. Zeigt generierten Link + Ablaufdatum + Regenerieren-Option.
Desktop
Mitglieder
3 Mitglieder · Familie Raddatz
MR
Marcel R.
Planer
seit 02.04.2026
Du
SR
Sandra R.
Mitglied
seit 03.04.2026
LR
Lena R.
Mitglied
seit 05.04.2026
+
Mitglied einladen
Einladelink teilen
Wer diesen Link öffnet, kann dem Haushalt als Mitglied beitreten.
Läuft ab: 12.04.2026
Notizen
  • Klick auf Einladekachel → POST /v1/households/mine/invites (falls kein aktiver Code vorhanden) oder GET /v1/households/mine/invites
  • Invite-Panel erscheint unterhalb der Grid-Reihe (kein Modal, kein Page-Change)
  • "Kopieren" → navigator.clipboard.writeText(shareUrl) → Button zeigt kurz "Kopiert ✓"
  • "Neuen Link generieren" → POST /v1/households/mine/invites → alten Code invalidieren → neuen Code anzeigen
  • Ablaufdatum expiresAt in gelbem Badge wenn ≤ 24h verbleibend
  • Nur Planer sehen den Einlade-CTA. Mitglied sieht keine Einladekachel.
S6 — Mitglied-Perspektive (read-only)
S6
Ansicht als Haushaltsmitglied (rolle = mitglied)
Mitglieder sehen die Kacheln ohne Kebab-Menü und ohne Einladekachel.
Desktop — Mitglied-Perspektive
Mitglieder
3 Mitglieder · Familie Raddatz
SR
Sandra R.
Mitglied
seit 03.04.2026
Du
MR
Marcel R.
Planer
seit 02.04.2026
LR
Lena R.
Mitglied
seit 05.04.2026
Notizen
  • Mitglied sieht keine Einladekachel und keine Kebab-Buttons auf anderen Kacheln
  • Eigene Kachel zeigt "Du"-Badge (grüner Rahmen), aber kein Kebab
  • Grid passt sich an: bei 3 Kacheln → grid-template-columns: repeat(3, 1fr) (kein leerer Slot für Einladen)
  • Server-seitige Prüfung: Aktionen (DELETE, PATCH) geben 403 für nicht-Planer zurück

Maschinen-lesbare Spezifikation

Diese Sektion enthält verbindliche Implementierungsregeln für den Coding-Agenten.

/* spec:rules — E2 Mitglieder Kachel
 *
 * LAYOUT
 *   grid: repeat(4, 1fr) gap 16px desktop; repeat(2, 1fr) gap 12px mobile
 *   card: bg white, border 1px solid --color-border, border-radius --radius-xl
 *   card padding: 24px 20px 20px desktop; 16px mobile
 *
 * AVATAR
 *   size: 56px desktop / 44px mobile; border-radius 50%
 *   initials: first two chars of displayName, uppercase
 *   planer color: --green-dark (#2E6E39)
 *   mitglied color: --blue (#185FA5)
 *
 * ROLE BADGE
 *   planer:  bg --green-tint, color --green-dark
 *   mitglied: bg --blue-tint,  color --blue-dark
 *   font-size 10px, font-weight 500, padding 2px 8px, border-radius --radius-full
 *
 * OWN CARD (benutzer.id === member.userId)
 *   border-color: --green-light
 *   show "Du" badge below join-date
 *   hide kebab button entirely
 *
 * KEBAB BUTTON
 *   position absolute, top 12px, right 12px
 *   opacity 0 by default; 1 on card:hover, card:focus-within, touch devices always 1
 *   opens dropdown: [Rolle ändern, divider, Entfernen(danger)]
 *   click-away closes; ESC closes
 *
 * ROLE CHANGE (S3)
 *   replaces badge in-place with segmented control [Planer | Mitglied]
 *   active button: bg --green-dark, color white
 *   inactive button: bg white, color --color-text-muted
 *   on select: PATCH /v1/households/mine/members/{userId} body { role }
 *   optimistic update; on error: rollback + toast
 *   Abbrechen link below control: reverts to badge without API call
 *   guard: planer cannot demote self if sole planer
 *
 * REMOVE CONFIRM (S4)
 *   modal dialog, backdrop rgba(28,28,24,.45), backdrop does NOT close on click
 *   shows member displayName in body text
 *   confirm → DELETE /v1/households/mine/members/{userId}
 *   on success: remove card from grid with fade-out
 *   mobile: bottom-sheet (border-radius top only)
 *
 * INVITE (S5)
 *   invite card always last in grid, only visible to planer
 *   click → POST /v1/households/mine/invites OR GET /v1/households/mine/invites
 *   panel below grid (not modal)
 *   copy: navigator.clipboard.writeText(shareUrl) → button text "Kopiert ✓" for 2s
 *   regenerate: POST new invite → invalidate old
 *   expiresAt badge yellow if ≤ 24h remaining
 *
 * MEMBER VIEW (S6)
 *   rolle === 'mitglied': hide all kebab buttons, hide invite card
 *   grid auto-adjusts columns (no empty slot)
 *
 * CARD ORDER
 *   1. own card (benutzer.id === userId)
 *   2. other members sorted by joinedAt ASC
 *   3. invite card (planer only)
 *
 * BACKEND GAPS (must exist before page ships)
 *   DELETE /v1/households/mine/members/{userId}
 *   PATCH  /v1/households/mine/members/{userId}  body: { role: "planer"|"mitglied" }
 *   GET    /v1/households/mine/invites
 */
    
PropertyValueNotes
Component: MemberCard
card-width1fr (grid)4-col desktop, 2-col mobile
card-min-height180pxdesktop; auto mobile
avatar-size56px / 44pxdesktop / mobile
avatar-radius50%full circle
kebab-target44×44pxWCAG 2.2 minimum touch target
dropdown-min-width160pxright-aligned to kebab
Role Control
control-height32pxsegmented, full card width
active-bg--green-darkselected role button
api-endpointPATCH /v1/households/mine/members/{userId}body: { role }
Remove Dialog
confirm-btn-bg--color-error (#DC4C3E)danger action
api-endpointDELETE /v1/households/mine/members/{userId}
backdroprgba(28,28,24,.45)click-outside does NOT close
Invite
api-createPOST /v1/households/mine/invitesreturns InviteResponse
api-listGET /v1/households/mine/invitesbackend gap
copy-feedback"Kopiert ✓" for 2000msthen revert to "Kopieren"