- Marked `Editier-Formulare_Dialog-vs-Fullscreen_v1.md` as APPROVED with finalized mapping for all edit screens. - Created `Empty-States_Spezifikation_v1.md` to outline design, behavior, and implementation plan for empty states across 10 screens. - Logged session outcomes in `2026-04-03_UIUX_B1_B4_Editier-Formulare_Empty-States_Curator_Log.md`. - Updated `UIUX_Roadmap.md` to reflect Sprint B completion and tasks for Sprint C-1. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
285 lines
15 KiB
Markdown
285 lines
15 KiB
Markdown
---
|
|
type: Frontend Guideline
|
|
status: APPROVED
|
|
owner: đïž UI/UX Designer
|
|
last_update: 2026-04-03
|
|
related:
|
|
- docs/06_Frontend/Guidelines/Editier-Formulare_Dialog-vs-Fullscreen_v1.md
|
|
- docs/04_Agents/Roadmaps/UIUX_Roadmap.md
|
|
- docs/04_Agents/Roadmaps/Frontend_Roadmap.md
|
|
---
|
|
|
|
# Empty States â Design-Spezifikation & Richtlinie
|
|
|
|
> **Ziel:** Konsistente, hilfreiche Leer-ZustĂ€nde fĂŒr alle Listenansichten im Meldestelle-Desktop.
|
|
> **Leitsatz:** Ein leerer Zustand ist kein Fehler â er ist eine Einladung zur ersten Aktion.
|
|
|
|
---
|
|
|
|
## 1. Warum Empty States wichtig sind
|
|
|
|
- **Erster Start / Onboarding:** Neue Nutzer sehen leere Listen â ohne Hinweis wirkt die App kaputt.
|
|
- **Nach Filterung:** Kein Treffer bei Suche/Filter muss klar kommuniziert werden (â Ladefehler).
|
|
- **Nach Löschung:** Letzte EntitĂ€t gelöscht â leere Liste braucht Orientierung.
|
|
- **Vertrauen:** Klare Kommunikation verhindert Unsicherheit ("Ist das ein Bug?").
|
|
|
|
---
|
|
|
|
## 2. Empty-State-Typen
|
|
|
|
| Typ | Auslöser | PrimÀre Aktion |
|
|
|------------------|-------------------------------------------------|-----------------------------|
|
|
| **`EMPTY_LIST`** | Liste ist leer (noch keine Daten angelegt) | CTA: Ersten Eintrag anlegen |
|
|
| **`NO_RESULTS`** | Suche/Filter liefert keine Treffer | CTA: Filter zurĂŒcksetzen |
|
|
| **`ERROR`** | Ladefehler (Netzwerk, Backend nicht erreichbar) | CTA: Erneut versuchen |
|
|
| **`LOADING`** | Daten werden geladen (Skeleton/Spinner) | Kein CTA |
|
|
|
|
> `LOADING` wird durch `MsLoadingIndicator` abgedeckt â kein separater Empty State nötig.
|
|
|
|
---
|
|
|
|
## 3. Visuelle Anatomie â `MsEmptyState`
|
|
|
|
```
|
|
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
â â
|
|
â [ Icon / 48dp ] â
|
|
â â
|
|
â Titel (MaterialTheme.titleMedium) â
|
|
â â
|
|
â Beschreibung (MaterialTheme.bodyMedium, zentriert, â
|
|
â max. 2 Zeilen, gedimmt: onSurface 60%) â
|
|
â â
|
|
â [ PrimĂ€rer CTA-Button ] (optional) â
|
|
â â
|
|
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
```
|
|
|
|
### MaĂe & AbstĂ€nde
|
|
|
|
| Element | Wert |
|
|
|-----------------------------|---------------------------------------------------|
|
|
| Icon-GröĂe | 48 dp |
|
|
| Icon-Farbe | `MaterialTheme.colorScheme.onSurface` @ 38% Alpha |
|
|
| Abstand IconâTitel | 16 dp |
|
|
| Abstand TitelâBeschreibung | 8 dp |
|
|
| Abstand BeschreibungâButton | 24 dp |
|
|
| Max. Breite Textblock | 360 dp (zentriert) |
|
|
| Padding gesamt | 32 dp rundum |
|
|
|
|
### Typografie
|
|
|
|
| Element | Style |
|
|
|--------------|-------------------------------------------------------|
|
|
| Titel | `MaterialTheme.typography.titleMedium` |
|
|
| Beschreibung | `MaterialTheme.typography.bodyMedium`, Alpha 60% |
|
|
| Button | Standard `MsButton` (outlined fĂŒr sekundĂ€re Aktionen) |
|
|
|
|
---
|
|
|
|
## 4. Icon-Konzept
|
|
|
|
Wir verwenden **Material Symbols (Outlined)** â konsistent mit dem restlichen Design-System.
|
|
Kein Custom-Illustration-Set fĂŒr MVP (zu aufwĂ€ndig, zu wenig Mehrwert bei High-Density-Desktop).
|
|
|
|
| Screen / Kontext | Icon | BegrĂŒndung |
|
|
|--------------------------|----------------------------------|-----------------------------|
|
|
| Veranstalter-Liste | `person_off` | Kein Veranstalter vorhanden |
|
|
| Veranstaltungs-Liste | `event_busy` | Keine Veranstaltung |
|
|
| Turnier-Liste | `emoji_events` (durchgestrichen) | Kein Turnier |
|
|
| Bewerb-Liste | `list_alt` | Keine Bewerbe |
|
|
| Nennungs-Liste | `assignment_ind` | Keine Nennungen |
|
|
| Reiter-Liste | `person_search` | Kein Reiter gefunden |
|
|
| Pferde-Liste | `search_off` | Kein Pferd gefunden |
|
|
| Verein-Liste | `group_off` | Kein Verein |
|
|
| FunktionÀr-Liste | `badge` | Kein FunktionÀr |
|
|
| Abteilungs-Startliste | `format_list_numbered` | Keine Starter |
|
|
| Abteilungs-Ergebnisliste | `leaderboard` | Keine Ergebnisse |
|
|
| Suche / Filter (allg.) | `search_off` | Kein Treffer |
|
|
| Fehler (allg.) | `cloud_off` | Verbindungsfehler |
|
|
|
|
---
|
|
|
|
## 5. Texte je Screen
|
|
|
|
### Allgemeine Regel
|
|
|
|
- **Titel:** Kurz, prÀzise, max. 5 Wörter. Keine Ausrufezeichen.
|
|
- **Beschreibung:** ErklÀrt den Zustand UND gibt Handlungshinweis. Max. 2 SÀtze.
|
|
- **CTA:** Verb + Objekt. Klar und direkt.
|
|
|
|
### Texte je Kontext
|
|
|
|
#### Veranstalter
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------------|---------------------------------------------------------|----------------------|
|
|
| `EMPTY_LIST` | Noch kein Veranstalter | Lege den ersten Veranstalter an, um loszulegen. | Veranstalter anlegen |
|
|
| `NO_RESULTS` | Kein Veranstalter gefunden | Kein Eintrag passt zur Suche. Passe den Suchbegriff an. | Suche zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Veranstalter konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Veranstaltungen
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|------------------------------|-----------------------------------------------------------|-----------------------|
|
|
| `EMPTY_LIST` | Noch keine Veranstaltung | Erstelle die erste Veranstaltung fĂŒr diesen Veranstalter. | Veranstaltung anlegen |
|
|
| `NO_RESULTS` | Keine Veranstaltung gefunden | Kein Treffer fĂŒr den gewĂ€hlten Filter. | Filter zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Veranstaltungen konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Turniere
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|-----------------------|-------------------------------------------------------|---------------------|
|
|
| `EMPTY_LIST` | Noch kein Turnier | FĂŒge das erste Turnier zu dieser Veranstaltung hinzu. | Turnier anlegen |
|
|
| `NO_RESULTS` | Kein Turnier gefunden | Kein Turnier entspricht dem aktuellen Filter. | Filter zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Turniere konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Bewerbe
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-----------------------------------------------|---------------------|
|
|
| `EMPTY_LIST` | Noch kein Bewerb | Lege den ersten Bewerb fĂŒr dieses Turnier an. | Bewerb anlegen |
|
|
| `NO_RESULTS` | Kein Bewerb gefunden | Kein Bewerb entspricht dem aktuellen Filter. | Filter zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Bewerbe konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Nennungen
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|------------------------|-----------------------------------------------------------|---------------------|
|
|
| `EMPTY_LIST` | Noch keine Nennung | Es wurden noch keine Nennungen fĂŒr diesen Bewerb erfasst. | Nennung erfassen |
|
|
| `NO_RESULTS` | Keine Nennung gefunden | Kein Treffer fĂŒr den gewĂ€hlten Filter oder die Suche. | Filter zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Nennungen konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Reiter
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-------------------------------------------------------|--------------------|
|
|
| `EMPTY_LIST` | Noch kein Reiter | Importiere Stammdaten oder lege den ersten Reiter an. | Reiter anlegen |
|
|
| `NO_RESULTS` | Kein Reiter gefunden | Kein Reiter entspricht dem Suchbegriff. | Suche zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Reiter konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Pferde
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-----------------------------------------------------|--------------------|
|
|
| `EMPTY_LIST` | Noch kein Pferd | Importiere Stammdaten oder lege das erste Pferd an. | Pferd anlegen |
|
|
| `NO_RESULTS` | Kein Pferd gefunden | Kein Pferd entspricht dem Suchbegriff. | Suche zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Pferde konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Vereine
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-----------------------------------------|--------------------|
|
|
| `EMPTY_LIST` | Noch kein Verein | Lege den ersten Verein an. | Verein anlegen |
|
|
| `NO_RESULTS` | Kein Verein gefunden | Kein Verein entspricht dem Suchbegriff. | Suche zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | Vereine konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### FunktionÀre
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|--------------------------|---------------------------------------------|--------------------|
|
|
| `EMPTY_LIST` | Noch kein FunktionÀr | Lege den ersten FunktionÀr an. | FunktionÀr anlegen |
|
|
| `NO_RESULTS` | Kein FunktionĂ€r gefunden | Kein FunktionĂ€r entspricht dem Suchbegriff. | Suche zurĂŒcksetzen |
|
|
| `ERROR` | Laden fehlgeschlagen | FunktionÀre konnten nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Abteilungs-Startliste
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-----------------------------------------------------------|------------------|
|
|
| `EMPTY_LIST` | Keine Starter | FĂŒr diese Abteilung wurden noch keine Starter zugewiesen. | Nennungen prĂŒfen |
|
|
| `ERROR` | Laden fehlgeschlagen | Startliste konnte nicht geladen werden. | Erneut versuchen |
|
|
|
|
#### Abteilungs-Ergebnisliste
|
|
|
|
| Typ | Titel | Beschreibung | CTA |
|
|
|--------------|----------------------|-----------------------------------------------------------|------------------|
|
|
| `EMPTY_LIST` | Keine Ergebnisse | FĂŒr diese Abteilung wurden noch keine Ergebnisse erfasst. | â |
|
|
| `ERROR` | Laden fehlgeschlagen | Ergebnisliste konnte nicht geladen werden. | Erneut versuchen |
|
|
|
|
---
|
|
|
|
## 6. Composable-Spezifikation â `MsEmptyState`
|
|
|
|
Ablageort:
|
|
`frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsEmptyState.kt`
|
|
|
|
### API
|
|
|
|
```kotlin
|
|
/**
|
|
* Einheitlicher Empty-State fĂŒr alle Listenansichten.
|
|
*
|
|
* @param icon Material-Symbol (ImageVector) passend zum Kontext.
|
|
* @param title Kurzer Titel (max. 5 Wörter).
|
|
* @param description ErklÀrung + Handlungshinweis (max. 2 SÀtze). Optional.
|
|
* @param actionLabel Text des primÀren CTA-Buttons. Null = kein Button.
|
|
* @param onAction Callback fĂŒr den CTA-Button.
|
|
* @param modifier Modifier fĂŒr Ă€uĂeres Layout.
|
|
*/
|
|
@Composable
|
|
fun MsEmptyState(
|
|
icon: ImageVector,
|
|
title: String,
|
|
description: String? = null,
|
|
actionLabel: String? = null,
|
|
onAction: (() -> Unit)? = null,
|
|
modifier: Modifier = Modifier
|
|
)
|
|
```
|
|
|
|
### Verhalten
|
|
|
|
- Zentriert (horizontal + vertikal) im verfĂŒgbaren Raum.
|
|
- Icon gedimmt (38% Alpha), Beschreibung gedimmt (60% Alpha).
|
|
- Button nur anzeigen wenn `actionLabel != null && onAction != null`.
|
|
- Button-Stil: `OutlinedButton` (sekundÀre Aktion, nicht zu dominant).
|
|
- Bei `ERROR`-Typ: Icon `cloud_off`, Button als `FilledButton` (Retry ist primÀre Aktion).
|
|
|
|
### Verwendungsbeispiel
|
|
|
|
```kotlin
|
|
// In einer LazyColumn / Column wenn items.isEmpty():
|
|
if (state.reiter.isEmpty() && !state.isLoading) {
|
|
MsEmptyState(
|
|
icon = Icons.Outlined.PersonSearch,
|
|
title = "Noch kein Reiter",
|
|
description = "Importiere Stammdaten oder lege den ersten Reiter an.",
|
|
actionLabel = "Reiter anlegen",
|
|
onAction = { onIntent(ReiterIntent.OpenCreateDialog) }
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Implementierungs-Reihenfolge (Sprint C-1)
|
|
|
|
| PrioritÀt | Screen | Typ(en) |
|
|
|-----------|--------------------------|----------------------------|
|
|
| 1 | Veranstalter-Auswahl | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 2 | Veranstaltungs-Ăbersicht | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 3 | Turnier-Bewerbe-Tab | `EMPTY_LIST` |
|
|
| 4 | Turnier-Nennungen-Tab | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 5 | Reiter-Liste | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 6 | Pferde-Liste | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 7 | Verein-Liste | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 8 | FunktionÀr-Liste | `EMPTY_LIST`, `NO_RESULTS` |
|
|
| 9 | Abteilungs-Startliste | `EMPTY_LIST` |
|
|
| 10 | Abteilungs-Ergebnisliste | `EMPTY_LIST` |
|
|
| Alle | Alle Listen | `ERROR` (einheitlich) |
|
|
|
|
> **Hinweis:** `ERROR`-State wird einmalig als Default-Variante in `MsEmptyState` implementiert
|
|
> und kann ĂŒberall mit `type = EmptyStateType.ERROR` aufgerufen werden.
|
|
|
|
---
|
|
|
|
## 8. Abgrenzung zu anderen Komponenten
|
|
|
|
| Situation | Komponente |
|
|
|-------------------------------|-----------------------|
|
|
| Daten werden geladen | `MsLoadingIndicator` |
|
|
| Liste leer / kein Treffer | `MsEmptyState` |
|
|
| Kritischer Fehler (App-Ebene) | Eigener Error-Screen |
|
|
| Inline-Validierungsfehler | `MsValidationWrapper` |
|
|
| Status einer EntitÀt | `MsStatusBadge` |
|