feat(ui): introduce PferdReiterEingabe, NennungenTabelle, and NennungsMaske components

- Added `PferdReiterEingabe` for horse and rider selection with search functionality and keyboard navigation.
- Implemented `NennungenTabelle` to display filtered registrations based on selected horse or rider.
- Introduced `NennungsMaske` to combine search, table, and competition views for streamlined user interaction.
- Extended types with `Veranstalter` interface and mock data for better context and integration.
- Documented ÖTO-compliant tournament structure for frontend reference.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-24 13:49:21 +01:00
parent 5a545182f2
commit 7702574904
105 changed files with 23088 additions and 0 deletions
@@ -0,0 +1,725 @@
# Architektur-Dokumentation - Lead-Architekt
## 🏛️ System-Architektur-Übersicht
### Architektur-Typ
**Single Page Application (SPA)** mit Client-Side Routing
### Architektur-Pattern
- **Component-Based Architecture** (React)
- **Container/Presentational Pattern**
- **Data Mode Routing** (React Router v7)
---
## 🎯 Architektur-Ziele
1. **Skalierbarkeit:** Modulare Komponenten für einfache Erweiterung
2. **Wartbarkeit:** Klare Trennung von UI und Business Logic
3. **Performance:** Code-Splitting, Lazy Loading
4. **Type-Safety:** TypeScript für robuste Typisierung
5. **Konsistenz:** Design System mit Material-UI
---
## 🗺️ Anwendungs-Hierarchie
```
┌─────────────────────────────────────────────────────────────┐
│ App.tsx │
│ (RouterProvider) │
└────────────────────────┬────────────────────────────────────┘
├─────────────────────────────────────┐
│ │
┌────▼─────┐ ┌────▼─────┐
│ Login │ │ Dashboard│
│ /login │ │ /admin │
└──────────┘ └────┬─────┘
┌────────────────────────────────────┼─────────────────┐
│ │ │
┌────▼─────────────┐ ┌───────────▼──────┐ ┌──────▼─────────┐
│ Veranstalter │ │ TurnierErstellen │ │ TurnierAnsicht │
│ Verwaltung │ │ /veranstaltung/ │ │ /turnier/ │
│ /veranstalter │ │ :id │ │ :veranstaltung │
└──────────────────┘ └─────────┬────────┘ │ Id/:nr │
│ └────────┬───────┘
│ │
┌────────────▼────────┐ │
│ Veranstaltung- │ │
│ Übersicht │ │
│ (Turnier-Karten) │ │
└─────────────────────┘ │
┌───────────────────────────────────────────────────────┘
┌────────────────┼────────────────┬─────────────────┬──────────────────┐
│ │ │ │ │
┌─────▼────┐ ┌────────▼──────┐ ┌─────▼──────┐ ┌──────▼───────┐ ┌──────▼──────┐
│Stammdaten│ │ Organisation │ │ Bewerbe │ │ Artikel │ │ Abrechnung │
│ Tab │ │ Tab │ │ Tab │ │ Tab │ │ Tab │
└──────────┘ └───────────────┘ └────────────┘ └──────────────┘ └─────────────┘
┌────────────────┬─────────────────┬──────────────────┐
│ │ │ │
┌─────▼────┐ ┌────────▼──────┐ ┌──────▼───────┐ │
│Nennungen │ │ Startlisten │ │Ergebnislisten│ │
│ Tab │ │ Tab │ │ Tab │ │
└──────────┘ └───────────────┘ └──────────────┘ │
```
---
## 🏗️ Komponenten-Architektur
### Layer-Struktur
```
┌─────────────────────────────────────────────┐
│ Presentation Layer │
│ (React Components - UI Only) │
│ - Login.tsx │
│ - Dashboard.tsx │
│ - TurnierAnsicht.tsx │
└─────────────────┬───────────────────────────┘
┌─────────────────▼───────────────────────────┐
│ Container Layer │
│ (Smart Components - State Management) │
│ - StammdatenTab.tsx │
│ - BewerbeTab.tsx │
│ - AbrechnungTab.tsx │
│ - NennungenTab.tsx │
└─────────────────┬───────────────────────────┘
┌─────────────────▼───────────────────────────┐
│ Component Library Layer │
│ (Reusable Components) │
│ - PferdReiterEingabe.tsx │
│ - NennungenTabelle.tsx │
│ - Bewerbsliste.tsx │
│ - VerkaufBuchungen.tsx │
└─────────────────┬───────────────────────────┘
┌─────────────────▼───────────────────────────┐
│ Material-UI Layer │
│ (Design System - MUI Components) │
│ - Box, Paper, Button, TextField, etc. │
└─────────────────┬───────────────────────────┘
┌─────────────────▼───────────────────────────┐
│ Style Layer │
│ (Tailwind CSS v4 + CSS Variables) │
│ - theme.css │
│ - fonts.css │
└─────────────────────────────────────────────┘
```
---
## 🔄 Routing-Architektur
### React Router v7 Data Mode
```typescript
// routes.tsx
const router = createBrowserRouter([
{
path: "/",
element: <Login / >,
},
{
path: "/admin",
element: <Dashboard / >,
},
{
path: "/veranstalter",
element: <VeranstalterVerwaltung / >,
},
{
path: "/veranstaltung/:id",
element: <TurnierErstellen / >,
},
{
path: "/turnier/:veranstaltungId/:nr",
element: <TurnierAnsicht / >,
},
]);
```
### URL-Struktur
```
/ → Login
/admin → Dashboard (Veranstaltungs-Übersicht)
/veranstalter → Veranstalter-Verwaltung
/veranstaltung/neu → Neue Veranstaltung (mit Veranstalter-Auswahl)
/veranstaltung/:id → Veranstaltung-Übersicht (Turnier-Karten)
/turnier/:veranstaltungId/neu → Neues Turnier → Stammdaten
/turnier/:veranstaltungId/:nr → Turnier-Ansicht (8 Tabs)
```
---
## 💾 Daten-Architektur
### Aktueller Stand: **Client-Side Mock Data**
```typescript
// Dashboard.tsx
export const veranstaltungenData = [
{
id: number,
name: string,
veranstalter: string,
ort: string,
startDatum: string,
endDatum: string,
status: 'geplant' | 'laufend' | 'abgeschlossen',
turniere: Turnier[]
}
]
```
### Datenmodell-Hierarchie
```
┌──────────────────┐
│ Veranstalter │
│ (Verein/Club) │
└────────┬─────────┘
│ 1:n
┌────────▼─────────┐
│ Veranstaltung │
│ (Event) │
└────────┬─────────┘
│ 1:n
┌────────▼─────────┐
│ Turnier │
│ (Competition) │
└────────┬─────────┘
│ 1:n
┌────────▼─────────┐
│ Bewerb │
│ (Contest) │
└──────────────────┘
┌──────────────────┐
│ Reiter │
│ (Rider) │
└────────┬─────────┘
│ n:m
┌────────▼─────────┐
│ Nennung │
│ (Registration) │
└────────┬─────────┘
│ n:m
┌────────▼─────────┐
│ Pferd │
│ (Horse) │
└──────────────────┘
┌──────────────────┐
│ Buchung │
│ (Transaction) │
└──────────────────┘
```
---
## 🎨 Design System-Architektur
### Material-UI Theme
```typescript
// Primärfarbe: Indigo
primary: {
main: '#3F51B5',
light
:
'#7986CB',
dark
:
'#303F9F',
}
// Font-Größen (kompakt für Desktop)
fontSize: {
xs: '10px',
sm
:
'11px',
md
:
'13px',
lg
:
'15px',
}
```
### Tailwind CSS v4 Integration
```css
/* theme.css */
@theme {
--color-primary: #3f51b5;
--color-primary-hover: #303f9f;
--font-size-xs: 10px;
--font-size-sm: 11px;
--font-size-md: 13px;
}
```
---
## 🔐 Authentifizierung-Architektur
### Aktuell: **Demo-Modus**
```typescript
// Login.tsx
const DEMO_USER = 'admin';
const DEMO_PASSWORD = 'Admin#1234';
```
### Geplant: **JWT-basierte Authentifizierung**
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │────────▶│ Backend │────────▶│ Database │
│ React │ Login │ API │ Verify │ Users │
└────┬─────┘ └────┬─────┘ └──────────┘
│ │
│◀───────────────────┤
│ JWT Token │
│ │
│ API Calls │
│ (with Token) │
├───────────────────▶│
│ │
│◀───────────────────┤
│ Response │
```
---
## 📊 State Management-Architektur
### Aktuell: **React useState**
```typescript
// Lokaler Component State
const [activeTab, setActiveTab] = useState(0);
const [nennungen, setNennungen] = useState<any[]>([]);
const [selectedPferd, setSelectedPferd] = useState<any>(null);
```
### Empfehlung für Skalierung:
#### Option 1: **React Context API** (für mittlere Komplexität)
```typescript
// TurnierContext.tsx
const TurnierContext = createContext();
export function TurnierProvider({children}) {
const [turnier, setTurnier] = useState();
const [bewerbe, setBewerbe] = useState([]);
return (
<TurnierContext.Provider value = {
{
turnier, bewerbe
}
}>
{
children
}
</TurnierContext.Provider>
)
;
}
```
#### Option 2: **Zustand** (empfohlen für große Apps)
```typescript
// store/turnierStore.ts
import {create} from 'zustand';
export const useTurnierStore = create((set) => ({
turnier: null,
bewerbe: [],
setTurnier: (turnier) => set({turnier}),
addBewerb: (bewerb) => set((state) => ({
bewerbe: [...state.bewerbe, bewerb]
})),
}));
```
#### Option 3: **React Query / TanStack Query** (für Server State)
```typescript
// hooks/useTurnier.ts
import {useQuery} from '@tanstack/react-query';
export function useTurnier(id: string) {
return useQuery({
queryKey: ['turnier', id],
queryFn: () => fetchTurnier(id),
});
}
```
---
## 🔌 Backend-Integration (Geplant)
### Option 1: **REST API**
```
GET /api/veranstaltungen
POST /api/veranstaltungen
GET /api/veranstaltungen/:id
PUT /api/veranstaltungen/:id
DELETE /api/veranstaltungen/:id
GET /api/veranstaltungen/:id/turniere
POST /api/veranstaltungen/:id/turniere
GET /api/turniere/:id
PUT /api/turniere/:id
GET /api/turniere/:id/bewerbe
POST /api/turniere/:id/bewerbe
PUT /api/bewerbe/:id
GET /api/turniere/:id/nennungen
POST /api/nennungen
GET /api/reiter/:id/nennungen
GET /api/pferde/:id/nennungen
GET /api/turniere/:id/buchungen
POST /api/buchungen
GET /api/reiter/:id/buchungen
```
### Option 2: **GraphQL** (empfohlen für komplexe Queries)
```graphql
query GetTurnier($id: ID!) {
turnier(id: $id) {
id
name
stammdaten {
znsDaten
oetoTyp
}
bewerbe {
id
name
klasse
}
nennungen {
id
reiter { vorname, nachname }
pferd { name }
bewerb { name }
}
buchungen {
id
text
soll
haben
saldo
}
}
}
```
### Option 3: **Supabase** (empfohlen für Rapid Development)
```typescript
// supabase/client.ts
import {createClient} from '@supabase/supabase-js';
export const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);
// Beispiel: Turniere abrufen
const {data, error} = await supabase
.from('turniere')
.select('*, veranstaltung(name), bewerbe(*)')
.eq('id', turnierId);
```
---
## 🚀 Performance-Optimierungen
### 1. **Code-Splitting**
```typescript
// Lazy Loading für Tabs
const BewerbeTab = lazy(() => import('./turnier/BewerbeTab'));
const AbrechnungTab = lazy(() => import('./turnier/AbrechnungTab'));
<Suspense fallback = { < Loading / >
}>
{
activeTab === 2 && <BewerbeTab / >
}
</Suspense>
```
### 2. **Memoization**
```typescript
// React.memo für teure Components
export const NennungenTabelle = memo(({nennungen, selectedPferd}) => {
// ...
});
// useMemo für teure Berechnungen
const gesamtSaldo = useMemo(() => {
return buchungen.reduce((sum, b) => sum + b.saldo, 0);
}, [buchungen]);
```
### 3. **Virtualisierung** (für große Listen)
```typescript
import {FixedSizeList} from 'react-window';
<FixedSizeList
height = {600}
itemCount = {bewerbe.length}
itemSize = {50}
>
{({index, style})
=>
(
<div style = {style} > {bewerbe[index].name} < /div>
)
}
</FixedSizeList>
```
---
## 🔒 Sicherheits-Architektur
### Geplante Security Features:
1. **Authentication & Authorization**
- JWT Tokens mit Refresh
- Role-Based Access Control (RBAC)
- Session Management
2. **Input Validation**
- Client-Side: React Hook Form + Zod
- Server-Side: Express Validator / Joi
3. **XSS Protection**
- Content Security Policy (CSP)
- Sanitization von User Inputs
4. **CSRF Protection**
- CSRF Tokens für State-Changing Operations
---
## 📦 Deployment-Architektur
### Empfohlener Stack:
```
┌─────────────────┐
│ Vercel/ │ → Frontend Hosting (React SPA)
│ Netlify │
└────────┬────────┘
│ HTTPS
┌────────▼────────┐
│ Backend API │ → Node.js / Express / Nest.js
│ (Railway/ │
│ Render/AWS) │
└────────┬────────┘
┌────────▼────────┐
│ Database │ → PostgreSQL (Supabase / RDS)
│ (Supabase/ │
│ AWS RDS) │
└─────────────────┘
```
### CI/CD Pipeline:
```
GitHub Push
GitHub Actions
├──▶ Lint & Type Check
├──▶ Unit Tests
├──▶ Build
└──▶ Deploy to Vercel
```
---
## 📈 Skalierungs-Strategie
### Phase 1: **Prototyp** (Aktuell)
- Client-Side Mock Data
- React useState
- Keine Backend-Integration
### Phase 2: **MVP**
- Supabase Backend
- REST API Integration
- Basic Authentication
- React Query für Server State
### Phase 3: **Production**
- Dedizierter Backend-Server
- GraphQL API (optional)
- Redis Caching
- WebSocket für Real-Time Updates
- Zustand für Global State
### Phase 4: **Enterprise**
- Microservices-Architektur
- Event-Driven Architecture
- CQRS Pattern
- Message Queue (RabbitMQ / Kafka)
- Multi-Tenant Support
---
## 🧪 Testing-Strategie
### Unit Tests
```typescript
// BewerbeTab.test.tsx
import {render, screen} from '@testing-library/react';
import {BewerbeTab} from './BewerbeTab';
test('renders bewerbe table', () => {
render(<BewerbeTab / >);
expect(screen.getByText('Bewerbs-Übersicht')).toBeInTheDocument();
});
```
### Integration Tests
```typescript
// TurnierAnsicht.integration.test.tsx
test('tab navigation works', async () => {
render(<TurnierAnsicht / >);
const bewerbeTab = screen.getByText('Bewerbe');
await userEvent.click(bewerbeTab);
expect(screen.getByText('Bewerbs-Übersicht')).toBeInTheDocument();
});
```
### E2E Tests (Playwright / Cypress)
```typescript
// e2e/turnier.spec.ts
test('create new turnier', async ({page}) => {
await page.goto('/admin');
await page.click('text=Neue Veranstaltung');
await page.click('text=RFV Musterstadt');
await page.fill('[name="name"]', 'Frühjahrsturnier 2026');
await page.click('text=Speichern');
await expect(page.locator('text=Frühjahrsturnier 2026')).toBeVisible();
});
```
---
## 📚 Architektur-Entscheidungen (ADRs)
### ADR-001: React Router Data Mode
**Status:** Angenommen
**Kontext:** Benötigen Client-Side Routing mit mehreren verschachtelten Routen
**Entscheidung:** React Router v7 mit Data Mode Pattern
**Konsequenzen:** Einfache Navigation, aber keine SSR
### ADR-002: Material-UI als Design System
**Status:** Angenommen
**Kontext:** Benötigen professionelles Design System für Desktop-App
**Entscheidung:** Material-UI v6 (Material Design 3)
**Konsequenzen:** Konsistentes Design, aber größere Bundle-Size
### ADR-003: Tailwind CSS für Custom Styles
**Status:** Angenommen
**Kontext:** Benötigen Utility-First CSS für schnelles Styling
**Entscheidung:** Tailwind CSS v4 zusätzlich zu MUI
**Konsequenzen:** Flexibles Styling, aber zwei CSS-Systeme
### ADR-004: TypeScript für Type-Safety
**Status:** Angenommen
**Kontext:** Komplexe Datenstrukturen benötigen Type-Safety
**Entscheidung:** TypeScript statt JavaScript
**Konsequenzen:** Bessere DX, aber längere Compile-Zeit
---
## 🔮 Zukunfts-Roadmap
### Q2 2026
- [ ] Supabase Backend-Integration
- [ ] REST API für alle CRUD-Operationen
- [ ] PDF-Export für Startlisten/Rechnungen
- [ ] Excel-Export
### Q3 2026
- [ ] Real-Time Collaboration (WebSockets)
- [ ] Mobile-Responsive Layout
- [ ] Offline-Modus (PWA)
- [ ] Multi-Language Support (i18n)
### Q4 2026
- [ ] GraphQL API
- [ ] Advanced Analytics Dashboard
- [ ] Email-Benachrichtigungen
- [ ] Zahlungs-Gateway Integration
---
**Dokumentiert von:** Lead-Architekt
**Version:** 1.0
**Datum:** 2026-03-24