37 KiB
Koogle App - Analyse & Vorschlag für Bereiche und Pages
Zweck der Anwendung (abgeleitet aus Datenmodell)
Koogle ist eine Vereinsverwaltung für Kegelvereine mit Schwerpunkt auf:
Kernfunktionen
- Vereinsverwaltung: Multi-Mandanten-System, jeder Verein = eigener Scope
- Mitglieder & Gäste: Verwaltung von Personen mit Status (Member/Guest)
- Spieltagsorganisation: Planung und Durchführung von Kegeltagen (Days)
- Status-Workflow: New → Started → Closed (oder Postponed)
- Zuordnung von Teilnehmern pro Spieltag
- Spielverwaltung: Mehrere Games pro Day möglich
- JSON-basierte Spielstände (GameData)
- Teilnehmerzuordnung pro Game
- Kostenmanagement:
- Vordefinierte Kosten/Strafen (Expenses) pro Verein
- Automatische Trigger (z.B. Pudel, Pumpe, Aus, Kranz, etc.)
- Variable/Fixe Preise, Inverse Kosten (alle außer einem zahlen)
- One-Click Kosten für schnelle Erfassung
- Abrechnung:
- Pro Person, pro Tag, pro Spiel
- Berechnungsmethoden: None, Average, Maximum (für fehlende Personen)
- Status: Open/Done für PersonExpenses
- Benutzer & Berechtigungen:
- ASP.NET Identity mit Custom UserProfile
- Rollen pro Verein: Viewer, Editor, Admin
- SuperAdmin für vereinsübergreifende Verwaltung
- Multi-Club Zugehörigkeit möglich
gerenelle Implementierungshinweise
- Der IMPLEMENTATION_PLAN ist in Deutscher Sprache formuliert.
- Die Anwendung wird in deutscher Sprache implementiert. Alle Klassen, Methoden, Interfaces und sonstigen Artikefakte werden dennoch in englischer Sprache benannt. Auch die XMLDOC Kommentare sind Englisch zu formulieren.
- Der Anwender wird vom Programm in lockerem Ton und in der Du-Form angesprochen.
Bestehende Implementierung
Vollständig implementiert
- ✅ Authentication/Authorization Framework
- ✅ Login mit Vereinsauswahl
- ✅ Fluxor State Management (AuthState)
- ✅ Rollenbasierte Berechtigungen
- ✅ MudBlazor UI Framework
- ✅ Dual DbContext (Domain + Identity)
- ✅ Clean Architecture Struktur
- ✅ Seeders (SuperAdmin, Roles)
Teilweise implementiert
- ⚠️ DayService (vorhanden, aber auskommentiert)
- ⚠️ Navigation (Skelett vorhanden, kaum Menüpunkte)
Nicht implementiert
- ❌ UI für Clubs, Days, Games, Persons, Expenses
- ❌ Services für Person, Game, Expense
- ❌ DTOs für die meisten Domain Entities
- ❌ Fluxor States für Domain-Daten
- ❌ Reporting/Export-Funktionalität
Vorgeschlagene Bereiche & Pages
1. DASHBOARD-BEREICH (Startseite)
Route: / oder /dashboard
Zweck: Übersicht über aktuelle Aktivitäten im ausgewählten Verein
Pages:
- Dashboard.razor:
- Nächste geplante Spieltage
- Aktuelle offene Kosten
- Quick-Actions (neuer Spieltag, neue Person)
- Statistiken (Anzahl Mitglieder, Gäste, offene Abrechnungen)
2. SPIELTAG-BEREICH (Day Management)
Route: /days
Pages:
-
DayList.razor (
/days):- Tabelle: PostDate, Status, Teilnehmeranzahl
- Filter: Jahr, Monat, Status
- Actions: Neu, Bearbeiten, Löschen, Schließen
-
DayDetail.razor (
/days/{id}):- Spieltag-Info (Datum, Status)
- Teilnehmerliste mit Anwesenheit
- Games des Tages
- PersonExpenses des Tages (Übersicht)
- Actions: Teilnehmer hinzufügen, Spiel hinzufügen, Status ändern
-
DayCreate.razor (
/days/new):- Formular: Datum, Vorauswahl Teilnehmer aus Mitgliedern
-
DayEdit.razor (
/days/{id}/edit):- Datum ändern, Teilnehmer hinzufügen/entfernen
3. SPIEL-BEREICH (Game Management)
Route: /days/{dayId}/games
Pages:
-
GameList.razor (
/days/{dayId}/games):- Liste der Spiele eines Spieltags
- Actions: Neues Spiel, Bearbeiten, Löschen
-
GameDetail.razor (
/days/{dayId}/games/{gameId}):- GameData anzeigen/bearbeiten (JSON-Editor oder strukturierte Eingabe)
- Teilnehmer des Spiels
- Kosten des Spiels (PersonExpenses mit GameId)
- Trigger-Events erfassen (z.B. "Pudel" → Expense zuweisen)
-
GameCreate.razor (
/days/{dayId}/games/new):- Teilnehmer auswählen (aus DayPersons)
- Optionale GameData-Initialisierung
4. KOSTEN-BEREICH (Expense Management)
Route: /expenses
Pages:
-
ExpenseList.razor (
/expenses):- Vordefinierte Expenses des Vereins
- Spalten: Name, Preis, Typ, IsOneClick, IsInverse, IsVariable
- Actions: Neu, Bearbeiten, Löschen
-
ExpenseCreate.razor (
/expenses/new):- Formular für neue Expense-Vorlage
- Trigger-Zuordnung optional
-
PersonExpenseList.razor (
/expenses/assigned):- Alle zugewiesenen Kosten (PersonExpenses)
- Filter: Person, Tag, Status (Open/Done), Datum
- Bulk-Actions: Als bezahlt markieren, Löschen
-
ExpenseTriggerConfig.razor (
/expenses/triggers):- Trigger-Typen anzeigen
- Zuordnung Trigger → Expense
5. PERSONEN-BEREICH (Person Management)
Route: /people
Pages:
-
PersonList.razor (
/people):- Tabelle: Name, Status (Member/Guest), Actions
- Filter: Status
- Actions: Neu, Bearbeiten, Löschen
-
PersonCreate.razor (
/people/new):- Name, Status
-
PersonDetail.razor (
/people/{id}):- Personen-Info
- Teilnahme-Historie (DayPersons)
- Kosten-Historie (PersonExpenses)
- Statistiken (Gesamtkosten, Anzahl Teilnahmen)
6. AUSWERTUNGEN-BEREICH (Reports/Evaluations)
Route: /reports
Pages:
-
ReportOverview.razor (
/reports):- Auswahl: Pro Person, Pro Tag, Pro Spiel
-
PersonReport.razor (
/reports/person/{id}):- Alle Kosten einer Person
- Summen pro Tag, pro Spiel
- Zeitraum-Filter
-
DayReport.razor (
/reports/day/{id}):- Alle Kosten eines Spieltags
- Aufschlüsselung pro Person
- Export-Option (PDF, CSV)
-
PeriodReport.razor (
/reports/period):- Zeitraum wählen (von-bis)
- Aggregierte Statistiken
- Top-Spieler, teuerste Tage, etc.
7. STAMMDATEN-BEREICH (Master Data - Vereins-spezifisch)
Route: /masterdata
Pages:
-
ClubSettings.razor (
/masterdata/club):- Vereins-Name
- ExpenseCalculation-Methode
- Weitere Einstellungen
-
ExpenseTemplates.razor (
/masterdata/expenses):- Siehe Expense-Bereich (evtl. Duplikat)
-
TriggerConfig.razor (
/masterdata/triggers):- Siehe ExpenseTriggerConfig
8. ADMIN-BEREICH (SuperAdmin - Vereinsübergreifend)
Route: /admin
Pages:
-
ClubList.razor (
/admin/clubs):- Alle Vereine (SuperAdmin only)
- Actions: Neu, Bearbeiten, Löschen
-
ClubCreate.razor (
/admin/clubs/new):- Name, ExpenseCalculation
-
UserManagement.razor (
/admin/users):- Alle UserProfiles
- Vereinszuordnung (UserProfileClub)
- Rollen-Zuordnung pro Verein
-
SystemSettings.razor (
/admin/system):- Globale Einstellungen
9. PROFIL-BEREICH (User Profile)
Route: /profile
Pages:
- UserProfile.razor (
/profile):- DisplayName, Locale, TimeZone ändern
- Passwort ändern
- Standard-Verein festlegen (UserProfileClub.IsDefault)
- Vereins-Mitgliedschaften anzeigen
Vorgeschlagene Navigation-Struktur
NavMenu.razor:
├── 🏠 Dashboard (/)
├── 📅 Spieltage (/days)
├── 👥 Personen (/people)
├── 💰 Kosten
│ ├── Vorlagen (/expenses)
│ ├── Zugewiesen (/expenses/assigned)
│ └── Trigger (/expenses/triggers)
├── 📊 Auswertungen (/reports)
│ ├── Pro Person
│ ├── Pro Tag
│ └── Zeitraum
├── ⚙️ Stammdaten (IsClubEditor+)
│ ├── Verein (/masterdata/club)
│ └── Kosten-Vorlagen
├── 🔧 Admin (IsSuperAdmin)
│ ├── Vereine (/admin/clubs)
│ ├── Benutzer (/admin/users)
│ └── System (/admin/system)
└── 👤 Profil (/profile)
Prioritäts-Vorschlag (Phasen) - basierend auf User-Feedback
Phase 1: MVP - Basis-Verwaltung (DIESE PLANUNG)
Scope: User/Account-Mgmt, Club, Personen (Teilnehmer), Tage, Strafen
-
User/Account-Verwaltung
- User-Registrierung (self-service oder Admin)
- Passwort zurücksetzen
- User-zu-Club Zuordnung (UserProfileClub)
- Rollen pro Club zuweisen (UserProfileClubRoleAssignment)
- User-Profil (DisplayName, Locale, TimeZone)
-
Club-Verwaltung (SuperAdmin)
- Liste, Create, Edit, Delete
- ExpenseCalculation-Methode
-
Person-Verwaltung (Club-Teilnehmer: Members + Guests)
- Liste, Create, Edit, Delete
- Status (Member/Guest)
- KEINE Login - reine Stammdaten für Kegelclub
-
Day-Verwaltung
- Liste, Create, Edit, Delete
- Datum, Status (New, Started, Closed, Postponed)
- Teilnehmer zuordnen (DayPerson)
-
PersonExpense - Strafen manuell erfassen
- Pro Teilnehmer pro Tag Strafen hinzufügen
- Expense-Vorlagen (Name, Preis)
- Status: Open/Done
-
Dashboard
- Übersicht: Nächste Tage, offene Kosten
- Quick-Actions
-
Einfache Auswertung
- Pro Tag: Wer schuldet was
- Pro Person: Summe offener Kosten
Phase 2: Detaillierte Spielverwaltung (SEPARATE PLANUNG SPÄTER)
NICHT in dieser Planung:
- Wurf-für-Wurf Eingabe
- Undo-Funktion
- Plugin-System für verschiedene Kegelspiele
- GameData strukturiert (JSON für jetzt)
- Automatische Trigger (Pudel, Pumpe, etc.)
- Expense-Trigger-Engine
Phase 3: Advanced Features (SPÄTER)
- Export (PDF, CSV)
- Benutzer-Rollen verwalten
- Erweiterte Reports
IMPLEMENTIERUNGSPLAN - Phase 1 MVP
Umsetzungsreihenfolge - Übersicht
| ✓ | Phase | Bereich | Beschreibung | Dateien |
|---|---|---|---|---|
| ✓ | A1 | Foundation | Repository Interfaces | 5 Interface-Dateien |
| ✓ | A2 | Foundation | Repository Implementations | 5 Repository-Dateien |
| ✓ | A3 | Foundation | DTOs | 5 DTO-Dateien |
| ✓ | A4 | Foundation | Service Interfaces | 5 Service-Interface-Dateien |
| ✓ | A5 | Foundation | Service Implementations | 5 Service-Dateien |
| ✓ | A6 | Foundation | AutoMapper Profiles | 5 Mapping-Dateien |
| ✓ | A7 | Foundation | DI Registration | 2 DI-Dateien ändern |
| ✓ | B1 | User/Account | UserService erweitern | 1 Service, 1 Interface, 1 DTO |
| ✓ | B2 | User/Account | Register Page | 1 Razor |
| ✓ | B3 | User/Account | Password Reset Pages | 2 Razor |
| ✓ | B4 | User/Account | Profile Page | 1 Razor |
| ✓ | B5 | User/Account | Admin Users Page | 1 Razor |
| ✓ | C1 | Clubs | ClubState Fluxor | 4 State-Dateien |
| ✓ | C2 | Clubs | Club Pages - ERSTES TESTBARES MVP | 1 Razor |
| ✓ | D1 | Personen | PersonState Fluxor | 4 State-Dateien |
| ✓ | D2 | Personen | Person Pages | 1 Razor |
| ✓ | D3 | Expenses | ExpenseState Fluxor | 4 State-Dateien |
| ✓ | D4 | Expenses | Expense Pages | 1 Razor |
| ✓ | E1 | Days | DayState Fluxor | 4 State-Dateien |
| ✓ | E2 | Days | Days List Page | 1 Razor |
| ✓ | E3 | Days | Day Details Page | 1 Razor |
| ✓ | E4 | Days | PersonExpense Management | Components in DayDetails |
| ✓ | F1 | Dashboard | Dashboard Page | 1 Razor |
| ✓ | F2 | Dashboard | Evaluation Components | 2 Shared Components |
| ✓ | F3 | Navigation | NavMenu finalisieren | 1 Razor ändern |
| ✓ | G1 | Erweiterte Reg. | MembershipStatus + UserProfileClub | 2 Dateien + Migration |
| ✓ | G2 | Erweiterte Reg. | ClubInvitation Entity | 1 Entity + Migration |
| ✓ | G3 | Erweiterte Reg. | IEmailService (Stub) | 2 Dateien |
| ✓ | G4 | Erweiterte Reg. | Services erweitern | 2 Services |
| ✓ | G5 | Erweiterte Reg. | Dashboard Pending-Widget | 1 Component |
| ✓ | G6 | Erweiterte Reg. | Admin Users Page erweitern | 1 Razor |
| ✓ | G7 | Erweiterte Reg. | Club-Beitritt UI | 1 Razor |
| ✓ | G8 | Erweiterte Reg. | Einladungslink-Handling | 2 Dateien |
Legende: ☐ = Offen | ☑ = In Arbeit | ✓ = Fertig
Geschätzte Dateien insgesamt: ~90 Dateien
Detaillierte Phasen
Phase A1: Repository Interfaces erstellen
Dateien:
src/Koogle.Domain/Interfaces/IClubRepository.cssrc/Koogle.Domain/Interfaces/IPersonRepository.cssrc/Koogle.Domain/Interfaces/IExpenseRepository.cssrc/Koogle.Domain/Interfaces/IDayRepository.cs(erweitern)src/Koogle.Domain/Interfaces/IPersonExpenseRepository.cs
Methoden pro Interface: GetAllAsync/GetByClubIdAsync, GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync
Phase A2: Repository Implementations erstellen
Dateien:
src/Koogle.Infrastructure/Repositories/ClubRepository.cssrc/Koogle.Infrastructure/Repositories/PersonRepository.cssrc/Koogle.Infrastructure/Repositories/ExpenseRepository.cssrc/Koogle.Infrastructure/Repositories/DayRepository.cssrc/Koogle.Infrastructure/Repositories/PersonExpenseRepository.cs
Pattern: IDbContextFactory, ClubId-Filter, Include Navigation Properties
Phase A3: DTOs erstellen
Dateien:
src/Koogle.Application/DTOs/ClubDto.cs(ClubDto, CreateClubDto, UpdateClubDto)src/Koogle.Application/DTOs/PersonDto.cs(PersonDto, CreatePersonDto, UpdatePersonDto)src/Koogle.Application/DTOs/ExpenseDto.cs(ExpenseDto, CreateExpenseDto, UpdateExpenseDto)src/Koogle.Application/DTOs/DayDto.cserweitern (DayDto, CreateDayDto, UpdateDayDto, DayParticipantDto)src/Koogle.Application/DTOs/PersonExpenseDto.cs(PersonExpenseDto, CreatePersonExpenseDto, DayEvaluationDto, PersonDayEvaluationDto)
Phase A4: Service Interfaces erstellen
Dateien:
src/Koogle.Application/Interfaces/IClubService.cssrc/Koogle.Application/Interfaces/IPersonService.cssrc/Koogle.Application/Interfaces/IExpenseService.cssrc/Koogle.Application/Interfaces/IDayService.cs(erweitern)src/Koogle.Application/Interfaces/IPersonExpenseService.cs
Methoden: GetAllAsync, GetByIdAsync, CreateAsync, UpdateAsync, DeleteAsync + spezifische Methoden
Phase A5: Service Implementations erstellen
Dateien:
src/Koogle.Application/Services/ClubService.cssrc/Koogle.Application/Services/PersonService.cssrc/Koogle.Application/Services/ExpenseService.cssrc/Koogle.Application/Services/DayService.cs(erweitern)src/Koogle.Application/Services/PersonExpenseService.cs
Dependencies: Repository, ICurrentClubContext, ICurrentUserService, IMapper Business Logic: ClubId-Injection, Audit-Felder, Validierung (mittels FluentValidation)
Phase A6: AutoMapper Profiles erstellen
Dateien:
src/Koogle.Application/Mapping/ClubMappingProfile.cssrc/Koogle.Application/Mapping/PersonMappingProfile.cssrc/Koogle.Application/Mapping/ExpenseMappingProfile.cssrc/Koogle.Application/Mapping/DayMappingProfile.cssrc/Koogle.Application/Mapping/PersonExpenseMappingProfile.cs
Pattern: CreateMap<Entity, Dto>() bidirektional
Phase A7: DI Registration
Dateien ändern:
src/Koogle.Infrastructure/DependencyInjection.cs(5 Repositories registrieren)src/Koogle.Application/DependencyInjection.cs(5 Services registrieren)
Phase B1: UserService erweitern
Dateien:
src/Koogle.Application/Interfaces/IUserService.cserweiternsrc/Koogle.Application/Services/UserService.cserweiternsrc/Koogle.Application/DTOs/UserDto.cserweitern (RegisterUserDto, ResetPasswordDto, UpdateUserProfileDto)
Neue Methoden:
- RegisterUserAsync
- RequestPasswordResetAsync
- ResetPasswordAsync
- UpdateProfileAsync
- AssignUserToClubAsync, RemoveUserFromClubAsync
- AssignClubRoleAsync, RemoveClubRoleAsync
Phase B2: Register Page
Dateien:
src/Koogle.Web/Components/Pages/Account/Register.razor
Features: Email, Password, DisplayName, Optional ClubName für initiale Zuordnung
Phase B3: Password Reset Pages
Dateien:
src/Koogle.Web/Components/Pages/Account/ForgotPassword.razorsrc/Koogle.Web/Components/Pages/Account/ResetPassword.razor
Flow: Email eingeben → Token per Email → Passwort zurücksetzen
Phase B4: Profile Page
Dateien:
src/Koogle.Web/Components/Pages/Account/Profile.razor
Features: DisplayName, Locale, TimeZone, Club-Memberships anzeigen, Standard-Club setzen
Phase B5: Admin Users Page
Dateien:
src/Koogle.Web/Components/Pages/Admin/Users.razor
Features: User-Liste, Club-Zuordnungen, Rollen pro Club
Phase C1: ClubState (Fluxor)
Dateien:
src/Koogle.Web/Store/ClubState/ClubState.cssrc/Koogle.Web/Store/ClubState/ClubActions.cssrc/Koogle.Web/Store/ClubState/ClubReducers.cssrc/Koogle.Web/Store/ClubState/ClubEffects.cs
Actions: Load, Create, Update, Delete
Phase C2: Club Pages
Dateien:
src/Koogle.Web/Components/Pages/Admin/Clubs.razor
Features: MudTable, CRUD-Dialogs, SuperAdmin only
Phase D1: PersonState (Fluxor)
Dateien:
src/Koogle.Web/Store/PersonState/PersonState.cssrc/Koogle.Web/Store/PersonState/PersonActions.cssrc/Koogle.Web/Store/PersonState/PersonReducers.cssrc/Koogle.Web/Store/PersonState/PersonEffects.cs
Phase D2: Person Pages
Dateien:
src/Koogle.Web/Components/Pages/Persons/Persons.razor
Features: MudTable mit Filter (Member/Guest), CRUD
Phase D3: ExpenseState (Fluxor)
Dateien:
src/Koogle.Web/Store/ExpenseState/ExpenseState.cssrc/Koogle.Web/Store/ExpenseState/ExpenseActions.cssrc/Koogle.Web/Store/ExpenseState/ExpenseReducers.cssrc/Koogle.Web/Store/ExpenseState/ExpenseEffects.cs
Phase D4: Expense Pages
Dateien:
src/Koogle.Web/Components/Pages/Expenses/Expenses.razor
Features: MudTable mit Expense-Vorlagen, CRUD
Phase E1: DayState (Fluxor)
Dateien:
src/Koogle.Web/Store/DayState/DayState.cssrc/Koogle.Web/Store/DayState/DayActions.cssrc/Koogle.Web/Store/DayState/DayReducers.cssrc/Koogle.Web/Store/DayState/DayEffects.cs
State: Days, SelectedDay, SelectedDayExpenses, AvailablePersons
Phase E2: Days List Page
Dateien:
src/Koogle.Web/Components/Pages/Days/Days.razor
Features: MudTable mit Jahr-Filter, Create Day
Phase E3: Day Details Page
Dateien:
src/Koogle.Web/Components/Pages/Days/DayDetails.razor
Features:
- Day-Header (Datum, Status)
- Status-Workflow Buttons (New→Started→Closed)
- Teilnehmer-Sektion (Add/Remove)
- PersonExpense-Sektion
Phase E4: PersonExpense Management
Components in DayDetails:
- PersonExpense-Tabelle
- Add Expense Dialog (Person auswählen, Expense auswählen, Preis editierbar wenn IsVariable)
- Delete PersonExpense (nur in New/Started)
- IsInverse Logic: 1 Person auswählen → alle anderen bekommen Expense
Phase F1: Dashboard Page
Dateien:
src/Koogle.Web/Components/Pages/Dashboard.razor
Features: Summary Cards, Recent Days, Top Penalty Recipients
Phase F2: Evaluation Components
Dateien:
src/Koogle.Web/Components/Shared/DayEvaluationComponent.razorsrc/Koogle.Web/Components/Shared/PersonEvaluationComponent.razor
Phase F3: Navigation finalisieren
Dateien ändern:
src/Koogle.Web/Components/Layout/NavMenu.razor
Features: Dashboard, Spieltage, Stammdaten, Admin, Profil
Phase G1: MembershipStatus + UserProfileClub erweitern
Dateien:
-
src/Koogle.Domain/Enums/MembershipStatus.cs(neu)- Pending, Approved, Rejected
-
src/Koogle.Domain/Entities/UserProfileClub.cs(erweitern)- MembershipStatus Status (default: Pending)
- string? RejectionReason
- DateTime? ApprovedAt, Guid? ApprovedById
- DateTime? RejectedAt, Guid? RejectedById
-
Migration erstellen
Phase G2: ClubInvitation Entity
Dateien:
-
src/Koogle.Domain/Entities/ClubInvitation.cs(neu)- Guid Id, Guid ClubId
- string Token (unique, für URL)
- DateTime ExpiresAt, DateTime CreatedAt, Guid CreatedById
- int? MaxUses (null = unbegrenzt), int UsedCount
-
src/Koogle.Infrastructure/Data/AppDbContext.cs- DbSet hinzufügen -
Migration erstellen
Phase G3: IEmailService (Stub)
Dateien:
-
src/Koogle.Application/Interfaces/IEmailService.cs(neu)- SendMembershipRequestNotificationAsync
- SendMembershipApprovedAsync
- SendMembershipRejectedAsync
-
src/Koogle.Infrastructure/Services/StubEmailService.cs(neu)- Logging statt echtem Versand
- TODO-Kommentare für SMTP
-
DI Registration
Phase G4: Services erweitern (Membership-Logik)
UserService erweitern:
- RequestClubMembershipAsync(userProfileId, clubId)
- RequestClubMembershipByNameAsync(userProfileId, clubName)
- RequestClubMembershipByInviteAsync(userProfileId, inviteToken)
- ApproveMembershipAsync(userProfileId, clubId, approvedById)
- RejectMembershipAsync(userProfileId, clubId, rejectedById, reason)
- GetPendingMembershipsAsync(clubId)
ClubService erweitern:
- CreateInvitationAsync(clubId, createdById, expiresAt, maxUses)
- GetInvitationByTokenAsync(token)
- ValidateInvitationAsync(token)
Phase G5: Dashboard Pending-Widget
Dateien:
-
src/Koogle.Web/Components/Shared/PendingMembershipsWidget.razor(neu)- Anzahl ausstehender Anträge für Club-Admins
- Link zur Admin Users Page
-
Dashboard.razor erweitern - Widget für Admins einbinden
Phase G6: Admin Users Page erweitern
Dateien:
src/Koogle.Web/Components/Pages/Admin/Users.razor(ändern)- Tab/Filter für "Ausstehende Anträge"
- Approve/Reject Buttons
- Reject-Dialog mit Begründungsfeld
Phase G7: Club-Beitritt UI
Dateien:
-
src/Koogle.Web/Components/Pages/Account/JoinClub.razor(neu)- Eingabefeld für Club-Name
- Suche/Validierung
- Beitrittsantrag senden
- Erfolgsmeldung "Antrag gestellt"
-
Dashboard.razor erweitern
- "Keinem Club zugeordnet" Meldung mit Link zu JoinClub
Phase G8: Einladungslink-Handling
Dateien:
-
src/Koogle.Web/Components/Pages/Club/JoinByInvite.razor(neu)- Route:
/club/join/{token} - Token validieren
- Wenn eingeloggt: direkt Beitritt (Pending)
- Wenn nicht eingeloggt: Redirect zu Register mit Token
- Route:
-
src/Koogle.Web/Controllers/AuthController.cs(erweitern)- InviteToken als optionalen Parameter bei Register
Zentrale Anforderungen (User-Feedback)
Business Rules
- PersonExpense.Price: Bei IsVariable=true editierbar, aber vorbelegt aus Expense
- Day Status-Workflow: Strikt New→Started→Closed, keine Sprünge
- User-Registrierung: Self-Service (öffentlich)
- Passwort-Reset: Email-basiert mit Token
- Day-Create: Alle Members vorbelegt, Gäste manuell
- PersonExpense löschen: Nur in Status New/Started
- Expense.IsInverse: Automatisch - 1 Person auswählen, alle anderen bekommen Expense
- Dashboard Zeitraum: Aktuelles Jahr
Code-Patterns & Konventionen
Repository Pattern
- Interface in Domain/Interfaces
- Implementation in Infrastructure/Repositories
- Standard-Methoden: GetAllAsync, GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync
- ClubId-Filter via ICurrentClubContext
- IDbContextFactory für Scoping
Service Pattern
- Interface in Application/Interfaces
- Implementation in Application/Services
- Dependencies: Repository, ICurrentClubContext, ICurrentUserService, IMapper
- ClubService: IsSuperAdmin Check
- Audit-Felder auto-setzen (CreatedById, CreatedAt, ModifiedById, ModifiedAt)
Fluxor Pattern (Redux)
- State: Record mit Collections + IsLoading + Error
- Actions: Record per Operation (Load, LoadSuccess, LoadFailure, Create, Update, Delete)
- Reducers: Pure functions, State transformieren
- Effects: Async Operations, Service-Calls, Dispatcher
UI Pattern (Blazor + MudBlazor)
- @inherits FluxorComponent
- @attribute [Authorize(Policy = "...")]
- MudTable für Listen
- MudDialog für Create/Edit
- MudForm mit Validation
Referenzdateien für Patterns
Service-Pattern:
src/Koogle.Application/Services/UserService.cs
Fluxor-Pattern:
src/Koogle.Web/Store/AuthState/AuthState.cssrc/Koogle.Web/Store/AuthState/AuthActions.cssrc/Koogle.Web/Store/AuthState/AuthReducers.cssrc/Koogle.Web/Store/AuthState/AuthEffects.cs
AutoMapper:
src/Koogle.Application/Mapping/UserProfile.cs
DI:
src/Koogle.Infrastructure/DependencyInjection.cssrc/Koogle.Application/DependencyInjection.cs
Domain:
src/Koogle.Domain/Entities/BaseEntity.cs
Berechtigungen pro Feature
- Club-Verwaltung: SuperAdmin only
- Person/Expense/Day CRUD: ClubEditor+
- Dashboard/Auswertungen: ClubViewer+
Policy: @attribute [Authorize(Policy = "ClubViewer|ClubEditor|ClubAdmin")]
Zusammenfassung Phase 1
23 feine Phasen → ~75 Dateien → MVP Phase 1 komplett ✓
IMPLEMENTIERUNGSPLAN - Phase 2: Detaillierte Spielverwaltung
Übersicht
Integration des Game-Management-Systems aus KoogleApp in Koogle.Web mit Clean Architecture. Spiele laufen im Speicher auf der DayDetails-Seite ohne Browser-Reload.
Geklärte Anforderungen
| Aspekt | Entscheidung |
|---|---|
| Spiel-Ende | Auto-Ende + manuelle "Beenden" Option |
| Strafen-Schnellwahl | Nur im Details-Tab, nicht in Eingabe/Tafel |
| Multi-Game | Sequentiell - mehrere Spiele pro Tag, Liste sichtbar |
| Persistenz | JSON in Game.GameData nach Spielende speichern |
| Scheiss-Spiel Ende | Erster mit 0 Punkten gewinnt, andere bekommen Strafen nach Restpunkten |
| Strafen-Berechnung | Fixbetrag pro Restpunkt (z.B. 0.10€ × 30 Punkte = 3.00€) |
Design-Entscheidungen
| Entscheidung | Lösung |
|---|---|
| State-Speicherung | Separater GameState (nicht in DayState) |
| View-Wechsel | ViewMode-Enum + MudTabs (kein URL-Routing) |
| Spieltyp-System | IGameDefinition in Domain, Registry in Application |
| Pin-Komponenten | Port aus KoogleApp mit neuem Namespace |
| Persistenz | In-Memory während Spiel → DB (Game.GameData) bei Ende |
Existierende Entity
// Koogle.Domain/Entities/Game.cs - bereits vorhanden
public class Game : BaseEntity
{
public string GameData { get; set; } = "{}"; // JSON für Spielergebnis
public Guid DayId { get; set; }
public Day Day { get; set; }
public ICollection<GamePerson> GamePersons { get; set; }
public Guid ClubId { get; set; }
public Club Club { get; set; }
}
// → GameType Property hinzufügen (string oder Enum)
Architektur
Domain: GameType Enum, IGameDefinition Interface
Application: Games/ mit GameProgress, IGameLogicService, Training/, Shit/
Web: Store/GameState/, Components/Game/
Umsetzungsreihenfolge Phase 2
| ☐ | Phase | Bereich | Beschreibung | Dateien |
|---|---|---|---|---|
| ☐ | H1 | Foundation | GameState Fluxor | 5 |
| ☐ | H2 | Components | Pin Input Components | 5 |
| ☐ | H3 | Framework | Game Definition Framework | 5 |
| ☐ | H4 | Games | Training Game | 5 |
| ☐ | H5 | Games | Scheiss-Spiel | 5 |
| ☐ | H6 | UI | Game Setup Dialog | 4 |
| ☐ | H7 | Integration | DayDetails Integration | 3 |
| ☐ | H8 | Features | Undo/Redo | 3 |
| ☐ | H9 | Features | Session Persistence | 2 |
| ☐ | H10 | Testing | Game Tests | 3 |
Geschätzte Dateien: ~42 neue/modifizierte
Detaillierte Phasen
Phase H1: GameState Foundation
Dateien:
src/Koogle.Web/Store/GameState/GameState.cssrc/Koogle.Web/Store/GameState/GameActions.cssrc/Koogle.Web/Store/GameState/GameReducers.cssrc/Koogle.Web/Store/GameState/GameEffects.cssrc/Koogle.Domain/Enums/GameType.cs
State-Struktur:
[FeatureState]
public record GameState
{
public bool IsGameActive { get; init; }
public string? GameTypeName { get; init; } // "Training", "Shit"
public Guid? DayId { get; init; }
public Guid? ActiveGameId { get; init; }
public ThrowPanelState ThrowPanel { get; init; }
public ParticipantsState Participants { get; init; }
public object? GameModel { get; init; } // JSON-serializable game-specific data
public UndoRedoState UndoRedo { get; init; }
public IReadOnlyList<GameSummaryDto> CompletedGames { get; init; }
public bool IsLoading { get; init; }
public string? Error { get; init; }
}
public record ThrowPanelState(
bool IsStarted,
PinStatus Pin1, PinStatus Pin2, PinStatus Pin3,
PinStatus Pin4, PinStatus Pin5, PinStatus Pin6,
PinStatus Pin7, PinStatus Pin8, PinStatus Pin9,
int ThrowsPerRound,
int ThrowCounterPerRound,
ThrowMode ThrowMode,
int TotalThrowCounter,
bool BellValue
);
public record ParticipantsState(
Guid[] PlayerIds,
int CurrentPlayerIndex,
ParticipantsMode Mode
);
Phase H2: Pin Input Components
Dateien (Port aus KoogleApp):
src/Koogle.Web/Components/Game/Pin.razorsrc/Koogle.Web/Components/Game/PinPanel.razorsrc/Koogle.Web/Components/Game/NumberPanel.razorsrc/Koogle.Web/Components/Game/ThrowPanel.razorsrc/Koogle.Web/Components/Game/GameInputPanel.razor
Pin-Layout (9 Kegel):
①
② ③
④ ⑤ ⑥
⑦ ⑧
⑨
Features:
- Pin.razor: Einzelner Kegel, klickbar, Status (Standing/Fallen/Disabled)
- PinPanel.razor: 9-Pin-Layout, Touch-optimiert
- NumberPanel.razor: Schnelleingabe 1-9, Wurf-Button, Glocke
- ThrowPanel.razor: Wurf-Bestätigung, Rinnen links/rechts
- GameInputPanel.razor: Orchestriert alles, zeigt aktuellen Spieler
Phase H3: Game Definition Framework
Dateien:
src/Koogle.Domain/Interfaces/IGameDefinition.cssrc/Koogle.Application/Games/GameProgress.cssrc/Koogle.Application/Games/IGameLogicService.cssrc/Koogle.Application/Games/GameDefinitionRegistry.cssrc/Koogle.Application/DependencyInjection.cs(erweitern)
IGameDefinition:
public interface IGameDefinition
{
string Name { get; } // "Training"
string DisplayName { get; } // "Kegel-Training"
Type SetupComponentType { get; }
Type BoardComponentType { get; }
Type GameLogicServiceType { get; }
Type GameModelType { get; }
}
GameProgress (Port):
- BeforeThrowState, AfterThrowState
- PinCount(), IsCircle() Extension Methods
- NextRound(), EndOfGame() Helpers
Phase H4: Training Game
Dateien:
src/Koogle.Application/Games/Training/TrainingGameDefinition.cssrc/Koogle.Application/Games/Training/TrainingGameModel.cssrc/Koogle.Application/Games/Training/TrainingGameLogicService.cssrc/Koogle.Web/Components/Game/Training/TrainingSetup.razorsrc/Koogle.Web/Components/Game/Training/TrainingBoard.razor
TrainingGameModel:
public record TrainingGameModel
{
public Dictionary<Guid, PlayerStats> PlayerStatistics { get; init; }
}
public record PlayerStats
{
public int ThrowCount { get; set; }
public int PinCount { get; set; }
public int CircleCount { get; set; } // Kränze (8+1)
public int StrikeCount { get; set; } // Alle 9
}
TrainingSetup.razor:
- ThrowMode: Reposition (in die Vollen) / Decrease (Abräumen)
- ThrowsPerRound: 1-5
- ParticipantsMode: GameLogic (Rotation) / FreeToChoose
TrainingBoard.razor (Tafel):
| Spieler | Würfe | Kegel | Kränze | ⌀ |
|---|---|---|---|---|
| Max | 24 | 156 | 3 | 6.5 |
Phase H5: Scheiss-Spiel
Dateien:
src/Koogle.Application/Games/Shit/ShitGameDefinition.cssrc/Koogle.Application/Games/Shit/ShitGameModel.cssrc/Koogle.Application/Games/Shit/ShitGameLogicService.cssrc/Koogle.Web/Components/Game/Shit/ShitSetup.razorsrc/Koogle.Web/Components/Game/Shit/ShitBoard.razor
ShitGameModel:
public record ShitGameModel
{
public int ShitNumber { get; init; } // 1-9 (Scheiss-Zahl)
public int StartNumber { get; init; } // 10-1000 (Startzahl)
public decimal PricePerPoint { get; init; } // 0.01-1.00 (€ pro Restpunkt)
public Dictionary<Guid, int> PlayerPoints { get; init; }
public int CollectedPoints { get; set; }
public Guid? WinnerId { get; set; }
}
Spiellogik:
- Spieler wirft
- Wenn Scheiss-Zahl oder Rinne → gesammelte Punkte werden vom Konto abgezogen, nächster Spieler
- Sonst → Punkte werden gesammelt (addiert)
- Erster mit 0 Punkten → GEWINNER
- Spiel endet, alle anderen bekommen Strafe:
Restpunkte × PricePerPoint
ShitSetup.razor:
- Scheiss-Zahl (1-9, Default: 5)
- Start-Zahl (10-1000, Default: 50)
- Betrag pro Punkt (0.01€ - 1.00€, Default: 0.10€)
ShitBoard.razor (Tafel):
| Spieler | Punkte | Status |
|---|---|---|
| Max | 23 | 🎯 |
| Anna | 0 | 🏆 GEWINNER |
Phase H6: Game Setup Dialog
Dateien:
src/Koogle.Web/Components/Game/GameSetupDialog.razorsrc/Koogle.Web/Components/Game/GameTypeSelector.razorsrc/Koogle.Web/Components/Game/ParticipantSelector.razorsrc/Koogle.Web/Components/Game/CommonSetupOptions.razor
Flow:
- Dialog öffnen
- Spieltyp auswählen (Training, Scheiss-Spiel)
- DynamicComponent rendert spieltyp-spezifisches Setup
- Teilnehmer aus Day-Participants auswählen
- "Spiel starten" →
StartGameActiondispatch
Phase H7: DayDetails Integration
Dateien ändern:
src/Koogle.Web/Components/Pages/Days/DayDetails.razorsrc/Koogle.Web/Components/Game/GameBoardPanel.razorsrc/Koogle.Web/Components/Game/CompletedGamesList.razor
DayDetails Erweiterung:
@code {
private int _activeTabIndex = 0;
}
<MudTabs @bind-ActivePanelIndex="_activeTabIndex">
<MudTabPanel Text="Details" Icon="@Icons.Material.Filled.Info">
<!-- Bestehend: Participants + Expenses + Quick-Assign -->
</MudTabPanel>
<MudTabPanel Text="Eingabe" Icon="@Icons.Material.Filled.SportsScore"
Disabled="@(!GameState.Value.IsGameActive)">
<GameInputPanel />
</MudTabPanel>
<MudTabPanel Text="Tafel" Icon="@Icons.Material.Filled.TableChart"
Disabled="@(!GameState.Value.IsGameActive)">
<GameBoardPanel />
</MudTabPanel>
</MudTabs>
<!-- Spiel starten Button (nur wenn Day.Status == Started und kein aktives Spiel) -->
@if (SelectedDay?.Status == DayStatus.Started && !GameState.Value.IsGameActive)
{
<MudButton OnClick="OpenGameSetupDialog" Color="Color.Primary">
Neues Spiel starten
</MudButton>
}
<!-- Liste abgeschlossener Spiele -->
<CompletedGamesList DayId="DayId" />
Phase H8: Undo/Redo
Dateien:
src/Koogle.Web/Store/GameState/UndoRedoState.cssrc/Koogle.Web/Store/GameState/UndoRedoActions.cssrc/Koogle.Web/Components/Game/UndoRedoButtons.razor
Logic:
- Jeder Wurf erstellt Snapshot
- Stack für Undo (max 20 Einträge)
- "Letzten Wurf rückgängig" Button
Phase H9: Session Persistence
Dateien:
src/Koogle.Web/wwwroot/js/gameSession.jssrc/Koogle.Web/Store/GameState/GameEffects.cs(erweitern)
Mechanismus:
- Bei State-Änderung: SessionStorage speichern
- Bei Page-Load: SessionStorage prüfen
- Wenn Session für aktuellen DayId existiert: Restore anbieten
Phase H10: Testing
Dateien:
test/Koogle.Tests/Application/Games/TrainingGameLogicServiceTests.cstest/Koogle.Tests/Application/Games/ShitGameLogicServiceTests.cstest/Koogle.Tests/Web/GameStateReducersTests.cs
Test-Cases:
- Pin-Count Berechnung
- Kranz-Erkennung (8 äußere + Mitte steht)
- Scheiss-Spiel: Gewinner bei 0
- Scheiss-Spiel: Strafen-Berechnung
- Reducer: State-Transformationen
Scheiss-Spiel Spielende-Logik
1. Spieler erreicht 0 Punkte → GEWINNER (WinnerId setzen)
2. Spiel endet automatisch (IsGameActive = false)
3. Für jeden Verlierer:
- Strafe = Restpunkte × Betrag_pro_Punkt
- PersonExpense erstellen (ExpenseType: GamePenalty)
4. Game.GameData speichern (JSON mit Ergebnis)
5. CompletedGames-Liste aktualisieren
Kritische Dateien (KoogleApp → Koogle.Web Port)
| Quelle (KoogleApp) | Ziel (Koogle.Web) |
|---|---|
src/KoogleApp/Games/GameProgress.cs |
src/Koogle.Application/Games/GameProgress.cs |
src/KoogleApp/Store/Game/ThrowPanel/State.cs |
In GameState.cs integriert |
src/KoogleApp/Games/Training/GameTrainingService.cs |
src/Koogle.Application/Games/Training/TrainingGameLogicService.cs |
src/KoogleApp/Components/Controls/PinPanel.razor |
src/Koogle.Web/Components/Game/PinPanel.razor |
src/KoogleApp/Components/Controls/NumberPanel.razor |
src/Koogle.Web/Components/Game/NumberPanel.razor |
Migration
# GameType Property zu Game Entity hinzufügen
dotnet ef migrations add AddGameType --project src/Koogle.Infrastructure --startup-project src/Koogle.Web --context AppDbContext --output-dir Data/Migrations
dotnet ef database update -p src/Koogle.Infrastructure -s src/Koogle.Web --context AppDbContext
Berechtigungen Phase 2
- Spiel starten/beenden: ClubEditor+
- Würfe eingeben: ClubEditor+
- Tafel ansehen: ClubViewer+
Zusammenfassung Phase 2
10 Phasen (H1-H10) → ~42 Dateien → Detaillierte Spielverwaltung
Bereit für Implementierung Phase H1