# Turnierverwaltungs-Anwendung - Frontend Prototyp ## Projektübersicht Dies ist ein professioneller Prototyp einer Turnierverwaltungs-Anwendung für den österreichischen Pferdesportverband ( ÖPS). Die Anwendung ist als **Desktop-First-Anwendung** konzipiert und bietet eine kompakte, tastaturoptimierte Benutzeroberfläche zur Verwaltung von Veranstaltungen, Turnieren und Bewerben im Pferdesport. ### Hauptmerkmale - **Desktop-optimierte UI**: Fokus auf kompakte Layouts und effiziente Datenerfassung - **Hierarchische Datenstruktur**: Veranstalter (Verein) → Veranstaltungen → Turniere → Bewerbe - **Veranstalter-Verwaltung**: Admin legt Veranstalter an → Veranstalter erhält Login → Veranstalter verwaltet eigene Veranstaltungen - **Material Design 3**: Moderne UI mit Primärfarbe Indigo (#3F51B5) - **Tastaturoptimiert**: Effiziente Navigation und Dateneingabe - **OETO-Ausschreibungs-Standard**: Tab-Struktur folgt österreichischen Richtlinien --- ## Technologie-Stack ### Core Technologies - **React 18** - UI Framework - **TypeScript** - Type-safe JavaScript - **React Router** (Data Mode) - Client-side Routing - **Material-UI (MUI) v6** - Component Library - **Vite** - Build Tool & Development Server ### Styling - **Material-UI System** - Sx Props für Styling - **Tailwind CSS v4** - Utility Classes (sekundär) - **Material Design 3** - Design Language ### Package Manager - **pnpm** - Fast, disk space efficient package manager --- ## Projektstruktur ``` / ├── src/ │ ├── app/ │ │ ├── components/ │ │ │ ├── veranstaltung/ │ │ │ │ ├── StammdatenTab.tsx # A-Satz / Stammdaten │ │ │ │ ├── OrganisationTab.tsx # Funktionäre & Plätze │ │ │ │ ├── PreislisteTab.tsx # Preisliste │ │ │ │ └── UebersichtTab.tsx # Transfer/Übersicht │ │ │ ├── turnier/ │ │ │ │ └── BewerbeTab.tsx # Bewerbe-Verwaltung (Hauptseite) │ │ │ ├── AdminDrawer.tsx # Haupt-Navigation │ │ │ ├── VeranstaltungAnsicht.tsx # Veranstaltungs-View │ │ │ └── TurnierAnsicht.tsx # Turnier-View │ │ ├── routes.tsx # React Router Konfiguration │ │ └── App.tsx # Root Component │ ├── styles/ │ │ ├── theme.css # CSS Variables & Theme │ │ └── fonts.css # Font Imports │ └── main.tsx # Entry Point ├── package.json └── README.md ``` --- ## Installation & Setup ### Voraussetzungen - **Node.js** >= 18.x - **pnpm** >= 8.x (empfohlen) oder npm ### Installation ```bash # Repository klonen git clone cd turnierverwaltung # Dependencies installieren pnpm install # Development Server starten pnpm dev # Build für Production pnpm build # Preview Production Build pnpm preview ``` ### Verfügbare Scripts ```json { "dev": "vite", // Development Server auf http://localhost:5173 "build": "vite build", // Production Build "preview": "vite preview" // Preview Production Build } ``` --- ## Architektur & Konzepte ### 1. Routing-System (React Router Data Mode) Die Anwendung verwendet React Router's Data Mode Pattern mit einer klar definierten Route-Hierarchie: ```typescript // src/app/routes.tsx const router = createBrowserRouter([ { path: "/", Component: Root, children: [ // Neue Veranstaltung { path: "veranstaltung/neu", Component: VeranstaltungAnsicht }, // Bestehende Veranstaltung { path: "veranstaltung/:id", Component: VeranstaltungAnsicht }, // Neues Turnier in Veranstaltung { path: "veranstaltung/:veranstaltungId/turnier/neu", Component: TurnierAnsicht }, // Bestehendes Turnier { path: "veranstaltung/:veranstaltungId/turnier/:nr", Component: TurnierAnsicht }, // 404 Fallback { path: "*", Component: NotFound } ] } ]); ``` **Wichtig**: Verwenden Sie immer das `react-router` Package (nicht `react-router-dom`), da die Anwendung in einer speziellen Umgebung läuft. --- ### 2. Navigation & Benutzerfluss #### Hauptnavigation: AdminDrawer Die Anwendung verwendet eine **Drawer-Navigation** (links) mit folgenden Bereichen: ``` Admin - Verwaltung ├── Veranstaltungen │ ├── Neue Veranstaltung → /veranstaltung/neu │ └── [Liste Veranstaltungen] → /veranstaltung/:id │ └── Turniere │ ├── Neues Turnier → /veranstaltung/:id/turnier/neu │ └── [Turnier-Liste] → /veranstaltung/:id/turnier/:nr └── ... ``` #### Login-System - **Demo Credentials**: - Username: `admin` - Passwort: `Admin#1234` - Login-State wird im `localStorage` gespeichert - Keine Backend-Integration im Prototyp --- ### 3. Tab-Struktur (OETO-Standard) #### Veranstaltungs-Tabs (Neue Veranstaltung) Bei einer **neuen Veranstaltung** sind alle 5 Tabs sichtbar: 1. **Veranstaltung - Übersicht** (ehemals "Transfer") 2. **Stammdaten** (A-Satz) ← Standardtab beim Erstellen 3. **Organisation** (Funktionäre + Plätze) 4. **Bewerbe** (wird versteckt, da turnierspezifisch) 5. **Preisliste** #### Veranstaltungs-Tabs (Bestehende Veranstaltung) Bei einer **bestehenden Veranstaltung** wird nur der Übersicht-Tab angezeigt: 1. **Veranstaltung - Übersicht** **Grund**: Turnierspezifische Daten (Stammdaten, Organisation, Bewerbe, Preisliste) werden nur auf Turnier-Ebene bearbeitet. #### Turnier-Tabs Wenn ein Turnier geöffnet wird, sind alle 5 Tabs sichtbar: 1. **Veranstaltung - Übersicht** (Read-only, zeigt Veranstaltungs-Info) 2. **Stammdaten** (A-Satz) 3. **Organisation** (Funktionäre + Plätze) 4. **Bewerbe** ⭐ **Wichtigste Seite der Anwendung** 5. **Preisliste** --- ### 4. Bewerbe-Tab - Die Hauptseite Der **Bewerbe-Tab** ist die zentrale Konfigurationsseite des gesamten Systems. Er ist in 3 Bereiche aufgeteilt: ``` ┌─────────────┬───────────────────────┬───────────────────────┐ │ Aktionen │ Bewerbs-Übersicht │ Bewerb-Konfiguration │ │ (150px) │ (50%) │ (50%) │ └─────────────┴───────────────────────┴──────────────────────┘ ``` #### Links: Aktionen (150px Sidebar) Buttons für Bewerbs-Management: - **Änderungen Speichern** / **Änderungen Rückgängig** - **Bewerb Einfügen** / **Bewerb Löschen** / **Bewerb Teilen** - **Bewerb nach oben/unten verschieben** - **Startliste Bearbeiten** / **Startliste Drucken** - **Ergebnisliste Bearbeiten** / **Ergebnisliste Drucken** #### Mitte: Bewerbs-Übersicht (50%) **Toolbar**: - Button: Aktualisieren - Button: X Bewerbe (zeigt Anzahl) - Button: Filtern **Tabelle** mit folgenden Spalten: - **Tag** (Datum) - **Platz** (Platz-Nummer) - **Bewerb** (Bewerb-Nummer) - **Beginn** (Uhrzeit) - **Ende** (Uhrzeit) - **Bewerbname** (mehrzeilig möglich) - **ZNS** (Zusätzliche Nennung Startnummer) - **Nennungen** (Anzahl Anmeldungen) **Features**: - Klickbare Zeilen zur Auswahl - Hervorhebung: Bewerbe 5 & 6 haben gelben Hintergrund (`warning.50`) - Selected State: Blau/Gelb-Orange je nach Bewerb #### Rechts: Bewerb-Konfiguration (50%) **4 Tabs** zur detaillierten Bewerbs-Konfiguration: ##### Tab 1: Bewerb (Grunddaten) - Nummer - Abteilung - Typ (z.B. "Dressur") - Name (z.B. "Dressurreiterprüfung") - Bezeichnung (z.B. "Dressurreiterprüfung Reiterpass") - Kategorie (Dropdown) - Klasse (Dropdown) - Lizenz (Dropdown) - Maximal (Pferde je Reiter) - Pferdealter (Dropdown) - Zeile 1, 2, 3 (Zusatzinformationen wie "Pony Einsteiger Cup OÖ") - Logo Bewerb (Dateipfad mit "..."-Button) ##### Tab 2: Bewertung - Prüfung (z.B. "Dressurreiterprüfung") - Richtverfahren (z.B. "A") - Para-Grade - Richteranzahl - Aufgabe (z.B. "Aufgabe R") - Aufgabennummer - Maximalpunkte (Punkte je Richter) **Richter-Liste**: - Position (z.B. "C") - Name (z.B. "Schuster Alexandra") - Aktiv (Checkbox) ##### Tab 3: Geldpreise **Section: Geldpreis** - Checkbox: Geldpreis - Startgeld (z.B. "15,00") - Auszahlung (Dropdown: fortführend, 1/3, 1/4, 1/5) **Section: Geldpreis für Kadererreiter** - Checkbox: Geldpreis für Kadererreiter - Startgeld für Kadererreiter (z.B. "15,00") **Geldpreisvorlage wählen** (Dropdown) **Tabelle: Geldpreise** - Spalten: Nummer, Geldpreis - Zeigt Anzahl der Geldpreise ##### Tab 4: Ort/Zeit - Tag (Dropdown: Datum) - Beginnzeit (Dropdown: "fix um", "nicht vor", "ca.") - Zeit (Textfeld mit Format hh:mm) - Reitdauer (Textfeld mit Format mm:ss) - Umbau (Textfeld in Minuten) - Besichtigung (Textfeld in Minuten) - Stechen (Textfeld in Minuten) - Platz (Dropdown: "Vorderer Turnierplatz", "Hauptplatz", etc.) --- ## Datenstrukturen ### Bewerb Interface ```typescript interface Bewerb { id: number; tag: string; // Tabellen-Datum platz: number; // Platz-Nummer bewerb: number; // Bewerb-Nummer beginn: string; // Beginn-Zeit ende: string; // End-Zeit bewerbname: string; // Mehrzeiliger Name zns: number; // ZNS nennungen: number; // Anzahl Nennungen // Tab 1: Bewerb nummer: string; abteilung: string; typ: string; name: string; bezeichnung: string; kategorie: string; klasse: string; lizenz: string; maximal: string; pferdealter: string; zeile1: string; zeile2: string; zeile3: string; logoBewerbPfad: string; // Tab 2: Bewertung prufung: string; richtverfahren: string; paraGrade: string; richteranzahl: number; aufgabe: string; aufgabennr: string; maximalPunkte: string; richter: { position: string; name: string; aktiv: boolean; }[]; // Tab 3: Geldpreise geldpreisAktiv: boolean; startgeld: string; auszahlung: string; geldpreisKadererreiterAktiv: boolean; startgeldKadererreiter: string; geldpreisvorlage: string; geldpreise: { nummer: string; betrag: string; }[]; // Tab 4: Ort/Zeit tagDatum: string; beginnzeit: string; beginnZeit: string; reitdauer: string; umbau: string; besichtigung: string; stechen: string; platzName: string; } ``` ### Veranstaltung Interface ```typescript interface Veranstaltung { id: string; name: string; von: string; // Datum von bis: string; // Datum bis ort: string; status: string; turniere: Turnier[]; } ``` ### Turnier Interface ```typescript interface Turnier { nr: number; name: string; datum: string; status: string; bewerbe: Bewerb[]; } ``` --- ## Design-System ### Farbschema (Material Design 3) **Primärfarbe**: Indigo (#3F51B5) ```css /* Theme Colors (src/styles/theme.css) */ --primary-color: #3F51B5; --primary-light: #757DE8; --primary-dark: #002984; /* Semantic Colors */ --background-default: #FAFAFA; --background-paper: #FFFFFF; --text-primary: rgba(0, 0, 0, 0.87); --text-secondary: rgba(0, 0, 0, 0.60); --divider: rgba(0, 0, 0, 0.12); /* Status Colors */ --success-color: #4CAF50; --warning-color: #FF9800; --error-color: #F44336; --info-color: #2196F3; ``` ### Typografie - **Body Text**: 10px - 11px (sehr kompakt für Desktop) - **Labels**: 10px, 600 Font Weight - **Section Headers**: 11px - 13px, 600 Font Weight - **Schriftart**: System Fonts (Roboto via MUI) ### Spacing & Layout - **Kompakte Abstände**: 1-2 (8px - 16px) - **Form-Felder**: - Höhe: `small` size - Padding: `py: 0.5` (4px) - Font: 10px - **Sidebar Width**: 150px (Aktionen-Sidebar im Bewerbe-Tab) - **Drawer Width**: 280px (Haupt-Navigation) ### Component-Sizing ```typescript // Standardgrößen size="small" // Buttons, TextFields, Selects sx={{ fontSize: '10px' }} // Text sx={{ py: 0.5 }} // Input Padding sx={{ gap: 1 }} // 8px Abstand sx={{ gap: 1.5 }} // 12px Abstand ``` --- ## MUI Theme Konfiguration Die Anwendung verwendet MUI's Default Theme mit angepasster Primärfarbe: ```typescript // src/main.tsx import { createTheme, ThemeProvider } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { main: '#3F51B5', // Indigo }, }, components: { MuiButton: { styleOverrides: { root: { textTransform: 'none', // Keine Großbuchstaben }, }, }, }, }); ``` --- ## State Management ### Aktuelle Implementierung (Prototyp) Der Prototyp verwendet **React Local State** mit `useState`: ```typescript // Beispiel: BewerbeTab.tsx const [bewerbe, setBewerbe] = useState(mockBewerbe); const [selectedBewerbId, setSelectedBewerbId] = useState(1); const [detailTab, setDetailTab] = useState(0); ``` ### Empfehlung für Production Für die Production-Version empfehlen wir: 1. **React Context API** für globalen State (Login, aktuelle Veranstaltung/Turnier) 2. **Zustand** oder **Redux Toolkit** für komplexes State Management 3. **React Query** für Server-State und Caching 4. **localStorage/sessionStorage** für Persistenz Beispiel mit React Context: ```typescript // context/VeranstaltungContext.tsx const VeranstaltungContext = createContext(null); export function VeranstaltungProvider({ children }: { children: ReactNode }) { const [activeVeranstaltung, setActiveVeranstaltung] = useState(null); const [activeTurnier, setActiveTurnier] = useState(null); return ( {children} ); } ``` --- ## Backend-Integration (TODO) ### API Endpunkte (geplant) ```typescript // Veranstaltungen GET /api/veranstaltungen GET /api/veranstaltungen/:id POST /api/veranstaltungen PUT /api/veranstaltungen/:id DELETE /api/veranstaltungen/:id // Turniere GET /api/veranstaltungen/:veranstaltungId/turniere GET /api/veranstaltungen/:veranstaltungId/turniere/:nr POST /api/veranstaltungen/:veranstaltungId/turniere PUT /api/veranstaltungen/:veranstaltungId/turniere/:nr DELETE /api/veranstaltungen/:veranstaltungId/turniere/:nr // Bewerbe GET /api/turniere/:turnierId/bewerbe GET /api/turniere/:turnierId/bewerbe/:id POST /api/turniere/:turnierId/bewerbe PUT /api/turniere/:turnierId/bewerbe/:id DELETE /api/turniere/:turnierId/bewerbe/:id // ÖPS Datasourcing POST /api/ops/import/veranstaltung/:id POST /api/ops/import/turnier/:id ``` ### Authentifizierung ```typescript POST /api/auth/login POST /api/auth/logout GET /api/auth/me POST /api/auth/refresh ``` --- ## Entwicklungsrichtlinien ### Code Style 1. **TypeScript Strict Mode**: Aktiviert 2. **Naming Conventions**: - Components: PascalCase (z.B. `BewerbeTab.tsx`) - Functions: camelCase (z.B. `handleBewerbAendern`) - Interfaces: PascalCase (z.B. `Bewerb`) - CSS Classes: kebab-case (falls verwendet) 3. **Component Structure**: ```typescript // 1. Imports import React from 'react'; import { Box, Button } from '@mui/material'; // 2. Interfaces/Types interface Props { ... } // 3. Component export function ComponentName({ prop1, prop2 }: Props) { // 3.1 State const [state, setState] = useState(); // 3.2 Handlers const handleAction = () => { ... }; // 3.3 Effects useEffect(() => { ... }, []); // 3.4 Render return ( ... ); } ``` ### MUI Best Practices 1. **Sx Props bevorzugen** statt styled components: ```typescript // ✅ Gut // ❌ Vermeiden (im Prototyp) ``` 2. **Theme-basierte Werte verwenden**: ```typescript // ✅ Gut - Theme Colors sx={{ color: 'primary.main', bgcolor: 'grey.50' }} // ❌ Vermeiden - Hardcoded sx={{ color: '#3F51B5', bgcolor: '#FAFAFA' }} ``` 3. **Responsive Werte** (für spätere mobile Version): ```typescript sx={{ width: { xs: '100%', md: 300 }, display: { xs: 'none', md: 'block' } }} ``` ### Performance-Optimierung 1. **React.memo** für große Listen: ```typescript export const BewerbRow = React.memo(({ bewerb }: Props) => { ... }); ``` 2. **useCallback** für Event Handlers in Listen: ```typescript const handleSelect = useCallback((id: number) => { ... }, []); ``` 3. **Lazy Loading** für Tabs: ```typescript const BewerbeTab = lazy(() => import('./turnier/BewerbeTab')); ``` --- ## Testing (geplant) ### Unit Tests mit Vitest ```typescript // BewerbeTab.test.tsx import { render, screen } from '@testing-library/react'; import { BewerbeTab } from './BewerbeTab'; describe('BewerbeTab', () => { it('renders 12 bewerbe', () => { render(); expect(screen.getByText('12 Bewerbe')).toBeInTheDocument(); }); }); ``` ### E2E Tests mit Playwright ```typescript // e2e/bewerbe.spec.ts test('can create new bewerb', async ({ page }) => { await page.goto('/veranstaltung/1/turnier/1'); await page.click('text=Bewerb Einfügen'); await page.fill('input[name="nummer"]', '13'); // ... }); ``` --- ## Browser-Unterstützung **Ziel-Browser** (Desktop): - Chrome/Edge >= 90 - Firefox >= 88 - Safari >= 14 **NICHT unterstützt**: - Internet Explorer - Mobile Browser (vorerst) --- ## Bekannte Einschränkungen (Prototyp) 1. **Keine Backend-Integration**: Alle Daten sind Mock-Daten 2. **Keine Persistenz**: Änderungen gehen bei Page Refresh verloren 3. **Eingeschränkte Validierung**: Minimale Form-Validierung 4. **Keine Fehlerbehandlung**: Fehler-States nicht implementiert 5. **Mock-Login**: Demo-Credentials hart-kodiert 6. **Keine Exports**: Drucken/Exportieren nur als Placeholder-Buttons 7. **Keine Suche/Filter**: Filter-Funktionen nicht implementiert 8. **Keine Undo/Redo**: "Änderungen Rückgängig" nicht funktional --- ## Nächste Schritte / Roadmap ### Phase 1: Backend-Integration - [ ] REST API Implementation - [ ] Authentifizierungs-System - [ ] Datenbank-Schema (PostgreSQL empfohlen) - [ ] ÖPS Datasourcing API-Integration ### Phase 2: Erweiterte Features - [ ] Such- und Filter-Funktionen - [ ] Sortierung in Tabellen - [ ] Drag & Drop für Bewerbs-Reihenfolge - [ ] Bulk-Operations (mehrere Bewerbe gleichzeitig bearbeiten) - [ ] Undo/Redo-Funktionalität - [ ] Auto-Save (mit Debouncing) ### Phase 3: Export & Reporting - [ ] PDF-Export (Startlisten, Ergebnislisten) - [ ] Excel-Export - [ ] Druckvorlagen - [ ] Berichts-Templates ### Phase 4: Erweiterte Tabs - [ ] Organisation-Tab: Funktionäre-Verwaltung - [ ] Organisation-Tab: Plätze-Verwaltung - [ ] Preisliste-Tab: Vollständige Implementierung - [ ] Übersicht-Tab: Dashboard mit Statistiken ### Phase 5: Zusätzliche Module - [ ] Meisterschaften/Cups-Verwaltung - [ ] Nennungs-System - [ ] Starter-Verwaltung - [ ] Pferde-Datenbank - [ ] Reiter-Datenbank ### Phase 6: Polish & Optimierung - [ ] Umfassendes Testing - [ ] Performance-Optimierung - [ ] Accessibility (WCAG 2.1 AA) - [ ] Internationalisierung (i18n) - [ ] Keyboard Shortcuts - [ ] Offline-Modus (PWA) --- ## Häufige Entwicklungs-Aufgaben ### Neue Komponente hinzufügen ```typescript // src/app/components/MyComponent.tsx import { Box, Typography } from '@mui/material'; interface MyComponentProps { title: string; } export function MyComponent({ title }: MyComponentProps) { return ( {title} ); } ``` ### Neue Route hinzufügen ```typescript // src/app/routes.tsx { path: "my-new-page", Component: MyNewPage, } ``` ### Neuen Tab in Veranstaltung/Turnier hinzufügen ```typescript // In VeranstaltungAnsicht.tsx oder TurnierAnsicht.tsx const tabs = [ // ... bestehende Tabs { label: 'Mein neuer Tab', component: } ]; ``` ### MUI Component anpassen ```typescript // Global Theme Override const theme = createTheme({ components: { MuiButton: { styleOverrides: { root: { textTransform: 'none', fontSize: '10px', }, }, }, }, }); // Oder mit Sx Props