From 439551951b77ed5cea3f1441c9802797538fffea Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Sat, 21 Mar 2026 13:40:37 +0100 Subject: [PATCH] feat(ui): add reusable components for FIGMA-based UI system - Implemented new reusable components including Label, Input, InputOTP, HoverCard, Popover, Pagination, NavigationMenu, Menubar, ScrollArea, Resizable, RadioGroup, and Progress under `docs/06_Frontend/FIGMA/src/app/components/ui/`. - Enhanced structural organization to improve scalability and maintainability. - Updated `settings.gradle.kts` to include the new module `frontend:features:nennung-feature`. Signed-off-by: Stefan Mogeritsch --- .../Logs/2026-03-21_Frontend_NennungsMaske.md | 81 ++ docs/06_Frontend/FIGMA/ATTRIBUTIONS.md | 5 + docs/06_Frontend/FIGMA/README.md | 544 ++++++++++++ docs/06_Frontend/FIGMA/guidelines.zip | Bin 0 -> 2683 bytes .../FIGMA/guidelines/Guidelines.md | 61 ++ docs/06_Frontend/FIGMA/package.json | 89 ++ docs/06_Frontend/FIGMA/postcss.config.mjs | 15 + docs/06_Frontend/FIGMA/src.zip | Bin 0 -> 221973 bytes docs/06_Frontend/FIGMA/src/app/App.tsx | 74 ++ .../FIGMA/src/app/components/Bewerbsliste.tsx | 139 +++ .../src/app/components/NennungenTabelle.tsx | 129 +++ .../src/app/components/NennungsMaske.tsx | 115 +++ .../src/app/components/PferdReiterEingabe.tsx | 558 ++++++++++++ .../src/app/components/VerkaufBuchungen.tsx | 213 +++++ .../components/figma/ImageWithFallback.tsx | 27 + .../FIGMA/src/app/components/ui/accordion.tsx | 67 ++ .../src/app/components/ui/alert-dialog.tsx | 157 ++++ .../FIGMA/src/app/components/ui/alert.tsx | 66 ++ .../src/app/components/ui/aspect-ratio.tsx | 11 + .../FIGMA/src/app/components/ui/avatar.tsx | 53 ++ .../FIGMA/src/app/components/ui/badge.tsx | 46 + .../src/app/components/ui/breadcrumb.tsx | 109 +++ .../FIGMA/src/app/components/ui/button.tsx | 58 ++ .../FIGMA/src/app/components/ui/calendar.tsx | 75 ++ .../FIGMA/src/app/components/ui/card.tsx | 92 ++ .../FIGMA/src/app/components/ui/carousel.tsx | 241 ++++++ .../FIGMA/src/app/components/ui/chart.tsx | 353 ++++++++ .../FIGMA/src/app/components/ui/checkbox.tsx | 32 + .../src/app/components/ui/collapsible.tsx | 33 + .../FIGMA/src/app/components/ui/command.tsx | 178 ++++ .../src/app/components/ui/context-menu.tsx | 252 ++++++ .../FIGMA/src/app/components/ui/dialog.tsx | 136 +++ .../FIGMA/src/app/components/ui/drawer.tsx | 133 +++ .../src/app/components/ui/dropdown-menu.tsx | 257 ++++++ .../FIGMA/src/app/components/ui/form.tsx | 168 ++++ .../src/app/components/ui/hover-card.tsx | 44 + .../FIGMA/src/app/components/ui/input-otp.tsx | 77 ++ .../FIGMA/src/app/components/ui/input.tsx | 21 + .../FIGMA/src/app/components/ui/label.tsx | 24 + .../FIGMA/src/app/components/ui/menubar.tsx | 276 ++++++ .../src/app/components/ui/navigation-menu.tsx | 168 ++++ .../src/app/components/ui/pagination.tsx | 127 +++ .../FIGMA/src/app/components/ui/popover.tsx | 48 ++ .../FIGMA/src/app/components/ui/progress.tsx | 31 + .../src/app/components/ui/radio-group.tsx | 45 + .../FIGMA/src/app/components/ui/resizable.tsx | 56 ++ .../src/app/components/ui/scroll-area.tsx | 58 ++ .../FIGMA/src/app/components/ui/select.tsx | 189 ++++ .../FIGMA/src/app/components/ui/separator.tsx | 28 + .../FIGMA/src/app/components/ui/sheet.tsx | 140 +++ .../FIGMA/src/app/components/ui/sidebar.tsx | 726 ++++++++++++++++ .../FIGMA/src/app/components/ui/skeleton.tsx | 13 + .../FIGMA/src/app/components/ui/slider.tsx | 63 ++ .../FIGMA/src/app/components/ui/sonner.tsx | 25 + .../FIGMA/src/app/components/ui/switch.tsx | 31 + .../FIGMA/src/app/components/ui/table.tsx | 116 +++ .../FIGMA/src/app/components/ui/tabs.tsx | 66 ++ .../FIGMA/src/app/components/ui/textarea.tsx | 18 + .../src/app/components/ui/toggle-group.tsx | 73 ++ .../FIGMA/src/app/components/ui/toggle.tsx | 47 + .../FIGMA/src/app/components/ui/tooltip.tsx | 62 ++ .../FIGMA/src/app/components/ui/use-mobile.ts | 21 + .../FIGMA/src/app/components/ui/utils.ts | 6 + .../meldestelle-desktop-screens.md | 163 ++++ .../pasted_text/nennungs-maske-design.md | 170 ++++ docs/06_Frontend/FIGMA/src/styles/fonts.css | 0 docs/06_Frontend/FIGMA/src/styles/index.css | 3 + .../06_Frontend/FIGMA/src/styles/tailwind.css | 5 + docs/06_Frontend/FIGMA/src/styles/theme.css | 180 ++++ docs/06_Frontend/FIGMA/vite.config.ts | 22 + ...top-Nennmaske-Entwurf_2026-03-21_11-53.png | Bin 0 -> 77506 bytes .../frontend/core/navigation/AppScreen.kt | 3 +- .../features/nennung-feature/build.gradle.kts | 37 + .../nennung/feature/di/NennungModule.kt | 9 + .../nennung/feature/domain/NennungModels.kt | 112 +++ .../feature/presentation/NennungViewModel.kt | 168 ++++ .../feature/presentation/NennungsMaske.kt | 812 ++++++++++++++++++ .../meldestelle-portal/build.gradle.kts | 1 + .../src/commonMain/kotlin/MainApp.kt | 14 +- .../commonMain/kotlin/NennungScreenContent.kt | 4 + .../src/jsMain/kotlin/NennungScreenContent.kt | 8 + .../jvmMain/kotlin/NennungScreenContent.kt | 10 + .../src/jvmMain/kotlin/main.kt | 13 +- settings.gradle.kts | 1 + 84 files changed, 8898 insertions(+), 3 deletions(-) create mode 100644 docs/04_Agents/Logs/2026-03-21_Frontend_NennungsMaske.md create mode 100644 docs/06_Frontend/FIGMA/ATTRIBUTIONS.md create mode 100644 docs/06_Frontend/FIGMA/README.md create mode 100644 docs/06_Frontend/FIGMA/guidelines.zip create mode 100644 docs/06_Frontend/FIGMA/guidelines/Guidelines.md create mode 100644 docs/06_Frontend/FIGMA/package.json create mode 100644 docs/06_Frontend/FIGMA/postcss.config.mjs create mode 100644 docs/06_Frontend/FIGMA/src.zip create mode 100644 docs/06_Frontend/FIGMA/src/app/App.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/Bewerbsliste.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/checkbox.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/collapsible.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/command.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/context-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/dialog.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/drawer.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/dropdown-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/form.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/hover-card.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/input-otp.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/input.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/label.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/menubar.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/navigation-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/pagination.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/popover.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/progress.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/radio-group.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/resizable.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/scroll-area.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/select.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/separator.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/sheet.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/sidebar.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/skeleton.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/slider.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/sonner.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/switch.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/table.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/tabs.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/textarea.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/toggle-group.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/toggle.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/tooltip.tsx create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/use-mobile.ts create mode 100644 docs/06_Frontend/FIGMA/src/app/components/ui/utils.ts create mode 100644 docs/06_Frontend/FIGMA/src/imports/pasted_text/meldestelle-desktop-screens.md create mode 100644 docs/06_Frontend/FIGMA/src/imports/pasted_text/nennungs-maske-design.md create mode 100644 docs/06_Frontend/FIGMA/src/styles/fonts.css create mode 100644 docs/06_Frontend/FIGMA/src/styles/index.css create mode 100644 docs/06_Frontend/FIGMA/src/styles/tailwind.css create mode 100644 docs/06_Frontend/FIGMA/src/styles/theme.css create mode 100644 docs/06_Frontend/FIGMA/vite.config.ts create mode 100644 docs/06_Frontend/Screenshots/Desktop-Nennmaske-Entwurf_2026-03-21_11-53.png create mode 100644 frontend/features/nennung-feature/build.gradle.kts create mode 100644 frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/di/NennungModule.kt create mode 100644 frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/domain/NennungModels.kt create mode 100644 frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungViewModel.kt create mode 100644 frontend/features/nennung-feature/src/commonMain/kotlin/at/mocode/nennung/feature/presentation/NennungsMaske.kt create mode 100644 frontend/shells/meldestelle-portal/src/commonMain/kotlin/NennungScreenContent.kt create mode 100644 frontend/shells/meldestelle-portal/src/jsMain/kotlin/NennungScreenContent.kt create mode 100644 frontend/shells/meldestelle-portal/src/jvmMain/kotlin/NennungScreenContent.kt 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 0000000000000000000000000000000000000000..deb4a504af720e4f4df21c34dd0627de468b91c4 GIT binary patch literal 2683 zcmai0O>f*b5Ow+mlt6F&0aN%8V6k2_ZB9;6z)6t6by6F#9Uwq2lr&lqrbvOLcD+5N zm;Q?Ozx21{Z}iZf-q9kY4-1 zRC<^A8WW>?m`K~T-b5-mB@PQZPI*hrsry_%)2mmn7jzLexk}`c>u-HTgtqe`NudJ_ zlCW&2)IPmr$3fd$uqbupHnbeaw<|JIBc`{|df~$a%Ary-I(2qKl9Mi?w>=Q-d^A!% zz@l}-4ttjzqYj$|opuSh2MQcJ0AyBJ2a?uoEyWc2GWCHgvKl!ju+jiI30_;iF8uYt zXfPEO8>+b0y-mpB4W7z`u%(9%^gz+l3&~f^-7s8Q)((?TWF%`RnqNU|Y&ABQEV@2H zeYWF3b?(`EJ#%-eh441dU&3${^X; z_}*63>MI_J802HUzancenk6G?Xt#M7QTna$Ah4=3;wWS@CsSvMMz)Y4WV|z3FcCn9 zI;3}6Py%S@d6DQkaE%spC9Aya&T{O%1!In2SM1`PRyr76Of9#au~2vAaw$roLJOx5 z{DE*Fz4hpEcMWo$OA7)WbPSjoV7Y09De2)oeR%)JPrai@-+p-iCzf8I{R^LxZ;_U_ zg$`2yFB0VyXl=9z4nSX&1E|<6ns~S4T3rZhZ$)6ekZfxLCD*8SrAIS zo9xC$Pi0_%EA|ELIF6Ws>_V~Q($uDe1d=v3-eE)S@6*#<7d(*1$1T5bBIJfnsB{bM z!!!&WDhMDz5$cjr;Y3V)M@l+OTjFcC}KboKm>AnWtPZYq5dFk9(=!SGVr?V ztSN!LciHwvcaO9(u}2OyjI7Y*Rqj~nX#}pJh)P4T2<3@gBhP}ZSQXmYh?>!-`zZJ_ z>Lsh8>>Iv{JiE%)v~|xZ;mL)Nsn}?)39UK@@?|4-BZ3qfS`(xA{v05>+JsgFR5soq zv$;au#-UFKU7;>1XI$4Khc)%hRMPVXPC>WbNC5hV!(+>3c{| zy{+bArxt+ zdCow*`gobz0$B$JpkP!L`%aF^ZJ)>cYCIjT1ozaJD}xQ9%-Er4(GQ}{CFJa?or<5y zg`w-RT&>47LT)U23)BFDFaa0EF>`Z-k0L~k7mQ3lUZUWSw%EA`OhN7;@B(*^gY1x* zJ?CL+PkPi;(QKV=ly@K7yfgrv~&L_h4s?lQh6;8_^)!E5| jPrly!|GQ$oMBe<_UjJ}&JUjXP3wiSZKTq&~mZSU&g{z>} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1aafc691ee8f31ad6df1f40d6334e15f1b5df571 GIT binary patch literal 221973 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�XuFmw0>cXkNSEJ&BCQp%b{m2OA>%y)S(-z5h6 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 Q8c^)r8tpzt)X#W*_tJiDp5YfTEAV1pqTu*{V* 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 bjQ_lK{cFLnhK3gKzcbjM{jXclpojh+$}thW literal 0 HcmV?d00001 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 + + + + 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 */} + + + + + + + {/* 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 ? ( +
+
+ Error loading image +
+
+ ) : ( + {alt} + ) +} 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