refactor(frontend): ActionMenu als standardisierte Komponente (DRY) + Rule: Keine Inline-Buttons #193

Closed
opened 2026-07-02 22:22:06 +00:00 by niboer · 0 comments
Owner

Einordnung

  • Position: 5 (Phase B, parallel zu #198)
  • Benötigt: — (keine; Modals-Doku verweist auf PersonEditModal, das erst in #195 kommt — Doku wird dann nachgezogen)
  • Wird benötigt von: #195 (PersonEditModal braucht ActionMenu als Trigger)
  • Ersetzt: #192 (Actions-Spalte Personen-Ansicht)

Beschreibung

Das Dropdown-Menue-Pattern fuer Actions (Drei-Punkt-Button) ist aktuell 3x inline dupliziert in frontend/src/routes/app/events/[eventId]/+page.svelte und wird in 2 weiteren Seiten als Inline-Buttons (ohne Menue) realisiert.

Projektregel (neu, in AGENTS.md)

Actions-Spalten in DataTables duerfen ausschliesslich das ActionMenu (drei Punkte •••) verwenden. Keine Inline-Buttons als Aktionen.

Der Frontend-AGENTS.md-Eintrag unter "App-Template-Komponenten" wird erweitert.

Betroffene Actions-Spalten (vollstaendige Migration)

# Seite Datei Aktuell Neu
1 Event-Detail, Guests-Tab events/[eventId]/+page.svelte:946-1035 Inline-Dropdown (90 Z., Status/Rolle/Zahlung/Entfernen) ActionMenu
2 Event-Detail, Antworten-Tab events/[eventId]/+page.svelte:1118-1160 Inline-Dropdown (43 Z., Status) ActionMenu
3 Event-Detail, Payment-Tab events/[eventId]/+page.svelte:1238-1271 Inline-Dropdown (34 Z., Zahlung) ActionMenu
4 Personen persons/+page.svelte:130-137 Inline-Buttons (Einladen + Archivieren) ActionMenu
5 Gruppen-Mitglieder groups/[groupId]/+page.svelte Inline-Button (Entfernen) ActionMenu

Hinweis fuer #4+#5: Die Aktionen "Bearbeiten" und "Einladen" werden erst in #195 (#4+#5) bzw. #191 (#4) ergaenzt. In diesem Issue werden zunaechst die bestehenden Aktionen (Einladen/Archivieren/Entfernen) ins ActionMenu ueberfuehrt.

Loesungsvorschlag

ActionMenu-Komponente in frontend/src/lib/components/action-menu.svelte

interface ActionMenuItem {
  label: string;
  type: 'button' | 'submit' | 'divider' | 'submenu';
  action?: string;            // Form action (z.B. '?/updateStatus')
  name?: string;              // Input name
  value?: string;             // Input value
  href?: string;              // Navigation
  disabled?: boolean;
  variant?: 'default' | 'destructive';
  children?: ActionMenuItem[];  // Submenu items
  onClick?: () => void;        // Callback fuer Modals (z.B. setzt editingPerson)
}

let {
  items = [] as ActionMenuItem[],
  rowId = '' as string,
}: Props = $props();

Interna:

  • Trigger: Drei-Punkt-SVG-Button (variant="ghost", h-7 w-7)
  • Position: fixed via getBoundingClientRect wie bisher
  • Backdrop: div.fixed.inset-0.z-40 zum Schliessen bei Klick ausserhalb
  • Submenues: Hover-Steuerung via onmouseenter/onmouseleave, Richtungs-Abwaegung left-full/right-full

AGENTS.md (root) erweitern:

### Actions-Spalten
- Actions-Spalten in DataTables nutzen ausschliesslich die `ActionMenu`-Komponente (drei Punkte `•••`)
- Keine Inline-Buttons oder andere Interaktionselemente in Actions-Spalten
- Das ActionMenu oeffnet via fixed-Positionierung, Backdrop schliesst bei Klick ausserhalb

docs/design/04-frontend-architektur.md erweitern:

Neuer Tabellen-Eintrag unter "Komponenten":

| `action-menu.svelte` | ActionMenu (Drei-Punkt-Menue) mit Submenues — einzige erlaubte Actions-Darstellung in DataTables |

Neuer Abschnitt "Modals / Dialoge" (Hinweis: PersonEditModal wird in #195 ergaenzt):

## Modals / Dialoge

Uebersicht aller modalen Dialoge (shadcn-svelte Dialog):

| Modal | Datei | Verwendung | Props |
|-------|-------|-----------|-------|
| GuestFormModal | `guest-form-modal.svelte` | Event-Detail: Gast-Formular beantworten/ueberschreiben | `guest`, `formDefinition`, `open`, `onClose` |
| PersonEditModal | `person-edit-modal.svelte` | Personen/Gruppen: Stammdaten+Kontaktdaten editieren *— wird in #195 angelegt* | `person`, `open`, `onClose` |
| ConsentDialog | `consent-dialog.svelte` | DSGVO-Zustimmung einholen | `personId`, `invitations`, `groupMembers`, `onConsented` |
| SessionTimeoutModal | `session-timeout-modal.svelte` | Session-Timer-Warnung | — |
| RoleEditor | `role-editor.svelte` | Event-Rollen CRUD | `eventRoles`, `eventId`, ... |
| FormEditor | `form-editor.svelte` | Formular-Definition bearbeiten | `formDefinition`, `templates`, ... |

Policy:
- Jedes Modal ist eine eigene `.svelte`-Datei in `$lib/components/`
- Props via `$props()` (Svelte 5 Runes)
- Datenabruf via `fetch` im Modal selbst (self-contained)
- Keine Abhaengigkeit von einer bestimmten Route

Abhaengigkeiten

  • Svelte 5 Runes ($state, $derived, $props)
  • Button aus $lib/components/ui/button.svelte

Vorteile

  1. Single Source of Truth — Aenderungen an 1 Stelle
  2. Weniger Code — ~170 Z. Inline-Dropdown → ~1 Z. <ActionMenu items={...}/> pro Stelle
  3. Konsistentes UX — gleiches Verhalten auf allen Seiten
  4. Erweiterbar — jede Actions-Spalte kann um neue Eintraege ergaenzt werden ohne Layout-Aenderungen

Dokumentation

  • frontend/AGENTS.md — Rule fuer Actions-Spalten
  • docs/design/04-frontend-architektur.md — ActionMenu in Tabelle, neuer "Modals"-Abschnitt
  • Ausfuehrliche Komponenten-Doku in frontend/src/lib/components/action-menu.svelte (Code-Selbsterklaerung, keine Kommentare)

Dateien

Datei Aenderung
frontend/src/lib/components/action-menu.svelte Neu
frontend/src/routes/app/events/[eventId]/+page.svelte 3 Dropdowns durch ActionMenu ersetzen
frontend/src/routes/app/persons/+page.svelte Inline-Buttons durch ActionMenu ersetzen (nur Einladen/Archivieren; "Bearbeiten" kommt in #195)
frontend/src/routes/app/groups/[groupId]/+page.svelte Inline-Button durch ActionMenu ersetzen (nur Entfernen; "Bearbeiten" kommt in #195)
frontend/AGENTS.md Rule ergaenzen
docs/design/04-frontend-architektur.md ActionMenu in Tabelle + Modals-Abschnitt
## Einordnung - **Position:** 5 (Phase B, parallel zu #198) - **Benötigt:** — (keine; Modals-Doku verweist auf PersonEditModal, das erst in #195 kommt — Doku wird dann nachgezogen) - **Wird benötigt von:** #195 (PersonEditModal braucht ActionMenu als Trigger) - **Ersetzt:** #192 (Actions-Spalte Personen-Ansicht) ## Beschreibung Das Dropdown-Menue-Pattern fuer Actions (Drei-Punkt-Button) ist aktuell **3x inline dupliziert** in `frontend/src/routes/app/events/[eventId]/+page.svelte` und wird in 2 weiteren Seiten als Inline-Buttons (ohne Menue) realisiert. ## Projektregel (neu, in AGENTS.md) > **Actions-Spalten in DataTables duerfen ausschliesslich das `ActionMenu` (drei Punkte `•••`) verwenden. Keine Inline-Buttons als Aktionen.** Der Frontend-AGENTS.md-Eintrag unter "App-Template-Komponenten" wird erweitert. ## Betroffene Actions-Spalten (vollstaendige Migration) | # | Seite | Datei | Aktuell | Neu | |---|-------|-------|---------|-----| | 1 | Event-Detail, Guests-Tab | `events/[eventId]/+page.svelte:946-1035` | Inline-Dropdown (90 Z., Status/Rolle/Zahlung/Entfernen) | ActionMenu | | 2 | Event-Detail, Antworten-Tab | `events/[eventId]/+page.svelte:1118-1160` | Inline-Dropdown (43 Z., Status) | ActionMenu | | 3 | Event-Detail, Payment-Tab | `events/[eventId]/+page.svelte:1238-1271` | Inline-Dropdown (34 Z., Zahlung) | ActionMenu | | 4 | Personen | `persons/+page.svelte:130-137` | Inline-Buttons (Einladen + Archivieren) | ActionMenu | | 5 | Gruppen-Mitglieder | `groups/[groupId]/+page.svelte` | Inline-Button (Entfernen) | ActionMenu | **Hinweis fuer #4+#5:** Die Aktionen "Bearbeiten" und "Einladen" werden erst in #195 (#4+#5) bzw. #191 (#4) ergaenzt. In diesem Issue werden zunaechst die bestehenden Aktionen (Einladen/Archivieren/Entfernen) ins ActionMenu ueberfuehrt. ## Loesungsvorschlag ### `ActionMenu`-Komponente in `frontend/src/lib/components/action-menu.svelte` ```typescript interface ActionMenuItem { label: string; type: 'button' | 'submit' | 'divider' | 'submenu'; action?: string; // Form action (z.B. '?/updateStatus') name?: string; // Input name value?: string; // Input value href?: string; // Navigation disabled?: boolean; variant?: 'default' | 'destructive'; children?: ActionMenuItem[]; // Submenu items onClick?: () => void; // Callback fuer Modals (z.B. setzt editingPerson) } let { items = [] as ActionMenuItem[], rowId = '' as string, }: Props = $props(); ``` **Interna:** - Trigger: Drei-Punkt-SVG-Button (`variant="ghost"`, `h-7 w-7`) - Position: `fixed` via `getBoundingClientRect` wie bisher - Backdrop: `div.fixed.inset-0.z-40` zum Schliessen bei Klick ausserhalb - Submenues: Hover-Steuerung via `onmouseenter`/`onmouseleave`, Richtungs-Abwaegung `left-full`/`right-full` ### `AGENTS.md` (root) erweitern: ```markdown ### Actions-Spalten - Actions-Spalten in DataTables nutzen ausschliesslich die `ActionMenu`-Komponente (drei Punkte `•••`) - Keine Inline-Buttons oder andere Interaktionselemente in Actions-Spalten - Das ActionMenu oeffnet via fixed-Positionierung, Backdrop schliesst bei Klick ausserhalb ``` ### `docs/design/04-frontend-architektur.md` erweitern: **Neuer Tabellen-Eintrag unter "Komponenten":** ``` | `action-menu.svelte` | ActionMenu (Drei-Punkt-Menue) mit Submenues — einzige erlaubte Actions-Darstellung in DataTables | ``` **Neuer Abschnitt "Modals / Dialoge" (Hinweis: PersonEditModal wird in #195 ergaenzt):** ```markdown ## Modals / Dialoge Uebersicht aller modalen Dialoge (shadcn-svelte Dialog): | Modal | Datei | Verwendung | Props | |-------|-------|-----------|-------| | GuestFormModal | `guest-form-modal.svelte` | Event-Detail: Gast-Formular beantworten/ueberschreiben | `guest`, `formDefinition`, `open`, `onClose` | | PersonEditModal | `person-edit-modal.svelte` | Personen/Gruppen: Stammdaten+Kontaktdaten editieren *— wird in #195 angelegt* | `person`, `open`, `onClose` | | ConsentDialog | `consent-dialog.svelte` | DSGVO-Zustimmung einholen | `personId`, `invitations`, `groupMembers`, `onConsented` | | SessionTimeoutModal | `session-timeout-modal.svelte` | Session-Timer-Warnung | — | | RoleEditor | `role-editor.svelte` | Event-Rollen CRUD | `eventRoles`, `eventId`, ... | | FormEditor | `form-editor.svelte` | Formular-Definition bearbeiten | `formDefinition`, `templates`, ... | Policy: - Jedes Modal ist eine eigene `.svelte`-Datei in `$lib/components/` - Props via `$props()` (Svelte 5 Runes) - Datenabruf via `fetch` im Modal selbst (self-contained) - Keine Abhaengigkeit von einer bestimmten Route ``` ## Abhaengigkeiten - Svelte 5 Runes (`$state`, `$derived`, `$props`) - `Button` aus `$lib/components/ui/button.svelte` ## Vorteile 1. **Single Source of Truth** — Aenderungen an 1 Stelle 2. **Weniger Code** — ~170 Z. Inline-Dropdown → ~1 Z. `<ActionMenu items={...}/>` pro Stelle 3. **Konsistentes UX** — gleiches Verhalten auf allen Seiten 4. **Erweiterbar** — jede Actions-Spalte kann um neue Eintraege ergaenzt werden ohne Layout-Aenderungen ## Dokumentation - `frontend/AGENTS.md` — Rule fuer Actions-Spalten - `docs/design/04-frontend-architektur.md` — ActionMenu in Tabelle, neuer "Modals"-Abschnitt - Ausfuehrliche Komponenten-Doku in `frontend/src/lib/components/action-menu.svelte` (Code-Selbsterklaerung, keine Kommentare) ## Dateien | Datei | Aenderung | |---|---| | `frontend/src/lib/components/action-menu.svelte` | **Neu** | | `frontend/src/routes/app/events/[eventId]/+page.svelte` | 3 Dropdowns durch ActionMenu ersetzen | | `frontend/src/routes/app/persons/+page.svelte` | Inline-Buttons durch ActionMenu ersetzen (nur Einladen/Archivieren; "Bearbeiten" kommt in #195) | | `frontend/src/routes/app/groups/[groupId]/+page.svelte` | Inline-Button durch ActionMenu ersetzen (nur Entfernen; "Bearbeiten" kommt in #195) | | `frontend/AGENTS.md` | Rule ergaenzen | | `docs/design/04-frontend-architektur.md` | ActionMenu in Tabelle + Modals-Abschnitt |
niboer changed title from refactor(frontend): ActionMenu als standardisierte Komponente (DRY) to refactor(frontend): ActionMenu als standardisierte Komponente (DRY) + Rule: Keine Inline-Buttons 2026-07-02 22:37:11 +00:00
Sign in to join this conversation.
No description provided.