diff --git a/docs/04_Agents/Logs/2026-03-21_Frontend_NennungsMaske.md b/docs/04_Agents/Logs/2026-03-21_Frontend_NennungsMaske.md
new file mode 100644
index 00000000..f28bd737
--- /dev/null
+++ b/docs/04_Agents/Logs/2026-03-21_Frontend_NennungsMaske.md
@@ -0,0 +1,81 @@
+---
+type: Log
+agent: Curator
+date: 2026-03-21
+status: COMPLETED
+---
+
+# đ§č Session Log: 21. MĂ€rz 2026
+
+## Zusammenfassung
+
+Diese Session hatte zwei Schwerpunkte: (1) Etablierung eines neuen **Figma â Repo â Compose**-Workflows fĂŒr die
+UI-Entwicklung und (2) Implementierung der ersten vollstĂ€ndigen Feature-Maske der **Master Desktop-App** â die
+`NennungsMaske`.
+
+## Erreichte Meilensteine
+
+### 1. Figma-Workflow etabliert
+
+- Stefan hat in **Figma Make** einen interaktiven Prototyp der Desktop-Nennungs-Maske erstellt.
+- Der Export (React/TypeScript-Code + Assets) wurde direkt ins Repo unter `docs/06_Frontend/FIGMA/` kopiert.
+- Dieser "brutale aber geniale" Workflow ermöglicht es, Figma-Exports als **direkte Blaupause** fĂŒr die
+ Compose-Implementierung zu nutzen.
+- Neuer Standard-Workflow: `Figma Make (Stefan) â Export ins Repo â Compose-Implementierung (Agent)`
+
+### 2. Neues Feature-Modul: `nennung-feature`
+
+- Neues KMP-Modul erstellt: `frontend/features/nennung-feature`
+- EnthÀlt:
+ - `NennungModels.kt` â Domain-Modelle (Pferd, Reiter, Bewerb, Nennung, VerkaufsArtikel)
+ - `NennungViewModel.kt` â State-Management mit Koin DI
+ - `NennungsMaske.kt` â VollstĂ€ndige 3-Spalten-Composable (Pferd/Reiter-Suche | Aktions-Hub | Verkauf/Buchungen +
+ Bewerbsliste)
+- Mock-Daten aus dem Figma-Export ĂŒbernommen (echte Preise: Boxenpauschale 115âŹ, Heu 13âŹ, etc.)
+
+### 3. Navigation & Shell-Integration
+
+- `AppScreen.Nennung` in der Navigation registriert (`AppScreen.kt`)
+- `expect/actual`-Pattern fĂŒr `NennungScreenContent` implementiert (JVM: vollstĂ€ndige Maske, JS: Placeholder)
+- `main.kt`: `nennungFeatureModule` in Koin registriert
+- `MainApp.kt`: Dashboard-Button "Nennungs-Maske öffnen" + Nennung-Branch in der Navigation
+
+## Fachlicher Kontext: Nennungs-Maske
+
+Die Nennungs-Maske ist das **HerzstĂŒck der Desktop-App**. Sie basiert auf dem Altsystem SuDo und wurde analysiert anhand
+von:
+
+- `docs/BilderSuDo/Nennungen.PNG`
+- `docs/BilderSuDo/Nennungen-Buchungen.PNG`
+- `docs/BilderSuDo/NennungsTausch.PNG`
+- `docs/06_Frontend/Screenshots/Desktop-Nennmaske-Entwurf_2026-03-21_11-53.png` (Stefans Figma-Entwurf)
+
+Layout: 3 Spalten
+
+- **Links:** Pferd- und Reiter-Suche + Nennungstabelle (Tabs: Reiter | Pferd | Bewerbe)
+- **Mitte:** Aktions-Hub (Nennung durchfĂŒhren, Stornieren, Startliste, Ergebnisse, Abrechnung)
+- **Rechts:** Verkauf/Buchungen (Tabs) + Bewerbsliste mit Filter
+
+## Offene Punkte / NĂ€chste Schritte
+
+- **Nennungstausch-Dialog** â eigene Maske/Modal (3-teilig: Quell-Nennung | Tausch-Optionen | Ziel-Nennung)
+- **Keyboard-Shortcuts** â F5 (Nennung), F6 (Stornieren), F7 (Startliste), F8 (Ergebnisse), Escape (Leeren)
+- **Lizenz-Badge** â grĂŒn/rot bei Reiter-Metadaten (nach Auswahl)
+- **Konto-Saldo** â rot wenn negativ, bei Reiter-Info
+- **Offline-Indikator** â Badge in der Titelleiste
+- **Weitere Masken** â Ergebnis-Erfassung, Startlisten-Erstellung (nĂ€chste Figma-Exports von Stefan)
+
+## Dokumentation
+
+- Neu: `frontend/features/nennung-feature/` (vollstÀndiges KMP-Modul)
+- Neu: `docs/06_Frontend/FIGMA/` (Figma Make Export â React/TypeScript Blaupause)
+- Neu: `docs/06_Frontend/Screenshots/Desktop-Nennmaske-Entwurf_2026-03-21_11-53.png`
+- Update: `frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/AppScreen.kt`
+- Update: `frontend/shells/meldestelle-portal/src/commonMain/kotlin/MainApp.kt`
+- Update: `frontend/shells/meldestelle-portal/src/jvmMain/kotlin/main.kt`
+
+## Build-Status
+
+- â
`./gradlew :frontend:features:nennung-feature:compileKotlinJvm` â BUILD SUCCESSFUL
+- â
`./gradlew :frontend:shells:meldestelle-portal:compileKotlinJvm` â BUILD SUCCESSFUL
+- â
Desktop-App startet erfolgreich (Koin initialisiert, lokale DB erstellt)
diff --git a/docs/06_Frontend/FIGMA/ATTRIBUTIONS.md b/docs/06_Frontend/FIGMA/ATTRIBUTIONS.md
new file mode 100644
index 00000000..ce6bb5a6
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/ATTRIBUTIONS.md
@@ -0,0 +1,5 @@
+This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used
+under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).
+
+This Figma Make file includes photos from [Unsplash](https://unsplash.com) used
+under [license](https://unsplash.com/license).
diff --git a/docs/06_Frontend/FIGMA/README.md b/docs/06_Frontend/FIGMA/README.md
new file mode 100644
index 00000000..5a88cec1
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/README.md
@@ -0,0 +1,544 @@
+# Turnierverwaltungs-Anwendung - Nennungs-Maske
+
+## đ ProjektĂŒbersicht
+
+Professionelle Desktop-Anwendung fĂŒr die Verwaltung von Pferdesport-Turnieren mit Fokus auf die **Nennungs-Maske** als
+HerzstĂŒck der Anwendung.
+
+Die Anwendung ermöglicht die effiziente Erfassung von Turnier-Nennungen durch ein intelligentes Pferd-Reiter-Suchsystem
+mit Cross-Reference-FunktionalitÀt und IMS-Kennzeichnung (Im System).
+
+---
+
+## đš Design-System
+
+- **Design Framework**: Material Design 3 (MUI v5+)
+- **PrimÀrfarbe**: Indigo (#3F51B5)
+- **UI-Philosophie**: Kompakt, informationsdicht, tastaturoptimiert
+- **Zielplattform**: Desktop (keine Mobile-Optimierung erforderlich)
+
+---
+
+## đ Technologie-Stack
+
+### Core
+
+- **React** 18+ mit TypeScript
+- **Material-UI (MUI)** v5+ fĂŒr UI-Komponenten
+- **Emotion** fĂŒr CSS-in-JS Styling
+
+### Dependencies
+
+```json
+{
+ "@mui/material": "^5.x",
+ "@emotion/react": "^11.x",
+ "@emotion/styled": "^11.x",
+ "@mui/icons-material": "^5.x",
+ "react": "^18.x",
+ "react-dom": "^18.x",
+ "typescript": "^5.x"
+}
+```
+
+---
+
+## đ Layout-Struktur
+
+### 2-Spalten 3-Zeilen Grid (60% / 40% horizontal)
+
+```
+âââââââââââââââââââââââââââââââââââŹâââââââââââââââââââââââ
+â Pferd/Reiter Suche (60%) â Verkauf/Buchungen â 50%
+â - Pferd Suche + Details â (40%) â
+â - Reiter Suche + Details â â
+âââââââââââââââââââââââââââââââââââŽâââââââââââââââââââââââ€
+â Navigation (Startliste | Ergebnisse | Abrechnung) â 5%
+âââââââââââââââââââââââââââââââââââŹâââââââââââââââââââââââ€
+â NennungsĂŒbersicht (60%) â BewerbsĂŒbersicht â 45%
+â - Reiter | Pferd | Bewerbe â (40%) â
+â - Tabbed Interface â - Doppelklick nennt â
+âââââââââââââââââââââââââââââââââââŽâââââââââââââââââââââââ
+```
+
+---
+
+## đ Komponenten-Architektur
+
+### Komponenten-Hierarchie
+
+```
+App.tsx
+âââ NennungsMaske.tsx (Hauptlayout)
+ âââ PferdReiterEingabe.tsx (Such-System)
+ âââ VerkaufBuchungen.tsx (Verkauf/Buchungen)
+ âââ NennungenTabelle.tsx (NennungsĂŒbersicht)
+ âââ Bewerbsliste.tsx (BewerbsĂŒbersicht)
+```
+
+### Datei-Ăbersicht
+
+| Datei | Beschreibung | Zeilen |
+|--------------------------|---------------------------------------|--------|
+| `App.tsx` | Root Component mit MUI Theme Provider | ~30 |
+| `theme.tsx` | Material Design 3 Theme (Indigo) | ~50 |
+| `NennungsMaske.tsx` | Hauptlayout mit Grid-Struktur | ~120 |
+| `PferdReiterEingabe.tsx` | Intelligentes Such-System | ~400 |
+| `VerkaufBuchungen.tsx` | Verkauf & Buchungen Tabs | ~250 |
+| `NennungenTabelle.tsx` | NennungsĂŒbersicht mit Tabs | ~180 |
+| `Bewerbsliste.tsx` | BewerbsĂŒbersicht | ~180 |
+
+---
+
+## đ Core Features
+
+### 1. **Intelligentes Such-System**
+
+#### Pferd-Suche
+
+- **Eingabe**: Kopfnummer oder Name
+- **Echtzeit-Filterung**: WĂ€hrend der Eingabe alle Treffer anzeigen
+- **Nach Auswahl**: Top 4 Ergebnisse behalten (scrollbar)
+- **Cross-Reference**: Zeigt automatisch Reiter des ausgewÀhlten Pferdes
+- **IMS-Kennzeichnung**: Bereits genannte Pferde mit "IMS" Badge
+
+#### Reiter-Suche
+
+- **Eingabe**: Vorname und/oder Nachname
+- **Echtzeit-Filterung**: WĂ€hrend der Eingabe alle Treffer anzeigen
+- **Nach Auswahl**: Top 4 Ergebnisse behalten (scrollbar)
+- **Cross-Reference**: Zeigt automatisch Pferde des ausgewÀhlten Reiters
+- **IMS-Kennzeichnung**: Bereits genannte Reiter mit "IMS" Badge
+- **Geburtsjahr**: Bei Namensgleichheit zur Unterscheidung (*1998, *2002)
+
+#### Tastaturnavigation
+
+- **â/â**: Navigation durch Suchergebnisse
+- **Enter**: Auswahl bestÀtigen
+- **Tab**: Zum nÀchsten Feld
+- **Doppelklick**: Alternative zur Enter-Taste
+
+### 2. **IMS-System (Im System)**
+
+**Definition**: Pferd-Reiter-Kombinationen, die bereits fĂŒr dieses Turnier genannt haben.
+
+**Verhalten**:
+
+```typescript
+// Mock-Daten Beispiel
+const turnieNennungen = [
+ { reiterId: 2, pferdId: 5, bewerbNr: 3 }, // Thomas Bauer mit Domino in Bewerb 3
+ { reiterId: 1, pferdId: 1, bewerbNr: 2 }, // Anna Schneider mit Obora's Donna
+];
+```
+
+**Vorteile**:
+
+- â
Schneller Zugriff auf hÀufig verwendete Kombinationen
+- â
Vermeidung von Duplikaten
+- â
Verkauf (z.B. "Heu") kann schnell auf bestehende Pferde gebucht werden
+
+### 3. **Cross-Reference-FunktionalitÀt**
+
+#### Szenario A: Reiter â Pferde
+
+```
+1. Reiter "Ba" eingeben
+2. Suchergebnis: "Thomas BAUER" (IMS)
+3. Automatisch im Pferd-Feld: "Domino" (IMS)
+4. Pferd auswĂ€hlen â Verkauf buchen ODER
+5. Anderes Pferd suchen â Neue Nennung
+```
+
+#### Szenario B: Pferd â Reiter
+
+```
+1. Pferd "Domino" suchen
+2. Pferd auswÀhlen
+3. Automatisch im Reiter-Feld: "Thomas Bauer" (IMS)
+4. Reiter bestÀtigen ODER
+5. Anderen Reiter suchen â Pferd hat 2 Reiter
+```
+
+### 4. **Workflow: Nennung erstellen**
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â 1. Pferd suchen (oder via Reiter-Cross-Reference) â
+â â Pfeiltasten navigieren â
+â â Enter oder Doppelklick bestĂ€tigen â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 2. Reiter suchen (oder via Pferd-Cross-Reference) â
+â â Pfeiltasten navigieren â
+â â Enter oder Doppelklick bestĂ€tigen â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 3. Pferd + Reiter Details werden angezeigt â
+â â Validierung (Lizenz, Konto-Saldo) â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 4. Bewerb aus BewerbsĂŒbersicht wĂ€hlen â
+â â Doppelklick auf Bewerb â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 5. Nennung erscheint in NennungsĂŒbersicht â
+â â Filterbar nach Reiter/Pferd/Bewerb â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### 5. **Verkauf & Buchungen**
+
+#### Verkauf-Tab
+
+- **Artikel-Liste**: Nenngeld, Stallmiete, Heu, Stroh, etc.
+- **Menge anpassen**: +/- Buttons oder direkte Eingabe
+- **Auto-Berechnung**: Betrag = Menge Ă Einzelpreis
+- **Hervorhebung**: Wichtige Artikel (Nenngeld, Stallmiete) gelb hinterlegt
+- **Buchung**: Nur auf ausgewÀhltes Pferd möglich
+
+#### Buchungen-Tab
+
+- **Ăbersicht**: Alle getĂ€tigten Buchungen
+- **Stornierung**: Einzelne Buchungen rĂŒckgĂ€ngig machen
+
+### 6. **NennungsĂŒbersicht**
+
+#### 3 Tabs
+
+- **Reiter**: Alle Nennungen gefiltert nach ausgewÀhltem Reiter
+- **Pferd**: Alle Nennungen gefiltert nach ausgewÀhltem Pferd
+- **Bewerbe**: Alle Nennungen chronologisch
+
+#### Funktionen
+
+- **Positionieren**: Startreihenfolge manuell festlegen
+- **Stornieren**: Nennung entfernen
+- **Farbcodierung**:
+ - GrĂŒn: Startwunsch "Vorne"
+ - Blau: Startwunsch "Hinten"
+
+### 7. **BewerbsĂŒbersicht**
+
+- **Alle Bewerbe**: Tag, Platz, Nr, Name, Beginn, Anzahl Nennungen
+- **Doppelklick**: Nennung erstellen (nur wenn Pferd + Reiter ausgewÀhlt)
+- **Filter**: Bewerbe nach Kriterien filtern
+- **Aktualisierung**: Echtzeit-Update der Nennungsanzahl
+
+---
+
+## đŻ Bedienkonzept
+
+### Kompakte Desktop-UI
+
+- **Font-GröĂe**: 10-11px (kompakt, informationsdicht)
+- **Button-GröĂe**: `size="small"` mit reduziertem Padding
+- **Tabellen**: Dense-Modus mit minimalen Zeilenhöhen
+- **Icons**: 14-16px (klein aber erkennbar)
+
+### Tastatur-First
+
+- **Tab-Navigation**: Durch alle Eingabefelder
+- **Pfeiltasten**: Navigation in Listen
+- **Enter**: BestÀtigung
+- **Escape**: Abbrechen (TODO)
+- **Shortcuts**: (TODO: Strg+N fĂŒr Neu, Strg+S fĂŒr Speichern)
+
+---
+
+## đ Datenmodell (Mock)
+
+### Pferd
+
+```typescript
+interface Pferd {
+ id: number;
+ kopfnr: string; // z.B. "A123", "4568"
+ name: string; // z.B. "Obora's Donna"
+ rasse: string; // z.B. "Hannoveraner"
+ farbe: string; // z.B. "Brauner"
+ besitzer: string; // z.B. "Franz Huber"
+ stall: string; // z.B. "Box 12"
+}
+```
+
+### Reiter
+
+```typescript
+interface Reiter {
+ id: number;
+ kopfnr: string; // z.B. "201"
+ vorname: string; // z.B. "Thomas"
+ nachname: string; // z.B. "Bauer"
+ verein: string; // z.B. "RC Graz"
+ lizenz: string; // z.B. "LNR-2024-4587"
+ lizenzGueltig: boolean;
+ kontoSaldo: number; // z.B. -125.50 (negativ = Schulden)
+ geburtsjahr: number; // z.B. 1998 (fĂŒr Namensgleichheit)
+}
+```
+
+### Nennung
+
+```typescript
+interface Nennung {
+ id: number;
+ pferdId: number;
+ reiterId: number;
+ bewerbNr: string; // z.B. "1", "2a"
+ bewerbName: string; // z.B. "Dressur Kl. L"
+ tag: string; // z.B. "SA", "SO"
+ platz: string; // z.B. "A1", "C2"
+ startwunsch?: string; // z.B. "Vorne", "Hinten"
+}
+```
+
+### Bewerb
+
+```typescript
+interface Bewerb {
+ nr: string; // z.B. "1", "2a"
+ name: string; // z.B. "Dressur Kl. L"
+ tag: string; // z.B. "SA"
+ platz: string; // z.B. "A1"
+ beginn: string; // z.B. "08:00"
+ nenn: number; // Anzahl Nennungen
+}
+```
+
+### Turnie-Nennung (IMS)
+
+```typescript
+interface TurnieNennung {
+ reiterId: number;
+ pferdId: number;
+ bewerbNr: number;
+}
+```
+
+---
+
+## đ Installation & Setup
+
+### Schritt 1: Projekt initialisieren
+
+```bash
+# React + TypeScript Projekt erstellen
+npx create-react-app turnierverwaltung --template typescript
+
+cd turnierverwaltung
+```
+
+### Schritt 2: Dependencies installieren
+
+```bash
+# MUI und Dependencies
+npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
+
+# TypeScript Types (falls benötigt)
+npm install --save-dev @types/react @types/react-dom
+```
+
+### Schritt 3: Projektstruktur erstellen
+
+```
+src/
+âââ app/
+â âââ App.tsx
+â âââ theme.tsx
+â âââ components/
+â âââ NennungsMaske.tsx
+â âââ PferdReiterEingabe.tsx
+â âââ VerkaufBuchungen.tsx
+â âââ NennungenTabelle.tsx
+â âââ Bewerbsliste.tsx
+âââ styles/
+â âââ theme.css
+âââ index.tsx
+```
+
+### Schritt 4: Dateien manuell ĂŒbertragen
+
+**WICHTIG**: Da kein Download verfĂŒgbar ist, mĂŒssen Sie die Dateien manuell aus Figma Make kopieren:
+
+1. **Ăffnen Sie jede Komponente** in Figma Make
+2. **Kopieren Sie den Code** (Strg+A, Strg+C)
+3. **Erstellen Sie die Datei** in Ihrer IDE
+4. **FĂŒgen Sie den Code ein** (Strg+V)
+
+**Reihenfolge**:
+
+1. `theme.tsx` (Theme zuerst!)
+2. `App.tsx`
+3. `NennungsMaske.tsx`
+4. `PferdReiterEingabe.tsx`
+5. `VerkaufBuchungen.tsx`
+6. `NennungenTabelle.tsx`
+7. `Bewerbsliste.tsx`
+
+### Schritt 5: Starten
+
+```bash
+npm start
+```
+
+---
+
+## đ NĂ€chste Schritte / TODO
+
+### Phase 1: Backend-Integration
+
+- [ ] REST API Endpoints definieren
+- [ ] Pferde aus Datenbank laden
+- [ ] Reiter aus Datenbank laden
+- [ ] Nennungen persistieren
+- [ ] Echtzeit-Updates (WebSocket?)
+
+### Phase 2: Erweiterte Features
+
+- [ ] **Neu-Button**: Dialog fĂŒr neue Pferde/Reiter
+- [ ] **Bearbeiten-Button**: Inline-Editing von Details
+- [ ] **Stornieren**: Nennung mit BestÀtigung löschen
+- [ ] **Positionieren**: Drag & Drop fĂŒr Startreihenfolge
+- [ ] **Filter**: Erweiterte Filteroptionen
+- [ ] **Drucken**: Startlisten, Nennungslisten
+- [ ] **Export**: PDF, Excel
+
+### Phase 3: Validierung & Business Logic
+
+- [ ] Lizenz-PrĂŒfung (abgelaufene Lizenzen warnen)
+- [ ] Konto-Saldo Warnung (negative Salden)
+- [ ] Doppel-Nennungen verhindern
+- [ ] Zeitkonflikte erkennen
+- [ ] KapazitÀtsgrenzen (max. Nennungen pro Bewerb)
+
+### Phase 4: Weitere Masken
+
+- [ ] **Startliste**: Reihenfolge, Startzeiten
+- [ ] **Ergebnisse**: Platzierungen erfassen
+- [ ] **Abrechnung**: Kosten, Zahlungen, Quittungen
+
+### Phase 5: UX-Verbesserungen
+
+- [ ] **Keyboard Shortcuts**: Strg+N, Strg+S, etc.
+- [ ] **Undo/Redo**: Historie fĂŒr Ănderungen
+- [ ] **Suche optimieren**: Fuzzy Search, Synonyme
+- [ ] **Loading States**: Spinner, Skeleton Screens
+- [ ] **Error Handling**: Benutzerfreundliche Fehlermeldungen
+
+### Phase 6: Performance
+
+- [ ] **Virtualisierung**: GroĂe Listen (1000+ EintrĂ€ge)
+- [ ] **Lazy Loading**: Komponenten on-demand laden
+- [ ] **Caching**: HĂ€ufig verwendete Daten
+
+---
+
+## đĄ Design-Entscheidungen
+
+### Warum Material Design 3?
+
+- **Konsistentes Design-System**: BewÀhrte Patterns
+- **Accessibility**: WCAG-konform out-of-the-box
+- **Rich Component Library**: Weniger Custom-Code
+- **Professional Look**: Moderne, cleane Optik
+
+### Warum Indigo als PrimÀrfarbe?
+
+- **Professionell**: VertrauenswĂŒrdig, seriös
+- **Kontrast**: Gute Lesbarkeit auf hellem Hintergrund
+- **Differenzierung**: Nicht das "Standard-Blau"
+
+### Warum 2-Spalten Layout?
+
+- **Workflow-orientiert**: Eingabe links, Ăbersicht rechts
+- **Platzsparend**: Maximale Information auf einem Screen
+- **Desktop-optimiert**: Nutzt breite Bildschirme effizient
+
+### Warum IMS-System?
+
+- **Performance**: Reduziert Suchzeit drastisch
+- **UX**: HĂ€ufige Kombinationen sofort verfĂŒgbar
+- **Fehlerminimierung**: Bereits validierte Kombinationen
+
+---
+
+## đ€ Team-Ăbergabe Checkliste
+
+### FĂŒr Frontend-Entwickler
+
+- [ ] README durchlesen (diese Datei!)
+- [ ] Komponenten-Struktur verstehen
+- [ ] Mock-Daten analysieren
+- [ ] Workflow nachvollziehen (IMS, Cross-Reference)
+- [ ] UI/UX Konzept verinnerlichen
+
+### FĂŒr Backend-Entwickler
+
+- [ ] Datenmodell definieren (siehe "Datenmodell")
+- [ ] API Endpoints spezifizieren
+- [ ] Validierungs-Regeln implementieren
+- [ ] Performance-Anforderungen klÀren
+
+### FĂŒr Product Owner
+
+- [ ] Feature-Priorisierung (siehe "NĂ€chste Schritte")
+- [ ] User Stories schreiben
+- [ ] Acceptance Criteria definieren
+
+### FĂŒr Designer
+
+- [ ] Theme anpassen (Farben, Schriften)
+- [ ] Icons konsistent gestalten
+- [ ] Print-Layouts definieren
+
+---
+
+## đ Support & Fragen
+
+### HĂ€ufige Fragen
+
+**Q: Warum verschwindet das Suchfeld nicht nach der Auswahl?**
+A: Bewusste Design-Entscheidung! Die Top 4 Ergebnisse bleiben sichtbar, damit man schnell zwischen Àhnlichen
+Pferden/Reitern wechseln kann (z.B. mehrere "Obora's..." Pferde).
+
+**Q: Was bedeutet IMS?**
+A: "Im System" = Pferd-Reiter-Kombination hat bereits eine Nennung fĂŒr dieses Turnier.
+
+**Q: Warum Cross-Reference?**
+A: Effizienz! Wenn ich einen Reiter suche, sehe ich sofort seine Pferde. Spart Zeit bei VerkÀufen oder weiteren
+Nennungen.
+
+**Q: Kann ein Pferd mehrere Reiter haben?**
+A: Ja! Ein Pferd kann von verschiedenen Reitern in verschiedenen Bewerben geritten werden.
+
+**Q: Warum ist die Schrift so klein?**
+A: Desktop-Anwendung fĂŒr Profis. Kompakte Darstellung = mehr Information auf einem Blick = weniger Scrollen.
+
+---
+
+## đ Lizenz
+
+(Bitte durch Ihr Team festlegen)
+
+---
+
+## âïž Autoren & Mitwirkende
+
+- **Projekt-Owner**: [Ihr Name]
+- **Prototyp**: Erstellt mit Figma Make
+- **Datum**: MĂ€rz 2026
+
+---
+
+## đ Ănderungshistorie
+
+| Version | Datum | Ănderung |
+|---------|------------|------------------------------------------|
+| 0.1.0 | 2026-03-21 | Initialer Prototyp mit Nennungs-Maske |
+| | | - IMS-System implementiert |
+| | | - Cross-Reference-Suche |
+| | | - 2-Spalten 3-Zeilen Layout (50%-5%-45%) |
+| | | - Material Design 3 (Indigo) |
+
+---
+
+**Ende der Dokumentation**
+
+Bei Fragen oder Unklarheiten: Bitte diese README erweitern und im Team diskutieren!
diff --git a/docs/06_Frontend/FIGMA/guidelines.zip b/docs/06_Frontend/FIGMA/guidelines.zip
new file mode 100644
index 00000000..deb4a504
Binary files /dev/null and b/docs/06_Frontend/FIGMA/guidelines.zip differ
diff --git a/docs/06_Frontend/FIGMA/guidelines/Guidelines.md b/docs/06_Frontend/FIGMA/guidelines/Guidelines.md
new file mode 100644
index 00000000..110f1178
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/guidelines/Guidelines.md
@@ -0,0 +1,61 @@
+**Add your own guidelines here**
+
diff --git a/docs/06_Frontend/FIGMA/package.json b/docs/06_Frontend/FIGMA/package.json
new file mode 100644
index 00000000..67da9a91
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/package.json
@@ -0,0 +1,89 @@
+{
+ "name": "@figma/my-make-file",
+ "private": true,
+ "version": "0.0.1",
+ "type": "module",
+ "scripts": {
+ "build": "vite build"
+ },
+ "dependencies": {
+ "@emotion/react": "11.14.0",
+ "@emotion/styled": "11.14.1",
+ "@mui/icons-material": "7.3.5",
+ "@mui/material": "7.3.5",
+ "@popperjs/core": "2.11.8",
+ "@radix-ui/react-accordion": "1.2.3",
+ "@radix-ui/react-alert-dialog": "1.1.6",
+ "@radix-ui/react-aspect-ratio": "1.1.2",
+ "@radix-ui/react-avatar": "1.1.3",
+ "@radix-ui/react-checkbox": "1.1.4",
+ "@radix-ui/react-collapsible": "1.1.3",
+ "@radix-ui/react-context-menu": "2.2.6",
+ "@radix-ui/react-dialog": "1.1.6",
+ "@radix-ui/react-dropdown-menu": "2.1.6",
+ "@radix-ui/react-hover-card": "1.1.6",
+ "@radix-ui/react-label": "2.1.2",
+ "@radix-ui/react-menubar": "1.1.6",
+ "@radix-ui/react-navigation-menu": "1.2.5",
+ "@radix-ui/react-popover": "1.1.6",
+ "@radix-ui/react-progress": "1.1.2",
+ "@radix-ui/react-radio-group": "1.2.3",
+ "@radix-ui/react-scroll-area": "1.2.3",
+ "@radix-ui/react-select": "2.1.6",
+ "@radix-ui/react-separator": "1.1.2",
+ "@radix-ui/react-slider": "1.2.3",
+ "@radix-ui/react-slot": "1.1.2",
+ "@radix-ui/react-switch": "1.1.3",
+ "@radix-ui/react-tabs": "1.1.3",
+ "@radix-ui/react-toggle-group": "1.1.2",
+ "@radix-ui/react-toggle": "1.1.2",
+ "@radix-ui/react-tooltip": "1.1.8",
+ "canvas-confetti": "1.9.4",
+ "class-variance-authority": "0.7.1",
+ "clsx": "2.1.1",
+ "cmdk": "1.1.1",
+ "date-fns": "3.6.0",
+ "embla-carousel-react": "8.6.0",
+ "input-otp": "1.4.2",
+ "lucide-react": "0.487.0",
+ "motion": "12.23.24",
+ "next-themes": "0.4.6",
+ "react-day-picker": "8.10.1",
+ "react-dnd": "16.0.1",
+ "react-dnd-html5-backend": "16.0.1",
+ "react-hook-form": "7.55.0",
+ "react-popper": "2.3.0",
+ "react-resizable-panels": "2.1.7",
+ "react-responsive-masonry": "2.7.1",
+ "react-router": "7.13.0",
+ "react-slick": "0.31.0",
+ "recharts": "2.15.2",
+ "sonner": "2.0.3",
+ "tailwind-merge": "3.2.0",
+ "tw-animate-css": "1.3.8",
+ "vaul": "1.1.2"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "4.1.12",
+ "@vitejs/plugin-react": "4.7.0",
+ "tailwindcss": "4.1.12",
+ "vite": "6.3.5"
+ },
+ "peerDependencies": {
+ "react": "18.3.1",
+ "react-dom": "18.3.1"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ },
+ "pnpm": {
+ "overrides": {
+ "vite": "6.3.5"
+ }
+ }
+}
diff --git a/docs/06_Frontend/FIGMA/postcss.config.mjs b/docs/06_Frontend/FIGMA/postcss.config.mjs
new file mode 100644
index 00000000..531dbecd
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/postcss.config.mjs
@@ -0,0 +1,15 @@
+/**
+ * PostCSS Configuration
+ *
+ * Tailwind CSS v4 (via @tailwindcss/vite) automatically sets up all required
+ * PostCSS plugins â you do NOT need to include `tailwindcss` or `autoprefixer` here.
+ *
+ * This file only exists for adding additional PostCSS plugins, if needed.
+ * For example:
+ *
+ * import postcssNested from 'postcss-nested'
+ * export default { plugins: [postcssNested()] }
+ *
+ * Otherwise, you can leave this file empty.
+ */
+export default {}
diff --git a/docs/06_Frontend/FIGMA/src.zip b/docs/06_Frontend/FIGMA/src.zip
new file mode 100644
index 00000000..1aafc691
Binary files /dev/null and b/docs/06_Frontend/FIGMA/src.zip differ
diff --git a/docs/06_Frontend/FIGMA/src/app/App.tsx b/docs/06_Frontend/FIGMA/src/app/App.tsx
new file mode 100644
index 00000000..bb496be7
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/App.tsx
@@ -0,0 +1,74 @@
+import {ThemeProvider, createTheme} from '@mui/material/styles';
+import CssBaseline from '@mui/material/CssBaseline';
+import {NennungsMaske} from './components/NennungsMaske';
+
+// Material Design 3 Theme mit Indigo PrimÀrfarbe
+const theme = createTheme({
+ palette: {
+ mode: 'light',
+ primary: {
+ main: '#3F51B5', // Indigo
+ light: '#7986CB',
+ dark: '#303F9F',
+ contrastText: '#FFFFFF',
+ },
+ secondary: {
+ main: '#FF4081',
+ light: '#FF80AB',
+ dark: '#F50057',
+ },
+ error: {
+ main: '#F44336',
+ },
+ warning: {
+ main: '#FF9800',
+ },
+ success: {
+ main: '#4CAF50',
+ },
+ background: {
+ default: '#FAFAFA',
+ paper: '#FFFFFF',
+ },
+ },
+ typography: {
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
+ fontSize: 13, // Kompakt fĂŒr Desktop-Anwendung
+ },
+ components: {
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ textTransform: 'none', // Keine ALL CAPS
+ borderRadius: 8,
+ },
+ },
+ },
+ MuiTextField: {
+ styleOverrides: {
+ root: {
+ '& .MuiInputBase-root': {
+ fontSize: '13px',
+ },
+ },
+ },
+ },
+ MuiTableCell: {
+ styleOverrides: {
+ root: {
+ padding: '6px 8px', // Kompakter als Standard
+ fontSize: '12px',
+ },
+ },
+ },
+ },
+});
+
+export default function App() {
+ return (
+
+
+
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/Bewerbsliste.tsx b/docs/06_Frontend/FIGMA/src/app/components/Bewerbsliste.tsx
new file mode 100644
index 00000000..d0ce73b2
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/Bewerbsliste.tsx
@@ -0,0 +1,139 @@
+import {useState} from 'react';
+import Box from '@mui/material/Box';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import IconButton from '@mui/material/IconButton';
+import Button from '@mui/material/Button';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import FilterListIcon from '@mui/icons-material/FilterList';
+
+// Mock-Daten fĂŒr Bewerbe
+const mockBewerbe = [
+ {tag: 'So', platz: 1, nr: '1', beginn: '08:00', nenn: 0, name: 'DressurreiterprĂŒfung Ratepass', klasse: 'A'},
+ {tag: 'So', platz: 1, nr: '2', beginn: '08:20', nenn: 0, name: 'DressurreiterprĂŒfung Katecnadel', klasse: 'L'},
+ {tag: 'So', platz: 1, nr: '3', beginn: '08:40', nenn: 0, name: 'DressurreiterprĂŒfung Idf. (Idf.)', klasse: 'M'},
+ {tag: 'So', platz: 1, nr: '4', beginn: '09:00', nenn: 0, name: 'DressurprĂŒfung Idf. (Idf.)', klasse: 'L'},
+ {tag: 'So', platz: 1, nr: '5', beginn: '09:20', nenn: 0, name: 'FĂŒhrzĂŒgelklasse', klasse: 'E'},
+ {tag: 'So', platz: 1, nr: '6', beginn: '09:40', nenn: 0, name: 'First Ridden', klasse: 'E'},
+ {tag: 'So', platz: 1, nr: '7', beginn: '10:00', nenn: 0, name: 'Pony DressurprĂŒfung Kl. A', klasse: 'A'},
+ {tag: 'So', platz: 1, nr: '8', beginn: '10:20', nenn: 0, name: 'DressurreiterprĂŒfung Kl. A', klasse: 'A'},
+ {tag: 'So', platz: 1, nr: '9', beginn: '10:40', nenn: 0, name: 'DressurprĂŒfung Kl. A', klasse: 'A'},
+ {tag: 'So', platz: 1, nr: '10', beginn: '11:00', nenn: 0, name: 'Pony DressurprĂŒfung Kl. A', klasse: 'A'},
+ {tag: 'So', platz: 1, nr: '11', beginn: '11:20', nenn: 0, name: 'DressurreiterprĂŒfung Kl. L', klasse: 'L'},
+ {tag: 'So', platz: 1, nr: '12', beginn: '11:40', nenn: 0, name: 'DressurprĂŒfung Kl. L', klasse: 'L'},
+];
+
+interface Props {
+ selectedPferd: any;
+ selectedReiter: any;
+ onNennung: (bewerb: any) => void;
+}
+
+export function Bewerbsliste({selectedPferd, selectedReiter, onNennung}: Props) {
+ const [selectedBewerb, setSelectedBewerb] = useState(null);
+
+ const handleBewerbDoppelklick = (bewerb: any) => {
+ if (selectedPferd && selectedReiter) {
+ onNennung(bewerb);
+ setSelectedBewerb(bewerb.nr);
+ }
+ };
+
+ const canNennen = selectedPferd && selectedReiter;
+
+ return (
+
+
+ BewerbsĂŒbersicht
+
+
+
+
+
+
+
+ Aktualisieren
+
+
+ {mockBewerbe.length} Bewerbe
+
+ }
+ sx={{fontSize: '10px', py: 0.25}}>
+ Filtern
+
+
+ 0 gefiltert
+
+
+
+
+
+
+
+ Tag
+ Pl.
+ Bewerb
+ Beginn
+ Nenn.
+ Bewerbsname
+
+
+
+ {mockBewerbe.map((bewerb, idx) => {
+ const isSelected = selectedBewerb === bewerb.nr;
+ const isClickable = canNennen;
+
+ return (
+ handleBewerbDoppelklick(bewerb)}
+ sx={{
+ cursor: isClickable ? 'pointer' : 'default',
+ '&:nth-of-type(odd)': {bgcolor: isSelected ? 'primary.100' : 'action.hover'},
+ '&.Mui-selected': {
+ bgcolor: 'primary.100',
+ '&:hover': {
+ bgcolor: 'primary.200',
+ },
+ },
+ opacity: isClickable ? 1 : 0.5,
+ }}
+ >
+ {bewerb.tag}
+ {bewerb.platz}
+ {bewerb.nr}
+ {bewerb.beginn}
+ {bewerb.nenn}
+ {bewerb.name}
+
+ );
+ })}
+
+
+
+
+ {!canNennen && (
+
+ Bitte wÀhlen Sie zuerst ein Pferd und einen Reiter aus
+
+ )}
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx b/docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx
new file mode 100644
index 00000000..1af2c8c8
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx
@@ -0,0 +1,129 @@
+import {useState} from 'react';
+import Box from '@mui/material/Box';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import Button from '@mui/material/Button';
+import IconButton from '@mui/material/IconButton';
+import RefreshIcon from '@mui/icons-material/Refresh';
+
+interface Props {
+ nennungen: any[];
+ selectedPferd: any;
+ selectedReiter: any;
+}
+
+export function NennungenTabelle({nennungen, selectedPferd, selectedReiter}: Props) {
+ const [tabValue, setTabValue] = useState(0);
+
+ // Filter basierend auf Tab
+ const getFilteredNennungen = () => {
+ if (!selectedPferd && !selectedReiter) return [];
+
+ switch (tabValue) {
+ case 0: // Reiter
+ return selectedReiter
+ ? nennungen.filter(n => n.reiter === selectedReiter.vorname + ' ' + selectedReiter.nachname)
+ : [];
+ case 1: // Pferd
+ return selectedPferd
+ ? nennungen.filter(n => n.pferd === selectedPferd.name)
+ : [];
+ case 2: // Bewerbe
+ return (selectedPferd && selectedReiter)
+ ? nennungen.filter(n =>
+ n.pferd === selectedPferd.name &&
+ n.reiter === selectedReiter.vorname + ' ' + selectedReiter.nachname
+ )
+ : [];
+ default:
+ return [];
+ }
+ };
+
+ const filteredNennungen = getFilteredNennungen();
+
+ return (
+
+ setTabValue(v)}
+ sx={{borderBottom: 1, borderColor: 'divider', minHeight: 32}}>
+
+
+
+
+
+
+
+
+
+
+ Aktualisieren
+
+
+ {filteredNennungen.length} Nennungen
+
+
+
+
+
+
+
+
+
+ Tag
+ Pl.
+ Bewerb
+ Bewerbsname
+ Bemerkung
+ Pferd
+
+
+
+ {filteredNennungen.length === 0 ? (
+
+
+ Keine Nennungen vorhanden
+
+
+ ) : (
+ filteredNennungen.map((nennung, idx) => (
+
+ {nennung.tag}
+ {nennung.platz}
+ {nennung.bewerbNr}
+ {nennung.bewerbName}
+ {nennung.startwunsch || '-'}
+ {nennung.pferd}
+
+ ))
+ )}
+
+
+
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx b/docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx
new file mode 100644
index 00000000..4399ec0f
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx
@@ -0,0 +1,115 @@
+import {useState} from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import {PferdReiterEingabe} from './PferdReiterEingabe';
+import {NennungenTabelle} from './NennungenTabelle';
+import {VerkaufBuchungen} from './VerkaufBuchungen';
+import {Bewerbsliste} from './Bewerbsliste';
+import ListIcon from '@mui/icons-material/List';
+import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
+import ReceiptIcon from '@mui/icons-material/Receipt';
+
+export function NennungsMaske() {
+ const [selectedPferd, setSelectedPferd] = useState(null);
+ const [selectedReiter, setSelectedReiter] = useState(null);
+ const [nennungen, setNennungen] = useState([]);
+
+ const handleNennung = (bewerb: any) => {
+ if (selectedPferd && selectedReiter) {
+ const neueNennung = {
+ tag: bewerb.tag,
+ platz: bewerb.platz,
+ bewerbNr: bewerb.nr,
+ bewerbName: bewerb.name,
+ beginn: bewerb.beginn,
+ pferd: selectedPferd.name,
+ reiter: `${selectedReiter.vorname} ${selectedReiter.nachname}`,
+ startwunsch: null,
+ };
+ setNennungen([...nennungen, neueNennung]);
+ }
+ };
+
+ return (
+
+ {/* Zeile 1 (50% Höhe): Pferd/Reiter Suche + Verkauf/Buchungen */}
+
+ {/* Links: Pferd & Reiter Eingabe (60%) */}
+
+
+
+
+ {/* Rechts: Verkauf/Buchungen (40%) */}
+
+
+
+
+
+ {/* Zeile 2 (5% Höhe): Navigation Buttons */}
+
+ }
+ sx={{minWidth: 130, fontSize: '11px', py: 0.5}}
+ >
+ Startliste
+
+ }
+ sx={{minWidth: 130, fontSize: '11px', py: 0.5}}
+ >
+ Ergebnisse
+
+ }
+ sx={{minWidth: 130, fontSize: '11px', py: 0.5}}
+ >
+ Abrechnung
+
+
+
+ {/* Zeile 3 (45% Höhe): NennungsĂŒbersicht + BewerbsĂŒbersicht */}
+
+ {/* Links: NennungsĂŒbersicht (60%) */}
+
+
+
+
+ {/* Rechts: BewerbsĂŒbersicht (40%) */}
+
+
+
+
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx b/docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx
new file mode 100644
index 00000000..42488218
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx
@@ -0,0 +1,558 @@
+import {useState, useEffect, useRef} from 'react';
+import Box from '@mui/material/Box';
+import TextField from '@mui/material/TextField';
+import Button from '@mui/material/Button';
+import Paper from '@mui/material/Paper';
+import Typography from '@mui/material/Typography';
+import List from '@mui/material/List';
+import ListItem from '@mui/material/ListItem';
+import ListItemButton from '@mui/material/ListItemButton';
+import ListItemText from '@mui/material/ListItemText';
+import Chip from '@mui/material/Chip';
+import Badge from '@mui/material/Badge';
+
+// Mock-Daten fĂŒr Pferde
+const mockPferde = [
+ {
+ id: 1,
+ kopfnr: 'A123',
+ name: "Obora's Donna",
+ rasse: 'Hannoveraner',
+ farbe: 'Brauner',
+ besitzer: 'Franz Huber',
+ stall: 'Box 12'
+ },
+ {
+ id: 2,
+ kopfnr: 'H597',
+ name: 'Weltmeyer',
+ rasse: 'Trakehner',
+ farbe: 'Schimmel',
+ besitzer: 'Maria Gruber',
+ stall: 'Box 8'
+ },
+ {
+ id: 3,
+ kopfnr: '9939',
+ name: 'Rubinstein',
+ rasse: 'Westfale',
+ farbe: 'Fuchs',
+ besitzer: 'Johann Maier',
+ stall: 'Box 15'
+ },
+ {
+ id: 4,
+ kopfnr: 'D456',
+ name: "Obora's Danilo",
+ rasse: 'Oldenburger',
+ farbe: 'Rappe',
+ besitzer: 'Anna Schmidt',
+ stall: 'Box 3'
+ },
+ {
+ id: 5,
+ kopfnr: '4568',
+ name: 'Domino',
+ rasse: 'Holsteiner',
+ farbe: 'Brauner',
+ besitzer: 'Thomas Bauer',
+ stall: 'Box 5'
+ },
+ {
+ id: 6,
+ kopfnr: 'B789',
+ name: "Obora's Dream",
+ rasse: 'Hannoveraner',
+ farbe: 'Fuchs',
+ besitzer: 'Franz Huber',
+ stall: 'Box 14'
+ },
+];
+
+// Mock-Daten fĂŒr Reiter
+const mockReiter = [
+ {
+ id: 1,
+ kopfnr: '201',
+ vorname: 'Anna',
+ nachname: 'Schneider',
+ verein: 'RV Wien',
+ lizenz: 'LNR-2024-4587',
+ lizenzGueltig: true,
+ kontoSaldo: 0,
+ geburtsjahr: 1995
+ },
+ {
+ id: 2,
+ kopfnr: '202',
+ vorname: 'Thomas',
+ nachname: 'Bauer',
+ verein: 'RC Graz',
+ lizenz: 'LNR-2023-1234',
+ lizenzGueltig: false,
+ kontoSaldo: -125.50,
+ geburtsjahr: 1998
+ },
+ {
+ id: 3,
+ kopfnr: '203',
+ vorname: 'Sophie',
+ nachname: 'Wagner',
+ verein: 'RFV Salzburg',
+ lizenz: 'LNR-2024-9876',
+ lizenzGueltig: true,
+ kontoSaldo: 50.00,
+ geburtsjahr: 1992
+ },
+ {
+ id: 4,
+ kopfnr: '204',
+ vorname: 'Michael',
+ nachname: 'MĂŒller',
+ verein: 'RC Innsbruck',
+ lizenz: 'LNR-2024-5555',
+ lizenzGueltig: true,
+ kontoSaldo: 0,
+ geburtsjahr: 2001
+ },
+ {
+ id: 5,
+ kopfnr: '205',
+ vorname: 'Franz',
+ nachname: 'Huber',
+ verein: 'RV Linz',
+ lizenz: 'LNR-2024-7777',
+ lizenzGueltig: true,
+ kontoSaldo: 0,
+ geburtsjahr: 2002
+ },
+ {
+ id: 6,
+ kopfnr: '206',
+ vorname: 'Franz',
+ nachname: 'Huber',
+ verein: 'RC Wien',
+ lizenz: 'LNR-2024-8888',
+ lizenzGueltig: true,
+ kontoSaldo: 0,
+ geburtsjahr: 1998
+ },
+];
+
+// Mock-Daten fĂŒr bereits getĂ€tigte Nennungen (IMS = Im System)
+const turnieNennungen = [
+ {reiterId: 2, pferdId: 5, bewerbNr: 3}, // Thomas Bauer mit Domino in Bewerb 3
+ {reiterId: 1, pferdId: 1, bewerbNr: 2}, // Anna Schneider mit Obora's Donna in Bewerb 2
+ {reiterId: 1, pferdId: 2, bewerbNr: 5}, // Anna Schneider mit Weltmeyer in Bewerb 5
+];
+
+interface Props {
+ selectedPferd: any;
+ setSelectedPferd: (pferd: any) => void;
+ selectedReiter: any;
+ setSelectedReiter: (reiter: any) => void;
+}
+
+export function PferdReiterEingabe({selectedPferd, setSelectedPferd, selectedReiter, setSelectedReiter}: Props) {
+ const [pferdSuche, setPferdSuche] = useState('');
+ const [reiterSuche, setReiterSuche] = useState('');
+ const [pferdErgebnisse, setPferdErgebnisse] = useState([]);
+ const [reiterErgebnisse, setReiterErgebnisse] = useState([]);
+ const [selectedPferdIndex, setSelectedPferdIndex] = useState(0);
+ const [selectedReiterIndex, setSelectedReiterIndex] = useState(0);
+
+ const pferdInputRef = useRef(null);
+ const reiterInputRef = useRef(null);
+
+ // Autofokus auf Pferd-Suchfeld beim Laden
+ useEffect(() => {
+ pferdInputRef.current?.focus();
+ }, []);
+
+ // Pferd-Suche
+ useEffect(() => {
+ if (pferdSuche.length > 0) {
+ // Normale Suche nach Eingabe
+ const results = mockPferde.filter(p =>
+ p.kopfnr.toLowerCase().includes(pferdSuche.toLowerCase()) ||
+ p.name.toLowerCase().includes(pferdSuche.toLowerCase())
+ );
+ setPferdErgebnisse(results);
+ setSelectedPferdIndex(0);
+ } else if (selectedReiter && !pferdSuche) {
+ // Cross-Reference: Zeige Pferde des ausgewÀhlten Reiters
+ const reiterPferde = turnieNennungen
+ .filter(n => n.reiterId === selectedReiter.id)
+ .map(n => mockPferde.find(p => p.id === n.pferdId))
+ .filter(Boolean);
+ setPferdErgebnisse(reiterPferde);
+ } else {
+ setPferdErgebnisse([]);
+ }
+ }, [pferdSuche, selectedReiter]);
+
+ // Reiter-Suche
+ useEffect(() => {
+ if (reiterSuche.length > 0) {
+ // Normale Suche nach Eingabe
+ const results = mockReiter.filter(r =>
+ r.vorname.toLowerCase().includes(reiterSuche.toLowerCase()) ||
+ r.nachname.toLowerCase().includes(reiterSuche.toLowerCase()) ||
+ `${r.vorname} ${r.nachname}`.toLowerCase().includes(reiterSuche.toLowerCase())
+ );
+ setReiterErgebnisse(results);
+ setSelectedReiterIndex(0);
+ } else if (selectedPferd && !reiterSuche) {
+ // Cross-Reference: Zeige Reiter des ausgewÀhlten Pferdes
+ const pferdReiter = turnieNennungen
+ .filter(n => n.pferdId === selectedPferd.id)
+ .map(n => mockReiter.find(r => r.id === n.reiterId))
+ .filter(Boolean);
+ setReiterErgebnisse(pferdReiter);
+ } else {
+ setReiterErgebnisse([]);
+ }
+ }, [reiterSuche, selectedPferd]);
+
+ // Hilfsfunktion: PrĂŒft ob Pferd im System ist (IMS)
+ const isPferdIMS = (pferdId: number) => {
+ return turnieNennungen.some(n => n.pferdId === pferdId);
+ };
+
+ // Hilfsfunktion: PrĂŒft ob Reiter im System ist (IMS)
+ const isReiterIMS = (reiterId: number) => {
+ return turnieNennungen.some(n => n.reiterId === reiterId);
+ };
+
+ // Pferd auswÀhlen
+ const handlePferdAuswahl = (pferd: any) => {
+ setSelectedPferd(pferd);
+
+ // Cross-Reference: Zeige Reiter dieses Pferdes
+ const pferdReiter = turnieNennungen
+ .filter(n => n.pferdId === pferd.id)
+ .map(n => mockReiter.find(r => r.id === n.reiterId))
+ .filter(Boolean);
+
+ if (pferdReiter.length > 0) {
+ setReiterErgebnisse(pferdReiter);
+ }
+
+ reiterInputRef.current?.focus();
+ };
+
+ // Reiter auswÀhlen
+ const handleReiterAuswahl = (reiter: any) => {
+ setSelectedReiter(reiter);
+
+ // Cross-Reference: Zeige Pferde dieses Reiters
+ const reiterPferde = turnieNennungen
+ .filter(n => n.reiterId === reiter.id)
+ .map(n => mockPferde.find(p => p.id === n.pferdId))
+ .filter(Boolean);
+
+ if (reiterPferde.length > 0) {
+ setPferdErgebnisse(reiterPferde);
+ }
+ };
+
+ // Keyboard Navigation fĂŒr Pferd
+ const handlePferdKeyDown = (e: React.KeyboardEvent) => {
+ if (pferdErgebnisse.length === 0) return;
+
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ setSelectedPferdIndex(prev => Math.min(prev + 1, pferdErgebnisse.length - 1));
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ setSelectedPferdIndex(prev => Math.max(prev - 1, 0));
+ } else if (e.key === 'Enter') {
+ e.preventDefault();
+ if (pferdErgebnisse[selectedPferdIndex]) {
+ handlePferdAuswahl(pferdErgebnisse[selectedPferdIndex]);
+ }
+ }
+ };
+
+ // Keyboard Navigation fĂŒr Reiter
+ const handleReiterKeyDown = (e: React.KeyboardEvent) => {
+ if (reiterErgebnisse.length === 0) return;
+
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ setSelectedReiterIndex(prev => Math.min(prev + 1, reiterErgebnisse.length - 1));
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ setSelectedReiterIndex(prev => Math.max(prev - 1, 0));
+ } else if (e.key === 'Enter') {
+ e.preventDefault();
+ if (reiterErgebnisse[selectedReiterIndex]) {
+ handleReiterAuswahl(reiterErgebnisse[selectedReiterIndex]);
+ }
+ }
+ };
+
+ const handlePferdLeeren = () => {
+ setPferdSuche('');
+ setSelectedPferd(null);
+ setPferdErgebnisse([]);
+ pferdInputRef.current?.focus();
+ };
+
+ const handleReiterLeeren = () => {
+ setReiterSuche('');
+ setSelectedReiter(null);
+ setReiterErgebnisse([]);
+ reiterInputRef.current?.focus();
+ };
+
+ return (
+
+ {/* Linke HĂ€lfte: Pferd */}
+
+ {/* Eingabefeld */}
+
+
+ Pferd:
+
+ setPferdSuche(e.target.value)}
+ onKeyDown={handlePferdKeyDown}
+ sx={{
+ flex: 1,
+ '& .MuiInputBase-input': {fontSize: '11px', py: 0.75},
+ }}
+ />
+
+
+
+
+ {/* Suchergebnisse - bleiben immer sichtbar */}
+
+
+ {pferdErgebnisse.length > 0 ? (
+ (pferdSuche ? pferdErgebnisse : pferdErgebnisse.slice(0, 4)).map((pferd, idx) => {
+ const istIMS = isPferdIMS(pferd.id);
+ return (
+
+ handlePferdAuswahl(pferd)}
+ sx={{py: 0.25, display: 'flex', gap: 1}}
+ >
+
+ {istIMS && (
+
+ )}
+
+
+ );
+ })
+ ) : (
+
+
+
+ )}
+
+
+
+ {/* Pferd Details - erscheint nach Auswahl */}
+ {selectedPferd && (
+
+
+ Pferd Details
+
+
+ Kopfnummer: {selectedPferd.kopfnr}
+
+
+ Name: {selectedPferd.name}
+
+
+ Rasse: {selectedPferd.rasse}
+
+
+ Farbe: {selectedPferd.farbe}
+
+
+ Besitzer: {selectedPferd.besitzer}
+
+
+ Stall: {selectedPferd.stall}
+
+
+ )}
+
+ {/* Buttons */}
+
+
+
+
+
+
+ {/* Rechte HĂ€lfte: Reiter */}
+
+ {/* Eingabefeld */}
+
+
+ Reiter:
+
+ setReiterSuche(e.target.value)}
+ onKeyDown={handleReiterKeyDown}
+ sx={{
+ flex: 1,
+ '& .MuiInputBase-input': {fontSize: '11px', py: 0.75},
+ }}
+ />
+
+
+
+
+ {/* Suchergebnisse - bleiben immer sichtbar */}
+
+
+ {reiterErgebnisse.length > 0 ? (
+ (reiterSuche ? reiterErgebnisse : reiterErgebnisse.slice(0, 4)).map((reiter, idx) => {
+ const istIMS = isReiterIMS(reiter.id);
+ return (
+
+ handleReiterAuswahl(reiter)}
+ sx={{py: 0.25, display: 'flex', gap: 1}}
+ >
+
+ {istIMS && (
+
+ )}
+
+
+ );
+ })
+ ) : (
+
+
+
+ )}
+
+
+
+ {/* Reiter Details - erscheint nach Auswahl */}
+ {selectedReiter && (
+
+
+ Reiter Details
+
+
+ Name: {selectedReiter.vorname} {selectedReiter.nachname}
+
+
+ Verein: {selectedReiter.verein}
+
+
+
+ Lizenz: {selectedReiter.lizenz}
+
+
+
+
+ Konto-Saldo: âŹ{selectedReiter.kontoSaldo.toFixed(2)}
+
+
+ )}
+
+ {/* Buttons */}
+
+
+
+
+
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx b/docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx
new file mode 100644
index 00000000..8fad1549
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx
@@ -0,0 +1,213 @@
+import {useState} from 'react';
+import Box from '@mui/material/Box';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import IconButton from '@mui/material/IconButton';
+import Button from '@mui/material/Button';
+import TextField from '@mui/material/TextField';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import AddIcon from '@mui/icons-material/Add';
+import RemoveIcon from '@mui/icons-material/Remove';
+
+// Mock-Daten fĂŒr Verkauf
+const mockVerkaufArtikel = [
+ {knr: '', text: 'Belastung', einzelpreis: 0, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Gutschrift', einzelpreis: 0, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Boxenpauschale', einzelpreis: 115.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Ansage', einzelpreis: 2.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'FĂŒttern', einzelpreis: 3.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Heu', einzelpreis: 13.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'SpÀne', einzelpreis: 15.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Stroh', einzelpreis: 5.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Strom', einzelpreis: 50.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Y-Nummer', einzelpreis: 35.00, menge: 0, gebucht: '0.00'},
+ {knr: '', text: 'Z-Nummer', einzelpreis: 10.00, menge: 0, gebucht: '0.00'},
+];
+
+interface Props {
+ selectedReiter: any;
+}
+
+export function VerkaufBuchungen({selectedReiter}: Props) {
+ const [tabValue, setTabValue] = useState(0);
+ const [verkaufMengen, setVerkaufMengen] = useState<{ [key: string]: number }>({});
+
+ const handleMengeChange = (text: string, delta: number) => {
+ setVerkaufMengen(prev => ({
+ ...prev,
+ [text]: Math.max(0, (prev[text] || 0) + delta),
+ }));
+ };
+
+ return (
+
+ setTabValue(v)}
+ sx={{borderBottom: 1, borderColor: 'divider', minHeight: 32}}>
+
+
+
+
+ {tabValue === 0 && (
+ <>
+
+
+
+
+
+ Aktualisieren
+
+
+ {mockVerkaufArtikel.length} Artikel
+
+
+
+
+
+
+
+
+
+ KNr
+ +
+ Menge
+ -
+ Buchungstext
+ Betrag
+ Gebucht
+
+
+
+ {mockVerkaufArtikel.map((artikel, idx) => {
+ const menge = verkaufMengen[artikel.text] || 0;
+ const betrag = menge * artikel.einzelpreis;
+ return (
+
+ {artikel.knr}
+
+ handleMengeChange(artikel.text, 1)}
+ sx={{width: 20, height: 20}}
+ >
+
+
+
+
+ setVerkaufMengen(prev => ({
+ ...prev,
+ [artikel.text]: Math.max(0, parseInt(e.target.value) || 0),
+ }))}
+ sx={{
+ width: 50,
+ '& .MuiInputBase-input': {
+ textAlign: 'center',
+ fontSize: '10px',
+ py: 0.25,
+ px: 0.5,
+ },
+ }}
+ />
+
+
+ handleMengeChange(artikel.text, -1)}
+ sx={{width: 20, height: 20}}
+ >
+
+
+
+ {artikel.text}
+ 0 ? 600 : 400, py: 0.5}} align="right">
+ {betrag.toFixed(2)}
+
+ {artikel.gebucht}
+
+ );
+ })}
+
+
+
+ >
+ )}
+
+ {tabValue === 1 && (
+ <>
+
+
+
+
+
+ Aktualisieren
+
+
+ 0 Buchungen
+
+
+
+
+
+
+
+
+ Kopfnr
+ Menge
+ Buchungstext
+ Soll
+ Haben
+
+
+
+
+
+ Keine Buchungen vorhanden
+
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx b/docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx
new file mode 100644
index 00000000..ff6e48f5
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx
@@ -0,0 +1,27 @@
+import React, {useState} from 'react'
+
+const ERROR_IMG_SRC =
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
+
+export function ImageWithFallback(props: React.ImgHTMLAttributes) {
+ const [didError, setDidError] = useState(false)
+
+ const handleError = () => {
+ setDidError(true)
+ }
+
+ const {src, alt, style, className, ...rest} = props
+
+ return didError ? (
+
+
+

+
+
+ ) : (
+
+ )
+}
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx
new file mode 100644
index 00000000..19e8905a
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+import {ChevronDownIcon} from "lucide-react";
+
+import {cn} from "./utils";
+
+function Accordion({
+ ...props
+ }: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export {Accordion, AccordionItem, AccordionTrigger, AccordionContent};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..d49018d1
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import {cn} from "./utils";
+import {buttonVariants} from "./button";
+
+function AlertDialog({
+ ...props
+ }: React.ComponentProps) {
+ return ;
+}
+
+function AlertDialogTrigger({
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogPortal({
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+ }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+ }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx
new file mode 100644
index 00000000..6424cc40
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import {cva, type VariantProps} from "class-variance-authority";
+
+import {cn} from "./utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+ }: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+ }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {Alert, AlertTitle, AlertDescription};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx
new file mode 100644
index 00000000..cd697698
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+function AspectRatio({
+ ...props
+ }: React.ComponentProps) {
+ return ;
+}
+
+export {AspectRatio};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx
new file mode 100644
index 00000000..cac4642f
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+
+import {cn} from "./utils";
+
+function Avatar({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+ }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {Avatar, AvatarImage, AvatarFallback};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx
new file mode 100644
index 00000000..07ffa941
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import {Slot} from "@radix-ui/react-slot";
+import {cva, type VariantProps} from "class-variance-authority";
+
+import {cn} from "./utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+ }: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return (
+
+ );
+}
+
+export {Badge, badgeVariants};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..6916fcd1
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react";
+import {Slot} from "@radix-ui/react-slot";
+import {ChevronRight, MoreHorizontal} from "lucide-react";
+
+import {cn} from "./utils";
+
+function Breadcrumb({...props}: React.ComponentProps<"nav">) {
+ return ;
+}
+
+function BreadcrumbList({className, ...props}: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({className, ...props}: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+ }: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({className, ...props}: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+ }: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+ }: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx
new file mode 100644
index 00000000..3e579e71
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx
@@ -0,0 +1,58 @@
+import * as React from "react";
+import {Slot} from "@radix-ui/react-slot";
+import {cva, type VariantProps} from "class-variance-authority";
+
+import {cn} from "./utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9 rounded-md",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+ }: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+
+ );
+}
+
+export {Button, buttonVariants};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx
new file mode 100644
index 00000000..c7228459
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import * as React from "react";
+import {ChevronLeft, ChevronRight} from "lucide-react";
+import {DayPicker} from "react-day-picker";
+
+import {cn} from "./utils";
+import {buttonVariants} from "./button";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+ }: React.ComponentProps) {
+ return (
+ .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+ : "[&:has([aria-selected])]:rounded-md",
+ ),
+ day: cn(
+ buttonVariants({variant: "ghost"}),
+ "size-8 p-0 font-normal aria-selected:opacity-100",
+ ),
+ day_range_start:
+ "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_range_end:
+ "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
+ day_selected:
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside:
+ "day-outside text-muted-foreground aria-selected:text-muted-foreground",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_range_middle:
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
+ day_hidden: "invisible",
+ ...classNames,
+ }}
+ components={{
+ IconLeft: ({className, ...props}) => (
+
+ ),
+ IconRight: ({className, ...props}) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+
+export {Calendar};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx
new file mode 100644
index 00000000..a5ae2f37
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import {cn} from "./utils";
+
+function Card({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({className, ...props}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx
new file mode 100644
index 00000000..cd39726b
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx
@@ -0,0 +1,241 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import {ArrowLeft, ArrowRight} from "lucide-react";
+
+import {cn} from "./utils";
+import {Button} from "./button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ }: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins,
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext],
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({className, ...props}: React.ComponentProps<"div">) {
+ const {carouselRef, orientation} = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({className, ...props}: React.ComponentProps<"div">) {
+ const {orientation} = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+ }: React.ComponentProps) {
+ const {orientation, scrollPrev, canScrollPrev} = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+ }: React.ComponentProps) {
+ const {orientation, scrollNext, canScrollNext} = useCarousel();
+
+ return (
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx b/docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx
new file mode 100644
index 00000000..079c6bd5
--- /dev/null
+++ b/docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx
@@ -0,0 +1,353 @@
+"use client";
+
+import * as React from "react";
+import * as RechartsPrimitive from "recharts";
+
+import {cn} from "./utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = {light: "", dark: ".dark"} as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+} & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+ }: React.ComponentProps<"div"> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"];
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({id, config}: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+