feat(diary): enhance predefined activities management and socket event handling
- Added new API endpoints for managing predefined activities, including retrieval, creation, updating, merging, and deduplication. - Updated socket event handling to improve the mapping of socket events to domain events, ensuring better integration with the diary service. - Enhanced repository mappers to support detailed mapping of predefined activities and training statistics, including images and member participation data. - Introduced new UI strings for managing predefined activities, improving user experience in the diary section.
This commit is contained in:
307
docs/api_contract.md
Normal file
307
docs/api_contract.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# API Contract (extrahiert aus Frontend + Backend-Routen)
|
||||
|
||||
## 1) Basis
|
||||
- Base URL (Frontend): `${VITE_BACKEND || (DEV ? 'http://localhost:3005' : window.location.origin)}/api`
|
||||
- Transport: JSON, bei Uploads `multipart/form-data`
|
||||
- Auth-Header (via Axios-Interceptor):
|
||||
- `authcode: <token>`
|
||||
- `userid: <username/email>`
|
||||
- Timeout: 60s (`apiClient`), einzelne Aufrufe überschreiben lokal
|
||||
|
||||
## 2) Globales Response-/Error-Format
|
||||
|
||||
### Erfolgsantwort
|
||||
- Meist `response.data` als fachliches Objekt/Array
|
||||
- Bei einigen Endpunkten explizit:
|
||||
- `{ success: true, ... }`
|
||||
- oder direkt Array/Objekt
|
||||
|
||||
### Fehlerantwort (Backend-Error-Handler)
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"code": "ERROR_...",
|
||||
"params": { },
|
||||
"error": "...",
|
||||
"message": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### Beobachtete HTTP-Fehler
|
||||
- `400` Bad Request
|
||||
- `401` Unauthorized (führt im Frontend zu auto-logout)
|
||||
- `403` Forbidden
|
||||
- `404` Not Found
|
||||
- `409` Conflict (z. B. Club existiert bereits)
|
||||
- `5xx` Serverfehler
|
||||
|
||||
### Bekannte Error-Codes (global)
|
||||
Quelle: `backend/constants/errorCodes.js`
|
||||
- Allgemein: `ERROR_INTERNAL_SERVER_ERROR`, `ERROR_UNKNOWN_ERROR`, `ERROR_VALIDATION_FAILED`, `ERROR_NOT_FOUND`, `ERROR_UNAUTHORIZED`, `ERROR_FORBIDDEN`, `ERROR_BAD_REQUEST`
|
||||
- Auth: `ERROR_USER_NOT_FOUND`, `ERROR_INVALID_PASSWORD`, `ERROR_LOGIN_FAILED`, `ERROR_SESSION_EXPIRED`
|
||||
- MyTischtennis: `ERROR_MYTISCHTENNIS_*`
|
||||
- Member: `ERROR_MEMBER_*`
|
||||
- Group: `ERROR_GROUP_*`
|
||||
- Tournament: `ERROR_TOURNAMENT_*`
|
||||
- Diary: `ERROR_DIARY_*`, `SUCCESS_DIARY_*`
|
||||
- Team: `ERROR_TEAM_*`
|
||||
- Activity: `ERROR_ACTIVITY_IMAGE_DELETE_FAILED`
|
||||
- Official Tournament: `SUCCESS_OFFICIAL_TOURNAMENT_PDF_UPLOAD`, `ERROR_OFFICIAL_TOURNAMENT_PDF_UPLOAD`
|
||||
- Club: `ERROR_CLUB_*`
|
||||
- Member Transfer: `ERROR_MEMBER_TRANSFER_BULK_FAILED`
|
||||
- Training Stats: `ERROR_TRAINING_STATS_LOAD_FAILED`
|
||||
- Logs: `ERROR_LOG_NOT_FOUND`
|
||||
|
||||
## 3) Endpoint-Katalog (im Frontend verwendet)
|
||||
|
||||
Hinweis: Payload/Response sind als **beobachtete Struktur** aus Aufrufstellen dokumentiert.
|
||||
|
||||
### Auth & Session
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| POST | `/auth/register` | `{ email, password }` | Erfolg ohne feste Struktur verwendet |
|
||||
| POST | `/auth/login` | `{ email, password }` | `{ token, ... }` |
|
||||
| GET | `/auth/activate/:activationCode` | - | Erfolg ohne Feldauswertung |
|
||||
| POST | `/auth/forgot-password` | `{ email }` | Erfolg ohne Feldauswertung |
|
||||
| POST | `/auth/reset-password` | `{ token, password }` | Erfolg ohne Feldauswertung |
|
||||
| GET | `/session/status` | - | `{ valid: boolean }` |
|
||||
|
||||
### Clubs & Approvals
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/clubs` | - | `Club[]` |
|
||||
| POST | `/clubs` | `{ name }` | neuer Club (ID wird weiterverwendet) |
|
||||
| GET | `/clubs/:clubId` | - | Club-Objekt |
|
||||
| PUT | `/clubs/:clubId/settings` | Club-Settings-Objekt | Erfolg |
|
||||
| GET | `/clubs/request/:clubId` | - | Erfolg |
|
||||
| GET | `/clubs/pending/:clubId` | - | Pending-Approvals-Liste |
|
||||
| POST | `/clubs/approve` | `{ clubId, userId, ... }` | Erfolg |
|
||||
| POST | `/clubs/reject` | `{ clubId, userId, ... }` | Erfolg |
|
||||
|
||||
### Permissions
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/permissions/:clubId` | - | `{ role, isOwner, permissions }` |
|
||||
| GET | `/permissions/roles/available` | - | `Role[]` |
|
||||
| GET | `/permissions/structure/all` | - | Permission-Struktur |
|
||||
| GET | `/permissions/:clubId/members` | optional `?t=<timestamp>` | Mitglieder + Rollen/Permissions |
|
||||
| PUT | `/permissions/:clubId/user/:userId/role` | `{ role }` | Erfolg |
|
||||
| PUT | `/permissions/:clubId/user/:userId/status` | `{ approved }` | Erfolg |
|
||||
| PUT | `/permissions/:clubId/user/:userId/permissions` | `{ permissions: {resource:{action:boolean}} }` | Erfolg |
|
||||
|
||||
### Members
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/clubmembers/get/:clubId/:showAll` | - | `Member[]` |
|
||||
| GET | `/clubmembers/notapproved/:clubId` | - | `Member[]` |
|
||||
| POST | `/clubmembers/set/:clubId` | Member-/Bulk-Payload | Erfolg/Member |
|
||||
| POST | `/clubmembers/update-ratings/:clubId` | - | Erfolg |
|
||||
| POST | `/clubmembers/quick-update-test-membership/:clubId/:memberId` | - | Erfolg |
|
||||
| POST | `/clubmembers/quick-update-member-form/:clubId/:memberId` | - | Erfolg |
|
||||
| POST | `/clubmembers/quick-deactivate/:clubId/:memberId` | - | Erfolg |
|
||||
| GET | `/clubmembers/gallery/:clubId?format=json&size=:size` | - | Galerie-Metadaten |
|
||||
| POST | `/clubmembers/image/:clubId/:memberId` | `multipart/form-data` | Erfolg |
|
||||
| GET | `/clubmembers/image/:clubId/:memberId` | - | Bild/Binärdaten |
|
||||
| GET | `/clubmembers/image/:clubId/:memberId/:imageId` | - | Bild/Binärdaten |
|
||||
| DELETE | `/clubmembers/image/:clubId/:memberId/:imageId` | - | Erfolg |
|
||||
| POST | `/clubmembers/image/:clubId/:memberId/:imageId/primary` | - | Erfolg |
|
||||
| POST | `/clubmembers/rotate-image/:clubId/:memberId/:imageId` | `{ direction }` | Erfolg |
|
||||
| POST | `/clubmembers/transfer/:clubId` | Transfer-Konfig inkl. Endpoint/Method/Template/Login | `{ success, transferred, total, invalidMembers?, errors? }` |
|
||||
|
||||
### Diary, Participants, Activities, Tags, Notes
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/diary/:clubId` | - | DiaryDate[] |
|
||||
| POST | `/diary/:clubId` | Datum/Trainingsdaten | neue Date |
|
||||
| PUT | `/diary/:clubId` | Trainingszeiten-Update | Erfolg |
|
||||
| DELETE | `/diary/:clubId/:dateId` | - | Erfolg |
|
||||
| GET | `/participants/:dateId` | - | Participant[] |
|
||||
| POST | `/participants/add` | `{ dateId, memberId, ... }` | neuer Participant |
|
||||
| POST | `/participants/remove` | `{ dateId, participantId/memberId }` | Erfolg |
|
||||
| PUT | `/participants/:dateId/:memberId/group` | `{ groupId }` | Erfolg |
|
||||
| GET | `/activities/:dateId` | - | Activity[] |
|
||||
| POST | `/activities/add` | Activity-Payload | neue Activity |
|
||||
| GET | `/group/:clubId/:dateId` | - | Group[] |
|
||||
| POST | `/group` | Group-Form | Erfolg |
|
||||
| PUT | `/group/:groupId` | Group-Änderung | Erfolg |
|
||||
| DELETE | `/group/:groupId` | - | Erfolg |
|
||||
| GET | `/tags` | - | Tag[] |
|
||||
| POST | `/tags` | `{ name }` | Tag |
|
||||
| POST | `/diary/tag/:clubId/add-tag` | `{ diaryDateId, tagId }` | Erfolg |
|
||||
| DELETE | `/diary/:clubId/tag` | Payload in `data` | Erfolg |
|
||||
| GET | `/notes?diaryDateId=:id&memberId=:id` | - | Note[] |
|
||||
| GET | `/diarymember/:clubId/note` | Query-Filter | Note[] |
|
||||
| POST | `/diarymember/:clubId/note` | Note-Payload | Erfolg |
|
||||
| DELETE | `/diarymember/:clubId/note/:noteId` | Query-Filter möglich | Erfolg |
|
||||
| POST | `/diarymember/:clubId/note/remove` | Entfernen-Bulk | Erfolg |
|
||||
| GET | `/diarymember/:clubId/tag` | Query-Filter | Tag[] |
|
||||
| POST | `/diarymember/:clubId/tag` | Tag-Zuordnung | Erfolg |
|
||||
| POST | `/diarymember/:clubId/tag/remove` | Tag-Entfernung | Erfolg |
|
||||
| POST | `/diarydatetags/:clubId` | Tag-Payload | Erfolg |
|
||||
| GET | `/diary-date-activities/:clubId/:dateId` | - | Plan-Items[] |
|
||||
| POST | `/diary-date-activities/:clubId` | Plan-Item | Erfolg |
|
||||
| PUT | `/diary-date-activities/:clubId/:id` | Plan-Update | Erfolg |
|
||||
| PUT | `/diary-date-activities/:clubId/:id/order` | `{ order }` | Erfolg |
|
||||
| DELETE | `/diary-date-activities/:clubId/:id` | - | Erfolg |
|
||||
| POST | `/diary-date-activities/group` | Group-Plan-Payload | Erfolg |
|
||||
| PUT | `/diary-date-activities/group/:clubId/:groupActivityId` | Group-Plan-Update | Erfolg |
|
||||
| DELETE | `/diary-date-activities/group/:clubId/:groupActivityId` | - | Erfolg |
|
||||
| GET | `/diary-member-activities/:clubId/:diaryDateActivityId` | - | ParticipantIds/Member[] |
|
||||
| POST | `/diary-member-activities/:clubId/:diaryDateActivityId` | `{ participantIds: [] }` | Erfolg |
|
||||
| DELETE | `/diary-member-activities/:clubId/:diaryDateActivityId/:participantId` | - | Erfolg |
|
||||
| POST | `/accident` | Accident-Objekt | Erfolg |
|
||||
| GET | `/accident/:clubId/:dateId` | - | Accident[] |
|
||||
|
||||
### Predefined Activities
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/predefined-activities` | - | ActivityTemplate[] |
|
||||
| GET | `/predefined-activities/:id` | - | ActivityTemplate-Detail |
|
||||
| GET | `/predefined-activities/search/query?q=:term&limit=:n` | - | Trefferliste |
|
||||
| POST | `/predefined-activities` | Template-Payload | neues Template |
|
||||
| PUT | `/predefined-activities/:id` | Update-Payload | aktualisiertes Template |
|
||||
| POST | `/predefined-activities/merge` | `{ sourceId, targetId }` | Erfolg |
|
||||
| POST | `/predefined-activities/deduplicate` | `{}` | Erfolg |
|
||||
| DELETE | `/predefined-activities/:id/image/:imageId` | - | Erfolg |
|
||||
|
||||
### Training Groups / Times / Stats
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/training-groups/:clubId` | - | Group[] |
|
||||
| GET | `/training-groups/:clubId/member/:memberId` | - | Group[] |
|
||||
| POST | `/training-groups/:clubId` | Group-Payload | Erfolg |
|
||||
| PUT | `/training-groups/:clubId/:groupId` | Group-Payload | Erfolg |
|
||||
| DELETE | `/training-groups/:clubId/:groupId` | - | Erfolg |
|
||||
| POST | `/training-groups/:clubId/:groupId/member/:memberId` | - | Erfolg |
|
||||
| DELETE | `/training-groups/:clubId/:groupId/member/:memberId` | - | Erfolg |
|
||||
| GET | `/training-times/:clubId` | - | Time[] |
|
||||
| POST | `/training-times/:clubId` | Time-Payload | Erfolg |
|
||||
| PUT | `/training-times/:clubId/:timeId` | Time-Payload | Erfolg |
|
||||
| DELETE | `/training-times/:clubId/:timeId` | - | Erfolg |
|
||||
| GET | `/training-stats/:clubId` | - | Statistikobjekt |
|
||||
| GET | `/member-activities/:clubId/:memberId` | `period` als Query | Aktivitäten-Stats |
|
||||
| GET | `/member-activities/:clubId/:memberId/last-participations?limit=3` | - | letzte Teilnahmen |
|
||||
|
||||
### Matches / Schedule
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| POST | `/matches/import` | CSV `multipart/form-data` | Erfolg/Import-Ergebnis |
|
||||
| GET | `/matches/leagues/:clubId/matches/:leagueId` | optional `seasonid` | Match[] |
|
||||
| GET | `/matches/leagues/:clubId/matches` | optional `seasonid` | Match[] |
|
||||
| GET | `/matches/leagues/:clubId/table/:leagueId` | - | Tabellenobjekt |
|
||||
| POST | `/matches/leagues/:clubId/table/:leagueId/fetch` | - | Fetch-Ergebnis |
|
||||
| GET | `/matches/leagues/:clubId/stats/:leagueId` | optional `seasonid` | Player-Stats[] |
|
||||
| PATCH | `/matches/:matchId/players` | Spielerzuordnung | Erfolg |
|
||||
|
||||
### Club Teams & Team Documents
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/club-teams/club/:clubId` | optional `seasonid` | Team[] |
|
||||
| GET | `/club-teams/leagues/:clubId` | optional `seasonid` | League[] |
|
||||
| POST | `/club-teams/club/:clubId` | `teamData` | Team |
|
||||
| PUT | `/club-teams/:clubTeamId` | `teamData` | Erfolg |
|
||||
| DELETE | `/club-teams/:clubTeamId` | - | Erfolg |
|
||||
| GET | `/team-documents/club-team/:clubTeamId` | - | Document[] |
|
||||
| POST | `/team-documents/club-team/:clubTeamId/upload` | PDF `multipart/form-data` | `{ id, ... }` |
|
||||
| POST | `/team-documents/:documentId/parse?leagueid=:leagueId` | - | Parse-Ergebnis |
|
||||
| GET | `/team-documents/:documentId/download` | - | PDF/Binärdaten |
|
||||
|
||||
### Tournaments
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/tournament/:clubId` | optional `?type=mini` | Tournament[] |
|
||||
| GET | `/tournament/:clubId/:tournamentId` | - | Tournament-Detail |
|
||||
| PUT | `/tournament/:clubId/:tournamentId` | `{ numberOfTables, winningSets, ... }` | Erfolg |
|
||||
| POST | `/tournament` | neues Turnier | Erfolg |
|
||||
| POST | `/tournament/mini` | Mini-Format-Payload | Erfolg |
|
||||
| POST | `/tournament/participant` | Teilnehmer hinzufügen | Erfolg |
|
||||
| DELETE | `/tournament/participant` | `data: { clubId, tournamentId, ... }` | Erfolg |
|
||||
| POST | `/tournament/participants` | `{ clubId, tournamentId, classId }` | Participant[] |
|
||||
| PUT | `/tournament/participant/:clubId/:tournamentId/:participantId/seeded` | `{ seeded }` | Erfolg |
|
||||
| PUT | `/tournament/participant/:clubId/:tournamentId/:participantId/gave-up` | `{ gaveUp }` | Erfolg |
|
||||
| PUT | `/tournament/participant/:clubId/:tournamentId/:participantId/class` | `{ classId }` | Erfolg |
|
||||
| POST | `/tournament/external-participant` | externer Teilnehmer | Erfolg |
|
||||
| DELETE | `/tournament/external-participant` | `data` mit IDs | Erfolg |
|
||||
| POST | `/tournament/external-participants` | `{ clubId, tournamentId, classId }` | ExternalParticipant[] |
|
||||
| PUT | `/tournament/external-participant/:clubId/:tournamentId/:participantId/seeded` | `{ seeded }` | Erfolg |
|
||||
| PUT | `/tournament/external-participant/:clubId/:tournamentId/:participantId/gave-up` | `{ gaveUp }` | Erfolg |
|
||||
| GET | `/tournament/groups` | Query `{ clubId, tournamentId }` | Group[] |
|
||||
| PUT | `/tournament/groups` | Gruppenkonfiguration | Erfolg |
|
||||
| POST | `/tournament/groups/create` | Gruppen erstellen | Erfolg |
|
||||
| POST | `/tournament/groups` | Gruppen füllen | Erfolg |
|
||||
| POST | `/tournament/groups/reset` | IDs/Turnier | Erfolg |
|
||||
| POST | `/tournament/modus` | Modus-Einstellungen | Erfolg |
|
||||
| POST | `/tournament/matches/create` | Match-Erstellung | Erfolg |
|
||||
| POST | `/tournament/matches/reset` | Reset-Payload | Erfolg |
|
||||
| POST | `/tournament/matches/cleanup-orphaned` | IDs/Turnier | Cleanup-Result |
|
||||
| GET | `/tournament/matches/:clubId/:tournamentId` | - | Match[] |
|
||||
| POST | `/tournament/match/result` | Match-Resultat | Erfolg |
|
||||
| DELETE | `/tournament/match/result` | `data` mit Match-ID | Erfolg |
|
||||
| POST | `/tournament/match/finish` | Match-Finish-Payload | Erfolg |
|
||||
| POST | `/tournament/match/reopen` | Reopen-Payload | Erfolg |
|
||||
| PUT | `/tournament/match/:clubId/:tournamentId/:matchId/active` | `{ isActive }` | Erfolg |
|
||||
| PUT | `/tournament/match/:clubId/:tournamentId/:matchId/table` | `{ tableNumber }` | Erfolg |
|
||||
| POST | `/tournament/knockout` | Start-KO-Payload | Erfolg |
|
||||
| DELETE | `/tournament/matches/knockout` | `data: payload` | Erfolg |
|
||||
| POST | `/tournament/pools/merge` | Merge-Payload | Erfolg |
|
||||
| PUT | `/tournament/participant/group` | `{ participantId, groupId, ... }` | Erfolg |
|
||||
| GET | `/tournament/classes/:clubId/:tournamentId` | - | Class[] |
|
||||
| POST | `/tournament/class/:clubId/:tournamentId` | Klassen-Payload | Erfolg |
|
||||
| PUT | `/tournament/class/:clubId/:tournamentId/:classId` | Klassen-Update | Erfolg |
|
||||
| DELETE | `/tournament/class/:clubId/:tournamentId/:classId` | - | Erfolg |
|
||||
| GET | `/tournament/pairings/:clubId/:tournamentId/:classId` | - | Pairing[] |
|
||||
| POST | `/tournament/pairing/:clubId/:tournamentId/:classId` | Pairing-Payload | Erfolg |
|
||||
| PUT | `/tournament/pairing/:clubId/:tournamentId/:pairingId` | Pairing-Update | Erfolg |
|
||||
| DELETE | `/tournament/pairing/:clubId/:tournamentId/:pairingId` | - | Erfolg |
|
||||
| GET | `/tournament/stages` | Query `{ clubId, tournamentId }` | Stage[] |
|
||||
| PUT | `/tournament/stages` | Stage-Konfiguration | Erfolg |
|
||||
| POST | `/tournament/stages/advance` | Advancement-Payload | Erfolg |
|
||||
|
||||
### Official Tournaments
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/official-tournaments/:clubId` | - | Tournament[] |
|
||||
| GET | `/official-tournaments/:clubId/participations/summary` | - | Summary-Objekt |
|
||||
| POST | `/official-tournaments/:clubId/upload` | PDF `multipart/form-data` | `{ id, ... }` |
|
||||
| GET | `/official-tournaments/:clubId/:id` | - | Parsed Tournament |
|
||||
| POST | `/official-tournaments/:clubId/:id/participation` | Teilnahme-Payload | Erfolg |
|
||||
| POST | `/official-tournaments/:clubId/:id/status` | Status-Payload | Erfolg |
|
||||
| DELETE | `/official-tournaments/:clubId/:id` | - | Erfolg |
|
||||
|
||||
### MyTischtennis
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/mytischtennis/account` | - | Account-Objekt |
|
||||
| GET | `/mytischtennis/status` | - | Status-Objekt |
|
||||
| POST | `/mytischtennis/verify` | `{}` oder Login-Daten | Verify-Ergebnis |
|
||||
| POST | `/mytischtennis/account` | Account-Payload | Erfolg |
|
||||
| DELETE | `/mytischtennis/account` | - | Erfolg |
|
||||
| GET | `/mytischtennis/update-history` | - | Historie[] |
|
||||
| POST | `/mytischtennis/parse-url` | `{ url, ... }` | Parse-Result |
|
||||
| POST | `/mytischtennis/configure-team` | Team-Konfig | Erfolg |
|
||||
| POST | `/mytischtennis/configure-league` | League-Konfig | Erfolg |
|
||||
| POST | `/mytischtennis/fetch-team-data/async` | `{ clubTeamId }` | `{ jobId }` |
|
||||
| GET | `/mytischtennis/fetch-team-data/jobs/:jobId` | - | `{ job: { status, result?, error? } }` |
|
||||
|
||||
### Logs
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| GET | `/logs` | Query-Filter (`params`) | Log-Liste |
|
||||
| GET | `/logs/:id` | - | Log-Detail |
|
||||
| GET | `/logs/scheduler/last-executions` | optional Club-Query | Scheduler-Logs |
|
||||
|
||||
### NuScore (direkte `fetch`-Aufrufe)
|
||||
| Methode | URL | Request-Payload | Response (beobachtet) |
|
||||
|---|---|---|---|
|
||||
| POST | `/nuscore/init-cookies/:code` | - | Erfolg |
|
||||
| GET | `/nuscore/meetinginfo/:code` | - | Meeting-Objekt |
|
||||
| GET | `/nuscore/meetingdetails/:uuid` | - | Meeting-Detail |
|
||||
| PUT | `/nuscore/validate/:uuid` | komplettes `matchData` | `{ validationWarnings?, validationErrors?, ... }` |
|
||||
| PUT | `/nuscore/submit/:uuid` | komplettes `matchData` | Erfolgs-/Fehlerobjekt |
|
||||
| POST | `/nuscore/broadcast-draft` | `{ clubId, gameCode, matchData }` | Erfolg |
|
||||
|
||||
## 4) Dynamische/indirekte API-Aufrufe
|
||||
- `GET image.apiPath` in `MembersView.vue` (Blob-Download)
|
||||
- `GET /:path` in `DiaryView.vue` (Blob-Download über dynamischen Pfad)
|
||||
- `fetch(resource.url)` in `NuscoreAnalyzer.vue` ist extern (kein Backend-API-Contract)
|
||||
|
||||
15
docs/diaryview_port.md
Normal file
15
docs/diaryview_port.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# DiaryView Port Status
|
||||
|
||||
## Umgesetzt in diesem Durchlauf
|
||||
- Keine Code-Implementierung vorgenommen.
|
||||
- Stattdessen erstellt: `docs/plan_diaryview.md`.
|
||||
|
||||
## Grund
|
||||
Die Rest-Portierung von `DiaryView.vue` in 1:1-Nähe überschreitet in einem Durchlauf realistisch die Vorgabe von max. 40 geänderten Dateien.
|
||||
|
||||
## Unterschiede zum Web
|
||||
- Android bleibt auf dem bereits zuvor migrierten Teilstand.
|
||||
- Die noch offenen Web-Funktionsblöcke sind in `docs/plan_diaryview.md` strukturiert aufgeführt.
|
||||
|
||||
## Offene TODOs
|
||||
- Umsetzung gemäß Blöcken A–F aus `docs/plan_diaryview.md`.
|
||||
146
docs/frontend_dependency_graph.md
Normal file
146
docs/frontend_dependency_graph.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Vue Frontend Dependency Graph
|
||||
|
||||
Stand: 2026-03-05
|
||||
|
||||
## Scope
|
||||
- Analysiert: `frontend/src/views`, `frontend/src/components`, `frontend/src/services`, `frontend/src/composables`, `frontend/src/store.js`, `frontend/src/services/socketService.js`, `frontend/src/router.js`
|
||||
- Ziel: Abhängigkeiten pro View (Components, Services, Composables, Store, Socket Events)
|
||||
|
||||
## Knoten
|
||||
- Views: 26
|
||||
- Components: 32
|
||||
- Services: 1
|
||||
- Composables: 2
|
||||
- Store State Keys: 9
|
||||
- Store Getters: 15
|
||||
- Store Actions: 12
|
||||
|
||||
## Store (global)
|
||||
- State: token, username, currentClub, clubs, permissions, dialogs, dialogCounter, sidebarCollapsed, language
|
||||
- Getters: isAuthenticated, token, username, currentClub, clubs, sidebarCollapsed, language, currentClubName, currentPermissions, hasPermission, isClubOwner, userRole, dialogs, minimizedDialogs, activeDialogs
|
||||
- Actions: login, logout, setCurrentClub, loadPermissions, setClubs, toggleSidebar, setLanguage, openDialog, closeDialog, minimizeDialog, restoreDialog, bringDialogToFront
|
||||
|
||||
## Socket Events (Service-Vertrag)
|
||||
- `onActivityChanged` -> `activity:changed`
|
||||
- `offActivityChanged` -> `activity:changed`
|
||||
- `onActivityMemberAdded` -> `activity:member:added`
|
||||
- `offActivityMemberAdded` -> `activity:member:added`
|
||||
- `onActivityMemberRemoved` -> `activity:member:removed`
|
||||
- `offActivityMemberRemoved` -> `activity:member:removed`
|
||||
- `onDiaryDateUpdated` -> `diary:date:updated`
|
||||
- `offDiaryDateUpdated` -> `diary:date:updated`
|
||||
- `onDiaryNoteAdded` -> `diary:note:added`
|
||||
- `offDiaryNoteAdded` -> `diary:note:added`
|
||||
- `onDiaryNoteDeleted` -> `diary:note:deleted`
|
||||
- `offDiaryNoteDeleted` -> `diary:note:deleted`
|
||||
- `onDiaryNoteUpdated` -> `diary:note:updated`
|
||||
- `offDiaryNoteUpdated` -> `diary:note:updated`
|
||||
- `onDiaryTagAdded` -> `diary:tag:added`
|
||||
- `offDiaryTagAdded` -> `diary:tag:added`
|
||||
- `onDiaryTagRemoved` -> `diary:tag:removed`
|
||||
- `offDiaryTagRemoved` -> `diary:tag:removed`
|
||||
- `onGroupChanged` -> `group:changed`
|
||||
- `offGroupChanged` -> `group:changed`
|
||||
- `onMemberChanged` -> `member:changed`
|
||||
- `offMemberChanged` -> `member:changed`
|
||||
- `onParticipantAdded` -> `participant:added`
|
||||
- `offParticipantAdded` -> `participant:added`
|
||||
- `onParticipantRemoved` -> `participant:removed`
|
||||
- `offParticipantRemoved` -> `participant:removed`
|
||||
- `onParticipantUpdated` -> `participant:updated`
|
||||
- `offParticipantUpdated` -> `participant:updated`
|
||||
- `onMatchReportSubmitted` -> `schedule:match-report:submitted`
|
||||
- `offMatchReportSubmitted` -> `schedule:match-report:submitted`
|
||||
- `onScheduleMatchUpdated` -> `schedule:match:updated`
|
||||
- `offScheduleMatchUpdated` -> `schedule:match:updated`
|
||||
- `onTournamentChanged` -> `tournament:changed`
|
||||
- `offTournamentChanged` -> `tournament:changed`
|
||||
|
||||
## Component -> Views
|
||||
- `AccidentFormDialog.vue`: DiaryView.vue
|
||||
- `BaseDialog.vue`: DiaryView.vue, MembersView.vue, OfficialTournaments.vue, ScheduleView.vue
|
||||
- `ConfirmDialog.vue`: Activate.vue, ClubView.vue, CreateClub.vue, DiaryView.vue, Login.vue, MemberTransferSettingsView.vue, MembersView.vue, MyTischtennisAccount.vue, OfficialTournaments.vue, PendingApprovalsView.vue, PermissionsView.vue, PredefinedActivities.vue, Register.vue, ScheduleView.vue, TeamManagementView.vue, TournamentTab.vue
|
||||
- `CourtDrawingDialog.vue`: DiaryView.vue, PredefinedActivities.vue
|
||||
- `CourtDrawingRender.vue`: DiaryView.vue
|
||||
- `CourtDrawingTool.vue`: nicht direkt in Views verwendet
|
||||
- `CsvImportDialog.vue`: ScheduleView.vue
|
||||
- `DialogExamples.vue`: nicht direkt in Views verwendet
|
||||
- `DialogManager.vue`: nicht direkt in Views verwendet
|
||||
- `ImageDialog.vue`: DiaryView.vue
|
||||
- `ImageViewerDialog.vue`: MembersView.vue
|
||||
- `InfoDialog.vue`: Activate.vue, ClubView.vue, CreateClub.vue, DiaryView.vue, ForgotPassword.vue, Login.vue, LogsView.vue, MemberTransferSettingsView.vue, MembersView.vue, MyTischtennisAccount.vue, OfficialTournaments.vue, PendingApprovalsView.vue, PermissionsView.vue, PredefinedActivities.vue, Register.vue, ResetPassword.vue, ScheduleView.vue, TeamManagementView.vue, TournamentTab.vue
|
||||
- `MatchAccountDialog.vue`: nicht direkt in Views verwendet
|
||||
- `MatchReportApiDialog.vue`: ScheduleView.vue
|
||||
- `MatchReportDialog.vue`: nicht direkt in Views verwendet
|
||||
- `MatchReportHeaderActions.vue`: nicht direkt in Views verwendet
|
||||
- `MemberActivitiesDialog.vue`: MembersView.vue
|
||||
- `MemberActivityStatsDialog.vue`: DiaryView.vue
|
||||
- `MemberGalleryDialog.vue`: DiaryView.vue
|
||||
- `MemberNotesDialog.vue`: DiaryView.vue, MembersView.vue
|
||||
- `MemberSelectionDialog.vue`: OfficialTournaments.vue
|
||||
- `MemberTransferDialog.vue`: MembersView.vue
|
||||
- `MyTischtennisDialog.vue`: MyTischtennisAccount.vue
|
||||
- `MyTischtennisHistoryDialog.vue`: nicht direkt in Views verwendet
|
||||
- `NuscoreAnalyzer.vue`: nicht direkt in Views verwendet
|
||||
- `PDFGenerator.js`: DiaryView.vue, MembersView.vue, OfficialTournaments.vue, ScheduleView.vue, TournamentTab.vue
|
||||
- `QuickAddMemberDialog.vue`: DiaryView.vue
|
||||
- `SeasonSelector.vue`: ScheduleView.vue, TeamManagementView.vue
|
||||
- `TagHistoryDialog.vue`: DiaryView.vue
|
||||
- `TrainingDetailsDialog.vue`: TrainingStatsView.vue
|
||||
- `TrainingGroupsTab.vue`: ClubSettings.vue
|
||||
- `TrainingTimesTab.vue`: ClubSettings.vue
|
||||
|
||||
## Service -> Views
|
||||
- `socketService.js`: DiaryView.vue, ScheduleView.vue, TournamentTab.vue
|
||||
|
||||
## Composable -> Views
|
||||
- `useDialog.js`: nicht direkt in Views verwendet
|
||||
- `usePermissions.js`: PermissionsView.vue
|
||||
|
||||
## View-Abhängigkeiten
|
||||
| View | Route | Components | Services | Composables | Store | API Client | Socket Events |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Activate.vue | /activate/:activationCode | ConfirmDialog.vue<br>InfoDialog.vue | - | - | - | ja | - |
|
||||
| ClubSettings.vue | /club-settings | TrainingGroupsTab.vue<br>TrainingTimesTab.vue | - | - | - | ja | - |
|
||||
| ClubView.vue | /showclub/:clubId | ConfirmDialog.vue<br>InfoDialog.vue | - | - | getters: clubs, currentClub, hasPermission, isAuthenticated, isClubOwner, userRole; actions: -; useStore: nein | ja | - |
|
||||
| CreateClub.vue | /createclub | ConfirmDialog.vue<br>InfoDialog.vue | - | - | getters: -; actions: setClubs, setCurrentClub; useStore: nein | ja | - |
|
||||
| Datenschutz.vue | /datenschutz | - | - | - | - | nein | - |
|
||||
| DiaryView.vue | /diary | AccidentFormDialog.vue<br>BaseDialog.vue<br>ConfirmDialog.vue<br>CourtDrawingDialog.vue<br>CourtDrawingRender.vue<br>ImageDialog.vue<br>InfoDialog.vue<br>MemberActivityStatsDialog.vue<br>MemberGalleryDialog.vue<br>MemberNotesDialog.vue<br>PDFGenerator.js<br>QuickAddMemberDialog.vue<br>TagHistoryDialog.vue | socketService.js | - | getters: currentClub, currentClubName, isAuthenticated; actions: -; useStore: nein | ja | connectSocket<br>disconnectSocket<br>onParticipantAdded (participant:added)<br>onParticipantRemoved (participant:removed)<br>onParticipantUpdated (participant:updated)<br>onDiaryNoteAdded (diary:note:added)<br>onDiaryNoteDeleted (diary:note:deleted)<br>onDiaryTagAdded (diary:tag:added)<br>onDiaryTagRemoved (diary:tag:removed)<br>onDiaryDateUpdated (diary:date:updated)<br>onActivityMemberAdded (activity:member:added)<br>onActivityMemberRemoved (activity:member:removed)<br>onActivityChanged (activity:changed)<br>onMemberChanged (member:changed)<br>onGroupChanged (group:changed)<br>offParticipantAdded (participant:added)<br>offParticipantRemoved (participant:removed)<br>offParticipantUpdated (participant:updated)<br>offDiaryNoteAdded (diary:note:added)<br>offDiaryNoteDeleted (diary:note:deleted)<br>offDiaryTagAdded (diary:tag:added)<br>offDiaryTagRemoved (diary:tag:removed)<br>offDiaryDateUpdated (diary:date:updated)<br>offActivityMemberAdded (activity:member:added)<br>offActivityMemberRemoved (activity:member:removed)<br>offActivityChanged (activity:changed)<br>offMemberChanged (member:changed)<br>offGroupChanged (group:changed) |
|
||||
| ForgotPassword.vue | /forgot-password | InfoDialog.vue | - | - | - | ja | - |
|
||||
| Home.vue | / | - | - | - | getters: isAuthenticated; actions: logout; useStore: nein | nein | - |
|
||||
| Impressum.vue | /impressum | - | - | - | - | nein | - |
|
||||
| Login.vue | /login | ConfirmDialog.vue<br>InfoDialog.vue | - | - | getters: -; actions: login; useStore: nein | ja | - |
|
||||
| LogsView.vue | /logs | InfoDialog.vue | - | - | - | ja | - |
|
||||
| MemberTransferSettingsView.vue | /member-transfer-settings | ConfirmDialog.vue<br>InfoDialog.vue | - | - | getters: currentClub; actions: -; useStore: nein | ja | - |
|
||||
| MembersView.vue | /members | BaseDialog.vue<br>ConfirmDialog.vue<br>ImageViewerDialog.vue<br>InfoDialog.vue<br>MemberActivitiesDialog.vue<br>MemberNotesDialog.vue<br>MemberTransferDialog.vue<br>PDFGenerator.js | - | - | getters: currentClub, isAuthenticated, token; actions: -; useStore: nein | ja | - |
|
||||
| MyTischtennisAccount.vue | /mytischtennis-account | ConfirmDialog.vue<br>InfoDialog.vue<br>MyTischtennisDialog.vue | - | - | - | ja | - |
|
||||
| OfficialTournaments.vue | n/a | BaseDialog.vue<br>ConfirmDialog.vue<br>InfoDialog.vue<br>MemberSelectionDialog.vue<br>PDFGenerator.js | - | - | getters: currentClub; actions: -; useStore: nein | ja | - |
|
||||
| PendingApprovalsView.vue | /pending-approvals | ConfirmDialog.vue<br>InfoDialog.vue | - | - | getters: currentClub; actions: -; useStore: nein | ja | - |
|
||||
| PermissionsView.vue | /permissions | ConfirmDialog.vue<br>InfoDialog.vue | - | usePermissions.js | getters: -; actions: -; useStore: ja | ja | - |
|
||||
| PersonalSettings.vue | /personal-settings | - | - | - | getters: language; actions: setLanguage; useStore: nein | nein | - |
|
||||
| PredefinedActivities.vue | /predefined-activities | ConfirmDialog.vue<br>CourtDrawingDialog.vue<br>InfoDialog.vue | - | - | getters: -; actions: -; useStore: nein | ja | - |
|
||||
| Register.vue | /register | ConfirmDialog.vue<br>InfoDialog.vue | - | - | - | ja | - |
|
||||
| ResetPassword.vue | /reset-password/:token | InfoDialog.vue | - | - | - | ja | - |
|
||||
| ScheduleView.vue | /schedule | BaseDialog.vue<br>ConfirmDialog.vue<br>CsvImportDialog.vue<br>InfoDialog.vue<br>MatchReportApiDialog.vue<br>PDFGenerator.js<br>SeasonSelector.vue | socketService.js | - | getters: clubs, currentClub, currentClubName, isAuthenticated; actions: openDialog; useStore: nein | ja | connectSocket<br>disconnectSocket<br>onScheduleMatchUpdated (schedule:match:updated)<br>offScheduleMatchUpdated (schedule:match:updated)<br>onMatchReportSubmitted (schedule:match-report:submitted)<br>offMatchReportSubmitted (schedule:match-report:submitted) |
|
||||
| TeamManagementView.vue | /team-management | ConfirmDialog.vue<br>InfoDialog.vue<br>SeasonSelector.vue | - | - | getters: -; actions: -; useStore: ja | ja | - |
|
||||
| TournamentTab.vue | n/a | ConfirmDialog.vue<br>InfoDialog.vue<br>PDFGenerator.js<br>TournamentConfigTab.vue<br>TournamentGroupsTab.vue<br>TournamentParticipantsTab.vue<br>TournamentPlacementsTab.vue<br>TournamentResultsTab.vue | socketService.js | - | getters: currentClub, isAuthenticated; actions: -; useStore: nein | ja | connectSocket<br>disconnectSocket<br>onTournamentChanged (tournament:changed)<br>offTournamentChanged (tournament:changed) |
|
||||
| TournamentsView.vue | /tournaments | - | - | - | - | nein | - |
|
||||
| TrainingStatsView.vue | /training-stats | TrainingDetailsDialog.vue | - | - | getters: currentClub, isAuthenticated; actions: -; useStore: nein | ja | - |
|
||||
|
||||
## Socket Event -> Views
|
||||
- `activity:changed`: DiaryView.vue
|
||||
- `activity:member:added`: DiaryView.vue
|
||||
- `activity:member:removed`: DiaryView.vue
|
||||
- `diary:date:updated`: DiaryView.vue
|
||||
- `diary:note:added`: DiaryView.vue
|
||||
- `diary:note:deleted`: DiaryView.vue
|
||||
- `diary:tag:added`: DiaryView.vue
|
||||
- `diary:tag:removed`: DiaryView.vue
|
||||
- `group:changed`: DiaryView.vue
|
||||
- `member:changed`: DiaryView.vue
|
||||
- `participant:added`: DiaryView.vue
|
||||
- `participant:removed`: DiaryView.vue
|
||||
- `participant:updated`: DiaryView.vue
|
||||
- `schedule:match-report:submitted`: ScheduleView.vue
|
||||
- `schedule:match:updated`: ScheduleView.vue
|
||||
- `tournament:changed`: TournamentTab.vue
|
||||
81
docs/frontend_logic_inventory.md
Normal file
81
docs/frontend_logic_inventory.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Frontend Logic Inventory (Vue, Non-View)
|
||||
|
||||
Stand: aus `frontend/src` analysiert.
|
||||
|
||||
## services/
|
||||
| Element | Zweck | Abhängigkeiten (API Endpoint, Socket Events, Store) | Android-Entsprechung |
|
||||
|---|---|---|---|
|
||||
| `services/socketService.js` | Zentrale Socket.IO-Verbindung (connect/disconnect, join/leave Club-Raum, Event-Listener on/off). | Nutzt `backendBaseUrl` aus `apiClient`; Events u. a. `participant:*`, `diary:*`, `activity:*`, `member:changed`, `group:changed`, `tournament:changed`, `schedule:match:updated`, `schedule:match-report:submitted`; kein direkter Store-Zugriff. | `network` (WebSocket-Client) + teilweise `repository` (Event-Adapter). |
|
||||
|
||||
## apiClient.js
|
||||
| Element | Zweck | Abhängigkeiten (API Endpoint, Socket Events, Store) | Android-Entsprechung |
|
||||
|---|---|---|---|
|
||||
| `apiClient.js` (`axios.create`) | Globale HTTP-Basis: Base URL, Timeout (60s), Redirect-Handling, zentrale Request/Response-Interceptors. | Base URL: `VITE_BACKEND` oder `http://localhost:3005` (DEV) oder `window.location.origin`; Request-Header aus Store (`authcode`, `userid`); bei `401` `store.dispatch('logout')`; Netzwerk-/Timeout-Fehler werden ohne Auto-Logout weitergereicht. | `network` (OkHttp/Retrofit-Konfiguration + Interceptor). |
|
||||
|
||||
## store.js
|
||||
| Element | Zweck | Abhängigkeiten (API Endpoint, Socket Events, Store) | Android-Entsprechung |
|
||||
|---|---|---|---|
|
||||
| `state` | Session/User/Club/Permissions/UI-State (Sidebar, Sprache, Dialog-Stack). | Persistenz via `safeSessionStorage` + `safeLocalStorage`. | `viewmodel` (global app state) + teilweise `domain`. |
|
||||
| `mutations` | Synchrone Zustandsänderungen inkl. Persistenz und Dialog-Stack-Management. | Keine direkten API-Calls; schreibt in Storage. | `viewmodel` (state reducer). |
|
||||
| `actions.login` | Session setzen und Clubs laden. | API: `GET /clubs`; setzt Token/Username/Permissions. | `repository` + `viewmodel`. |
|
||||
| `actions.logout` | Session reset + Navigation auf Login. | Router-Push `/login`; löscht Token/Permissions. | `viewmodel` (Logout-Usecase). |
|
||||
| `actions.setCurrentClub` + `loadPermissions` | Clubwechsel + Permissions nachladen. | API: `GET /permissions/{clubId}`; Fallback auf read-only bei Fehlern. | `repository` + `viewmodel` + `domain` (permission model). |
|
||||
| `getters` | Abgeleitete Berechtigungslogik (`hasPermission`, `isClubOwner`, `userRole`) und UI-Selektionen. | Greift auf State zu; keine API. | `domain` + `viewmodel`. |
|
||||
|
||||
## composables/
|
||||
| Element | Zweck | Abhängigkeiten (API Endpoint, Socket Events, Store) | Android-Entsprechung |
|
||||
|---|---|---|---|
|
||||
| `composables/useDialog.js` (`useDialog`, `useConfirm`, `useInfo`) | Wiederverwendbare Dialog-Steuerung (open/close/toggle, promise-basiertes Confirm/Info). | Nutzt `utils/dialogUtils.js` (`buildInfoConfig`, `buildConfirmConfig`, `safeErrorMessage`); keine API. | `ui component` + `viewmodel` (UI-state helper). |
|
||||
| `composables/usePermissions.js` | Wiederverwendbare Permission-Prüfungen (`canRead/Write/Delete`, `isAdmin`, `hasRole`). | Liest Vuex-Getter (`currentPermissions`, `isClubOwner`, `userRole`, `hasPermission`). | `domain` (permission rules) + `viewmodel`. |
|
||||
|
||||
## components/ (UI-Komponenten + Nutzung in Views)
|
||||
Hinweis: Mapping „View-Nutzung“ basiert auf Imports in `src/views/*.vue`.
|
||||
|
||||
| Component | Verwendet in Views | Zweck | Abhängigkeiten (API/Socket/Store) | Android-Entsprechung |
|
||||
|---|---|---|---|---|
|
||||
| `AccidentFormDialog.vue` | `DiaryView.vue` | Unfallformular-Dialog. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `BaseDialog.vue` | `DiaryView.vue`, `MembersView.vue`, `OfficialTournaments.vue`, `ScheduleView.vue` | Basis-Dialog (Draggable/Modal-Grundstruktur). | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `ConfirmDialog.vue` | `Activate.vue`, `ClubView.vue`, `CreateClub.vue`, `DiaryView.vue`, `Login.vue`, `MemberTransferSettingsView.vue`, `MembersView.vue`, `MyTischtennisAccount.vue`, `OfficialTournaments.vue`, `PendingApprovalsView.vue`, `PermissionsView.vue`, `PredefinedActivities.vue`, `Register.vue`, `ScheduleView.vue`, `TeamManagementView.vue`, `TournamentTab.vue` | Wiederverwendbarer Bestätigungsdialog. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `CourtDrawingDialog.vue` | `DiaryView.vue`, `PredefinedActivities.vue` | Dialog für Hallen-/Übungszeichnung. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `CourtDrawingRender.vue` | `DiaryView.vue` | Rendering/Animation von Zeichnungsdaten. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `CourtDrawingTool.vue` | - | Interaktives Zeichentool (derzeit nicht direkt in Views importiert). | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `CsvImportDialog.vue` | `ScheduleView.vue` | CSV-Import-Dialog. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `DialogExamples.vue` | - | Demo/Beispiel für Dialogsystem. | `useDialog` composable. | `ui component` |
|
||||
| `DialogManager.vue` | (global über `App.vue`) | Verwaltung mehrerer/minimierter Dialoge. | Vuex (`dialogs`, z-index Handling). | `ui component` + `viewmodel` |
|
||||
| `ImageDialog.vue` | `DiaryView.vue` | Bildanzeige-/Bearbeitungsdialog. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `ImageViewerDialog.vue` | `MembersView.vue` | Bildviewer. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `InfoDialog.vue` | `Activate.vue`, `ClubView.vue`, `CreateClub.vue`, `DiaryView.vue`, `ForgotPassword.vue`, `Login.vue`, `LogsView.vue`, `MemberTransferSettingsView.vue`, `MembersView.vue`, `MyTischtennisAccount.vue`, `OfficialTournaments.vue`, `PendingApprovalsView.vue`, `PermissionsView.vue`, `PredefinedActivities.vue`, `Register.vue`, `ResetPassword.vue`, `ScheduleView.vue`, `TeamManagementView.vue`, `TournamentTab.vue` | Wiederverwendbarer Info-/Statusdialog. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `MatchAccountDialog.vue` | - | Match-Account Dialog (nicht direkt in Views importiert). | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `MatchReportApiDialog.vue` | `ScheduleView.vue` | API-gestützter Spielbericht/NuScore-Workflow. | API u. a. `/nuscore/broadcast-draft`, `/clubs/{clubId}`; Socket: `schedule:match-report:submitted`. | `ui component` + `repository` |
|
||||
| `MatchReportDialog.vue` | - | Iframe-/PIN-basierter Matchreport-Dialog. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `MatchReportHeaderActions.vue` | - | Header-Actions für Matchreport. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `MemberActivitiesDialog.vue` | `MembersView.vue` | Aktivitäts-Statistik pro Mitglied. | API (`member-activities` Endpunkte). | `ui component` + `repository` |
|
||||
| `MemberActivityStatsDialog.vue` | `DiaryView.vue` | Member-Aktivitätsstatistiken im Diary-Kontext. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `MemberGalleryDialog.vue` | `DiaryView.vue` | Mitglieder-Galerie inkl. Bildabruf. | API: `/clubmembers/gallery/{clubId}`, `/clubmembers/image/{clubId}/{memberId}`. | `ui component` + `repository` |
|
||||
| `MemberNotesDialog.vue` | `DiaryView.vue`, `MembersView.vue` | Notizdialog für Mitglieder. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `MemberSelectionDialog.vue` | `OfficialTournaments.vue` | Auswahl-/Empfehlungsdialog für Mitglieder. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `MemberTransferDialog.vue` | `MembersView.vue` | Transfer-Konfiguration/-Ausführung für Mitglieder. | API: `/member-transfer-config/{clubId}`, Transfer-POST; Vuex (`currentClub`). | `ui component` + `repository` |
|
||||
| `MyTischtennisDialog.vue` | `MyTischtennisAccount.vue` | Verifikation/Konto-Dialog myTischtennis. | API: `/mytischtennis/verify`, `/mytischtennis/account`. | `ui component` + `repository` |
|
||||
| `MyTischtennisHistoryDialog.vue` | - | Historie-Dialog für myTischtennis Sync. | API: `/mytischtennis/update-history`. | `ui component` + `repository` |
|
||||
| `NuscoreAnalyzer.vue` | - | Analyse-/Hilfskomponente für NuScore. | Keine direkte API/Store-Abhängigkeit. | `ui component` |
|
||||
| `PDFGenerator.js` | `DiaryView.vue`, `MembersView.vue`, `OfficialTournaments.vue`, `ScheduleView.vue`, `TournamentTab.vue` | PDF-Erzeugung aus UI-Daten. | Keine API/Store-Abhängigkeit (reine Utility). | `domain` (Dokument-Generierung) |
|
||||
| `QuickAddMemberDialog.vue` | `DiaryView.vue` | Schnellanlage/-zuordnung Mitglied. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `SeasonSelector.vue` | `ScheduleView.vue`, `TeamManagementView.vue` | Saisonauswahl + Saisonanlage. | API: `/seasons` (GET/POST); Vuex Store. | `ui component` + `repository` |
|
||||
| `TagHistoryDialog.vue` | `DiaryView.vue` | Verlauf/History von Tags. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `TrainingDetailsDialog.vue` | `TrainingStatsView.vue` | Detaildialog für Trainingseinträge/Stats. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `TrainingGroupsTab.vue` | `ClubSettings.vue` | CRUD Trainingsgruppen. | API: `/training-groups/{clubId}` + Member-Zuordnung; Vuex Store. | `ui component` + `repository` |
|
||||
| `TrainingTimesTab.vue` | `ClubSettings.vue` | CRUD Trainingszeiten. | API: `/training-times/{clubId}`; Vuex Store. | `ui component` + `repository` |
|
||||
| `tournament/TournamentConfigTab.vue` | `TournamentTab.vue` | Konfiguration Turnierphasen/Stages. | API: `/tournament/stages`, `/tournament/stages/advance`. | `ui component` + `repository` |
|
||||
| `tournament/TournamentGroupsTab.vue` | `TournamentTab.vue` | Gruppen-Tab im Turnierfluss. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `tournament/TournamentParticipantsTab.vue` | `TournamentTab.vue` | Teilnehmer-Tab im Turnierfluss. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `tournament/TournamentPlacementsTab.vue` | `TournamentTab.vue` | Platzierungen/Seeding-Tab. | API: `/clubmembers/get/{clubId}/true`, `/tournament/external-participants`. | `ui component` + `repository` |
|
||||
| `tournament/TournamentResultsTab.vue` | `TournamentTab.vue` | Ergebnis-Tab im Turnierfluss. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `tournament/PlayerDetailsDialog.vue` | - | Detaildialog für Spieler (intern/extern). | API: `/tournament/external-participants`, `/clubmembers/get/{clubId}/true`. | `ui component` + `repository` |
|
||||
| `tournament/TournamentClassList.vue` | - | Klassenlisten-UI für Turniere. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
| `tournament/TournamentClassSelector.vue` | - | Klassenauswahl-UI für Turniere. | Keine direkte API-Import-Abhängigkeit gefunden. | `ui component` |
|
||||
|
||||
## Kurzfazit zur Schichtzuordnung
|
||||
- Vue `apiClient` + `services/socketService` entsprechen primär Android `network`.
|
||||
- Vue `store/actions` + API-lastige Components entsprechen Android `repository` + `viewmodel` (in Vue oft direkt im Component vermischt).
|
||||
- Vue `composables` + Permission-Regeln entsprechen Android `domain/viewmodel`-Hilfsschicht.
|
||||
- Vue `components` entsprechen Android `ui component`.
|
||||
30
docs/implementation_matrix.md
Normal file
30
docs/implementation_matrix.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Implementation Matrix
|
||||
|
||||
| Vue View | Route (router.js) | Android Route/Screen | Status (Implemented/Partial/Placeholder/Missing) | Hinweise (API/State/Socket) |
|
||||
|---|---|---|---|---|
|
||||
| Activate.vue | `/activate/:activationCode` | `AppRoute.Activate` / `ActivateScreen` | Implemented | Dateien: `ActivateScreen.kt`, `ActivateViewModel.kt`, `TrainingstagebuchRepository.activate()`; DeepLink: `trainingstagebuch://auth/activate/{activationCode}`. |
|
||||
| ClubSettings.vue | `/club-settings` | `AppRoute.ClubSettings` / `ClubSettingsScreen` | Partial | Dateien: `ClubSettingsScreen.kt`, `ClubSettingsViewModel.kt`, `ClubSettingsService.kt`, `TrainingstagebuchRepository.loadClubSettings()/saveClubSettings()/loadClubTrainingGroups()/createClubTrainingGroup()/updateClubTrainingGroup()/deleteClubTrainingGroup()/loadClubTrainingTimes()/createClubTrainingTime()/updateClubTrainingTime()/deleteClubTrainingTime()`; umgesetzt: Tabs `Settings/Training Groups/Training Times`, CRUD inkl. Edit für Groups/Times, Loading/Error/Success, Dropdown-Auswahl (Group/Weekday) + Edit-Dialoge. Offen: weitere Feinschliffe zur vollständigen Vue-UX-Parität. |
|
||||
| ClubView.vue | `/showclub/:clubId` | `AppRoute.ShowClub` / `ShowClubScreen` | Partial | Dateien: `ShowClubScreen.kt`, `ShowClubViewModel.kt`, `HomeService.loadClub()/requestClubAccess()/loadNotApprovedMembers()`, `TrainingstagebuchRepository.loadClub()/requestClubAccess()/loadNotApprovedMembers()`; umgesetzt: Laden + Error/Retry + aktive Mitgliederliste + AccessDenied/Requested + Zugriff anfragen (`/clubs/request/{clubId}`) + Open-Requests (`/clubmembers/notapproved/{clubId}`). Offen: weitere Club-Detail-Parität (z. B. differenzierte Rechteanzeige). |
|
||||
| CreateClub.vue | `/createclub` | `AppRoute.CreateClub` / `CreateClubScreen` | Implemented | Dateien: `CreateClubScreen.kt`, `CreateClubViewModel.kt`, `HomeService.createClub()`, `TrainingstagebuchRepository.createClub()`; Validierung (Name >= 3), Loading/Error, setzt `currentClub` nach Erfolg wenn Club-ID vorhanden. |
|
||||
| Datenschutz.vue | `/datenschutz` | `AppRoute.Datenschutz` / `DatenschutzScreen` | Placeholder | Reiner Platzhalter-Screen. |
|
||||
| DiaryView.vue | `/diary` | `AppRoute.Diary` / `DiaryScreen` | Placeholder | Viele Diary-APIs vorhanden, aber Android-Screen aktuell Platzhalter. |
|
||||
| ForgotPassword.vue | `/forgot-password` | `AppRoute.ForgotPassword` / `ForgotPasswordScreen` | Implemented | Dateien: `ForgotPasswordScreen.kt`, `ForgotPasswordViewModel.kt`, `TrainingstagebuchRepository.forgotPassword()`; Loading/Error/Success vorhanden, DeepLink: `trainingstagebuch://auth/forgot-password`. |
|
||||
| Home.vue | `/` | `AppRoute.Home` / `HomeScreen` | Implemented | Dateien: `HomeScreen.kt`, `HomeViewModel.kt`, `TrainingstagebuchRepository.loadHomeData()` (nutzt `/clubs`), Loading/Error vorhanden. |
|
||||
| Impressum.vue | `/impressum` | `AppRoute.Impressum` / `ImpressumScreen` | Placeholder | Reiner Platzhalter-Screen. |
|
||||
| Login.vue | `/login` | `AppRoute.Login` / `LoginScreen` | Implemented | Dateien: `LoginScreen.kt`, `LoginViewModel.kt`, `TrainingstagebuchRepository.login()`; Session via `SessionManager`, ErrorBanner/Snackbar integriert. |
|
||||
| LogsView.vue | `/logs` | `AppRoute.Logs` / `LogsScreen` | Placeholder | API-Endpunkte vorhanden (`getLogs`, `getLogDetail`), Android-Screen Platzhalter. |
|
||||
| MembersView.vue | `/members` | `AppRoute.Members` / `MembersScreen` | Partial | Dateien: `MembersScreen.kt`, `MembersViewModel.kt`, `MembersService.loadMembers()/saveMember()/quick*()/image*()/trainingGroup*()/note*()`, `TrainingstagebuchRepository.loadMembers()/saveMember()/quick*()/image*()/trainingGroup*()/note*()`; umgesetzt: Loading/Error/Retry, Stats (aktiv/test/inaktiv), Suche, Inaktiv-Filter, Sortierung, Core-Form (Vorname/Nachname/active/testMembership), Kontakte (Phone/Email), Quick-Aktionen (`quick-deactivate`, `quick-update-test-membership`), Bild-Basis (Upload/Primär/Löschen), Training-Groups (laden/hinzufügen/entfernen), Notizen (laden/anlegen/löschen via `/membernotes`). Fehlend zur Vue-Parität: Transfer, erweiterte Kontakt-/Bilddetails. |
|
||||
| MemberTransferSettingsView.vue | `/member-transfer-settings` | `AppRoute.MemberTransferSettings` / `MemberTransferSettingsScreen` | Implemented | Dateien: `MemberTransferSettingsScreen.kt`, `MemberTransferSettingsViewModel.kt`, `MemberTransferService.kt`, `TrainingstagebuchRepository.loadMemberTransferConfig()/saveMemberTransferConfig()/deleteMemberTransferConfig()/executeMemberTransfer()`; inkl. Loading/Error, Save/Delete/Execute + Transfer-Summary. |
|
||||
| MyTischtennisAccount.vue | `/mytischtennis-account` | `AppRoute.MyTischtennisAccount` / `MyTischtennisAccountScreen` | Placeholder | API-Endpunkte vorhanden (`getMyTischtennis*`), Android-Screen Platzhalter. |
|
||||
| OfficialTournaments.vue | - | - | Missing | Keine Vue-Route in `router.js`; Android hat keinen Route-/Screen-Eintrag. API-Endpunkte vorhanden (`getOfficialTournaments` etc.). |
|
||||
| PendingApprovalsView.vue | `/pending-approvals` | `AppRoute.PendingApprovals` / `PendingApprovalsScreen` | Implemented | Dateien: `PendingApprovalsScreen.kt`, `PendingApprovalsViewModel.kt`, `HomeService.loadPendingApprovals()/approvePendingUser()/rejectPendingUser()`, `TrainingstagebuchRepository.loadPendingApprovals()/approvePendingUser()/rejectPendingUser()`; umgesetzt: Laden der offenen Requests + Approve/Reject inkl. Loading/Error/Success. |
|
||||
| PermissionsView.vue | `/permissions` | `AppRoute.Permissions` / `PermissionsScreen` | Placeholder | Permissions-APIs vorhanden, Android ohne State/Logik. |
|
||||
| PersonalSettings.vue | `/personal-settings` | `AppRoute.PersonalSettings` / `PersonalSettingsScreen` | Placeholder | Route vorhanden, derzeit Platzhalter. |
|
||||
| PredefinedActivities.vue | `/predefined-activities` | `AppRoute.PredefinedActivities` / `PredefinedActivitiesScreen` | Placeholder | Predefined-Activities-APIs vorhanden, Android-Screen Platzhalter. |
|
||||
| Register.vue | `/register` | `AppRoute.Register` / `RegisterScreen` | Implemented | Dateien: `RegisterScreen.kt`, `RegisterViewModel.kt`, `TrainingstagebuchRepository.register()` (nutzt `/auth/register`), Loading/Error vorhanden. |
|
||||
| ResetPassword.vue | `/reset-password/:token` | `AppRoute.ResetPassword` / `ResetPasswordScreen` | Implemented | Dateien: `ResetPasswordScreen.kt`, `ResetPasswordViewModel.kt`, `TrainingstagebuchRepository.resetPassword()`; Validierung (match/min length) + Loading/Error/Success, DeepLink: `trainingstagebuch://auth/reset-password/{token}`. |
|
||||
| ScheduleView.vue | `/schedule` | `AppRoute.Schedule` / `ScheduleScreen` | Placeholder | Match/Schedule-APIs vorhanden; Socket-Themen aus Vue nicht in Android umgesetzt. |
|
||||
| TeamManagementView.vue | `/team-management` | `AppRoute.TeamManagement` / `TeamManagementScreen` | Placeholder | Team-APIs vorhanden, Android-Screen Platzhalter. |
|
||||
| TournamentTab.vue | - | - | Missing | Vue-Komponente (Tab), keine eigene Route; Android hat keinen separaten Tab-/Screen-Eintrag. |
|
||||
| TournamentsView.vue | `/tournaments` | `AppRoute.Tournaments` / `TournamentsScreen` | Placeholder | Umfangreiche Tournament-APIs vorhanden, Android-Screen aktuell Platzhalter. |
|
||||
| TrainingStatsView.vue | `/training-stats` | `AppRoute.TrainingStats` / `TrainingStatsScreen` | Placeholder | Training-Stats-API vorhanden (`getTrainingStats`), Android-Screen Platzhalter. |
|
||||
51
docs/implementation_status.md
Normal file
51
docs/implementation_status.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Implementation Status (Vue -> Android)
|
||||
|
||||
Stand: 2026-03-06
|
||||
|
||||
Quellen:
|
||||
- `docs/frontend_dependency_graph.md`
|
||||
- `docs/mobile_spec.md`
|
||||
- `android-app` (Navigation/Screens/ViewModels/Repository)
|
||||
|
||||
Status-Legende:
|
||||
- `Implemented`: eigener Android Screen + ViewModel + Repository-Anbindung vorhanden
|
||||
- `Partial`: funktional vorhanden, aber klar reduzierte/paritätisch unvollständige Umsetzung
|
||||
- `Placeholder`: Route vorhanden, aber nur Platzhalter-Screen
|
||||
- `Missing`: keine Android-Route/kein Android-Screen vorhanden
|
||||
|
||||
| Vue View | Android Screen/Route | Status | Details |
|
||||
|---|---|---|---|
|
||||
| Activate.vue | `ActivateScreen` / `activate/{activationCode}` | Implemented | Dateien: `ui/screens/ActivateScreen.kt`, `viewmodel/ActivateViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`activate`). API: `GET /auth/activate/{activationCode}`. Socket: - |
|
||||
| ClubSettings.vue | `ClubSettingsScreen` / `club-settings` | Implemented | Dateien: `ui/screens/ClubSettingsScreen.kt`, `viewmodel/ClubSettingsViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`loadClubSettings`, `saveClubSettings`, `loadClubTrainingGroups`, `create/update/deleteClubTrainingGroup`, `load/create/update/deleteClubTrainingTime`). API: `GET /clubs/{clubId}`, `PUT /clubs/{clubId}/settings`, `GET/POST/PUT/DELETE /training-groups/{clubId}*`, `GET/POST/PUT/DELETE /training-times/{clubId}*`. Socket: - |
|
||||
| ClubView.vue | `ShowClubScreen` / `showclub/{clubId}` | Implemented | Dateien: `ui/screens/ShowClubScreen.kt`, `viewmodel/ShowClubViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`loadClub`, `requestClubAccess`). API: `GET /clubs/{clubId}`, `GET /clubs/request/{clubId}`. Socket: - |
|
||||
| CreateClub.vue | `CreateClubScreen` / `createclub` | Implemented | Dateien: `ui/screens/CreateClubScreen.kt`, `viewmodel/CreateClubViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`createClub`). API: `POST /clubs`. Socket: - |
|
||||
| Datenschutz.vue | `DatenschutzScreen` / `datenschutz` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| DiaryView.vue | `DiaryScreen` / `diary` | Partial | Dateien: `ui/screens/DiaryScreen.kt`, `viewmodel/DiaryViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (Diary-Block: Dates, Participants, Activities, Tags, Notes, Plan, Groups, Group-Activity, Activity-Member-Assignments, Accident, Stats, Predefined Search). API (zentral): `GET/POST/PUT/DELETE /diary/{clubId}*`, `/participants/*`, `/activities/*`, `/group/*`, `/tags`, `/diary/tag/*`, `/diarymember/{clubId}/note*`, `/diary-date-activities/*`, `/diary-date-activities/group*`, `/diary-member-activities/*`, `/accident*`, `/member-activities/*`, `/predefined-activities/search/query`. Socket: `participant:*`, `diary:*`, `activity-member:*`, `activity:changed`, `member:changed`, `group:changed` (über `network/SocketManager.kt` + `viewmodel/DiaryViewModel.kt`). |
|
||||
| ForgotPassword.vue | `ForgotPasswordScreen` / `forgot-password` | Implemented | Dateien: `ui/screens/ForgotPasswordScreen.kt`, `viewmodel/ForgotPasswordViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`forgotPassword`). API: `POST /auth/forgot-password`. Socket: - |
|
||||
| Home.vue | `HomeScreen` / `home` | Implemented | Dateien: `ui/screens/HomeScreen.kt`, `viewmodel/HomeViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`loadHomeData`). API: `GET /clubs`. Socket: - |
|
||||
| Impressum.vue | `ImpressumScreen` / `impressum` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| Login.vue | `LoginScreen` / `login` | Implemented | Dateien: `ui/screens/LoginScreen.kt`, `viewmodel/LoginViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`login`). API: `POST /auth/login` (plus Session-Validierung global über `GET /session/status`). Socket: Connect-on-login erfolgt über `repository/connectSocketOnLogin`. |
|
||||
| LogsView.vue | `LogsScreen` / `logs` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| MemberTransferSettingsView.vue | `MemberTransferSettingsScreen` / `member-transfer-settings` | Implemented | Dateien: `ui/screens/MemberTransferSettingsScreen.kt`, `viewmodel/MemberTransferSettingsViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`load/save/deleteMemberTransferConfig`, `executeMemberTransfer`). API: `GET/POST/DELETE /member-transfer-config/{clubId}`, `POST /clubmembers/transfer/{clubId}`. Socket: - |
|
||||
| MembersView.vue | `MembersScreen` / `members` | Partial | Dateien: `ui/screens/MembersScreen.kt`, `viewmodel/MembersViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (Members-Block). API (zentral): `GET /clubmembers/get/{clubId}/{showAll}`, `POST /clubmembers/set/{clubId}`, `POST /clubmembers/quick-update-test-membership/{clubId}/{memberId}`, `POST /clubmembers/quick-deactivate/{clubId}/{memberId}`, `POST/DELETE /clubmembers/image/{clubId}/{memberId}*`, `GET /clubmembers/gallery/{clubId}`, `GET/POST/DELETE /membernotes*`, `GET /training-groups/{clubId}`, `GET /training-groups/{clubId}/member/{memberId}`, `POST/DELETE /training-groups/{clubId}/{groupId}/member/{memberId}`. Socket: - |
|
||||
| MyTischtennisAccount.vue | `MyTischtennisAccountScreen` / `mytischtennis-account` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| OfficialTournaments.vue | - | Missing | Fehlende Abhängigkeiten aus Graph: Components `BaseDialog.vue`, `ConfirmDialog.vue`, `InfoDialog.vue`, `MemberSelectionDialog.vue`, `PDFGenerator.js`; Services `-`; Store `getter: currentClub`; Socket `-`. |
|
||||
| PendingApprovalsView.vue | `PendingApprovalsScreen` / `pending-approvals` | Implemented | Dateien: `ui/screens/PendingApprovalsScreen.kt`, `viewmodel/PendingApprovalsViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`loadPendingApprovals`, `approvePendingUser`, `rejectPendingUser`). API: `GET /clubs/pending/{clubId}`, `POST /clubs/approve`, `POST /clubs/reject`. Socket: - |
|
||||
| PermissionsView.vue | `PermissionsScreen` / `permissions` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| PersonalSettings.vue | `PersonalSettingsScreen` / `personal-settings` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| PredefinedActivities.vue | `PredefinedActivitiesScreen` / `predefined-activities` | Implemented | Dateien: `ui/screens/PredefinedActivitiesScreen.kt`, `viewmodel/PredefinedActivitiesViewModel.kt`, `services/PredefinedActivitiesService.kt`, `repository/TrainingstagebuchRepository.kt` (`load/search/create/update/merge/deduplicate/deleteImage`). API: `GET /predefined-activities`, `GET /predefined-activities/{id}`, `GET /predefined-activities/search/query`, `POST /predefined-activities`, `PUT /predefined-activities/{id}`, `POST /predefined-activities/merge`, `POST /predefined-activities/deduplicate`, `DELETE /predefined-activities/{id}/image/{imageId}`. Socket: - |
|
||||
| Register.vue | `RegisterScreen` / `register` | Implemented | Dateien: `ui/screens/RegisterScreen.kt`, `viewmodel/RegisterViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`register`). API: `POST /auth/register`. Socket: - |
|
||||
| ResetPassword.vue | `ResetPasswordScreen` / `reset-password/{token}` | Implemented | Dateien: `ui/screens/ResetPasswordScreen.kt`, `viewmodel/ResetPasswordViewModel.kt`, `repository/TrainingstagebuchRepository.kt` (`resetPassword`). API: `POST /auth/reset-password`. Socket: - |
|
||||
| ScheduleView.vue | `ScheduleScreen` / `schedule` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| TeamManagementView.vue | `TeamManagementScreen` / `team-management` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| TournamentTab.vue | - | Missing | Fehlende Abhängigkeiten aus Graph: Components `TournamentConfigTab.vue`, `TournamentGroupsTab.vue`, `TournamentParticipantsTab.vue`, `TournamentResultsTab.vue`, `TournamentPlacementsTab.vue`, `ConfirmDialog.vue`, `InfoDialog.vue`, `PDFGenerator.js`; Services `socketService.js`; Store `getters: currentClub, isAuthenticated`; Socket `tournament:changed`. |
|
||||
| TournamentsView.vue | `TournamentsScreen` / `tournaments` | Placeholder | Platzhalter über `ui/screens/PlaceholderScreens.kt`. |
|
||||
| TrainingStatsView.vue | `TrainingStatsScreen` / `training-stats` | Implemented | Dateien: `ui/screens/TrainingStatsScreen.kt`, `viewmodel/TrainingStatsViewModel.kt`, `services/TrainingStatsService.kt`, `repository/TrainingstagebuchRepository.kt` (`loadTrainingStats`, `loadMemberActivityStats`). API: `GET /training-stats/{clubId}`, `GET /member-activities/{clubId}/{memberId}?period=all`, `GET /member-activities/{clubId}/{memberId}/last-participations?limit=3`. Socket: - |
|
||||
|
||||
## Kurzfazit
|
||||
- `Implemented`: 13
|
||||
- `Partial`: 2
|
||||
- `Placeholder`: 9
|
||||
- `Missing`: 2
|
||||
|
||||
Hauptlücken mit höchstem Migrationsaufwand sind aktuell `OfficialTournaments.vue`, `TournamentTab.vue` sowie die Placeholder-Bereiche `Schedule`, `Tournaments`, `TrainingStats`, `Permissions`, `PredefinedActivities`, `TeamManagement`, `MyTischtennisAccount`.
|
||||
114
docs/migration_state.md
Normal file
114
docs/migration_state.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Migration State
|
||||
|
||||
## Done
|
||||
- Globales Auth-Handling für Android-App implementiert:
|
||||
- OkHttp Interceptor hängt `authcode`/`userid` aus Session an Requests.
|
||||
- 401/403 + Unauthorized-Codes (`ERROR_UNAUTHORIZED`, `ERROR_FORBIDDEN`, `ERROR_SESSION_EXPIRED`) werden zentral erkannt.
|
||||
- Session wird bei Unauthorized gelöscht und per globalem Event auf Login navigiert.
|
||||
- Feature-Slice `registration -> login -> home` end-to-end umgesetzt:
|
||||
- Compose-Screens für Registrierung, Login, Home.
|
||||
- Navigation zwischen den drei Screens.
|
||||
- ViewModels: `RegisterViewModel`, `LoginViewModel`, `HomeViewModel`.
|
||||
- Repository-Calls auf API-Contract-Endpunkte (`/auth/register`, `/auth/login`, `/clubs`).
|
||||
- Loading/Error-States in den Flows.
|
||||
- Auth-Erweiterungen end-to-end umgesetzt:
|
||||
- `Activate` mit Repository + Service + ViewModel + Compose-Screen (`/auth/activate/:activationCode`).
|
||||
- `ForgotPassword` mit Repository + Service + ViewModel + Compose-Screen (`/auth/forgot-password`).
|
||||
- `ResetPassword` mit Repository + Service + ViewModel + Compose-Screen (`/auth/reset-password`).
|
||||
- Login verlinkt auf Forgot-Password-Flow.
|
||||
- Deeplinks für Auth-Flows über `trainingstagebuch://auth/...` (activate/reset-password/forgot-password).
|
||||
- Wiederverwendbares Error-Pattern eingeführt:
|
||||
- `StandardErrorMapper` (Netzwerk, Timeout, 4xx, 5xx, Unknown).
|
||||
- `ErrorBanner` + `ErrorSnackbarHost` als Compose-Bausteine.
|
||||
- Nutzung im Login-Screen als Referenz.
|
||||
- i18n-Baseline umgesetzt:
|
||||
- `values/strings.xml` + `values-en/strings.xml` ausgebaut.
|
||||
- `UiText`-Pattern in ViewModels/Screens aktiv.
|
||||
- Build-Guard gegen neue hardcoded UI-Strings (`checkI18nHardcodedUiStrings`).
|
||||
- Feature-Slice `Members` (Basis) umgesetzt:
|
||||
- Compose-Screen mit Loading/Error/Retry + einfacher Mitgliederliste.
|
||||
- ViewModel (`MembersViewModel`) lädt Club aus Session und Members über Service/Repository.
|
||||
- Repository-Mapping für Members aus `/clubmembers/get/{clubId}/true`.
|
||||
- Unit-Test für Member-Mapping ergänzt (`RepositoryMappersTest`).
|
||||
- Feature-Slice `Members` erweitert:
|
||||
- Stats-Zähler im Screen ergänzt (aktive / Test- / inaktive Mitglieder).
|
||||
- Suche, Inaktiv-Filter und Sortierung (Name A-Z/Z-A, Status).
|
||||
- Core-Form für Create/Edit (Vorname, Nachname, aktiv, Testmitglied) via `/clubmembers/set/{clubId}`.
|
||||
- Kontakte (Telefon/E-Mail) im Editor hinzugefügt und mit `/clubmembers/set/{clubId}` persistiert.
|
||||
- Quick-Aktionen ergänzt: Teststatus entfernen (`/clubmembers/quick-update-test-membership/{clubId}/{memberId}`) und Mitglied deaktivieren (`/clubmembers/quick-deactivate/{clubId}/{memberId}`).
|
||||
- Bilder-Basis ergänzt: Upload, Primär setzen und Löschen für ausgewähltes Mitglied (`/clubmembers/image/{clubId}/{memberId}*`).
|
||||
- Training-Groups ergänzt: laden, für Mitglied zuweisen/entfernen (`/training-groups/{clubId}*`).
|
||||
- Notizen ergänzt: laden/anlegen/löschen über `/membernotes`.
|
||||
- Zusätzlicher Unit-Test für Filter-/Sortierlogik (`MembersFilterUtilsTest`).
|
||||
- Feature-Slice `Member Transfer Settings` umgesetzt:
|
||||
- Compose-Screen `MemberTransferSettingsScreen` ersetzt Placeholder.
|
||||
- ViewModel `MemberTransferSettingsViewModel` mit Loading/Error/Message-State.
|
||||
- Service/Repository-Integration für:
|
||||
- Laden: `/member-transfer-config/{clubId}`
|
||||
- Speichern: `/member-transfer-config/{clubId}`
|
||||
- Löschen: `/member-transfer-config/{clubId}`
|
||||
- Ausführen: `/clubmembers/transfer/{clubId}`
|
||||
- UI enthält Save/Delete/Execute sowie Ergebnisanzeige (`transferred/total`, invalid members, errors).
|
||||
- i18n-Strings (de/en) für den kompletten Transfer-Screen ergänzt.
|
||||
- Feature-Slice `Create Club` umgesetzt:
|
||||
- Compose-Screen `CreateClubScreen` ersetzt Placeholder.
|
||||
- ViewModel `CreateClubViewModel` mit Eingabevalidierung, Loading/Error und Erfolgspfad.
|
||||
- Repository/Service-Integration für `POST /clubs` (`createClub`).
|
||||
- Nach erfolgreichem Erstellen wird `currentClub` in der Session gesetzt (falls Club-ID aus Response extrahierbar).
|
||||
- Navigation Home -> CreateClub ergänzt und Rücknavigation nach Erfolg eingebaut.
|
||||
- Feature-Slice `Show Club` (Basis) umgesetzt:
|
||||
- Compose-Screen `ShowClubScreen` ersetzt Placeholder.
|
||||
- ViewModel `ShowClubViewModel` mit Loading/Error/Retry-State.
|
||||
- Repository/Service-Integration für `GET /clubs/{clubId}` (`loadClub`).
|
||||
- Home bietet Navigation zum aktuellen Club (`AppState.currentClub` -> `showclub/{clubId}`).
|
||||
- Aktive Mitglieder werden im Detail-Screen gelistet.
|
||||
- Access-Flow ergänzt: 403/Pending-State + `GET /clubs/request/{clubId}` (Zugriff anfragen) umgesetzt.
|
||||
- Open-Requests ergänzt: `GET /clubmembers/notapproved/{clubId}` wird geladen und angezeigt.
|
||||
- Feature-Slice `Pending Approvals` umgesetzt:
|
||||
- Compose-Screen `PendingApprovalsScreen` ersetzt Placeholder.
|
||||
- ViewModel `PendingApprovalsViewModel` lädt/synchronisiert offene Anfragen für `currentClub`.
|
||||
- Repository/Service-Integration:
|
||||
- Laden: `GET /clubs/pending/{clubId}`
|
||||
- Freigeben: `POST /clubs/approve`
|
||||
- Ablehnen: `POST /clubs/reject`
|
||||
- Home-Navigation ergänzt (`Freigaben öffnen`).
|
||||
- Feature-Slice `Club Settings` (Basis) umgesetzt:
|
||||
- Compose-Screen `ClubSettingsScreen` ersetzt Placeholder.
|
||||
- ViewModel `ClubSettingsViewModel` lädt/speichert für `currentClub`.
|
||||
- Repository/Service-Integration:
|
||||
- Laden: `GET /clubs/{clubId}` (Settings-Felder aus Club-Objekt)
|
||||
- Speichern: `PUT /clubs/{clubId}/settings`
|
||||
- Felder: `greetingText`, `associationMemberNumber`; inkl. Loading/Error/Success-State.
|
||||
- Tab-Erweiterung umgesetzt:
|
||||
- `Settings` Tab: Greeting + Association Number speichern.
|
||||
- `Training Groups` Tab: Laden/Anlegen/Bearbeiten/Löschen (`/training-groups/{clubId}`).
|
||||
- `Training Times` Tab: Laden/Anlegen/Bearbeiten/Löschen (`/training-times/{clubId}`).
|
||||
- UX-Upgrade umgesetzt:
|
||||
- Dropdown-Auswahl für Training Group und Weekday im Time-Create-Flow.
|
||||
- Bearbeiten von Gruppen/Zeiten über kompaktere Dialoge.
|
||||
- Minimale Unit-Tests ergänzt:
|
||||
- Mapper/Error-Parsing Tests (`RepositoryMappersTest`).
|
||||
- AGP-Warnung zu `compileSdk=35` behoben durch SDK-Absenkung:
|
||||
- `compileSdk = 34`, `targetSdk = 34`.
|
||||
- Builds/Tests zuletzt erfolgreich:
|
||||
- `./gradlew assembleDebug`
|
||||
- `./gradlew testDebugUnitTest`
|
||||
|
||||
## Next
|
||||
- Error-Pattern (`ErrorBanner`/`ErrorSnackbarHost`) auf Register/Home/Forgot/Reset vereinheitlichen.
|
||||
- Zusätzliche Unit-Tests für neue Auth-Repositories/ViewModels ergänzen (Forgot/Reset/Activate, inkl. Fehlpfade).
|
||||
- API-Responses für Home (`/clubs`) stärker typisieren (statt reinem `JsonElement`).
|
||||
- Nächsten vertikalen Feature-Flow umsetzen (z. B. Members oder Diary) inkl. Socket-Event-Nutzung.
|
||||
- Members-Flow weiter auf Vue-Parität ausbauen (erweiterte Kontakt-/Bilddetails, Feinschliff Transfer-UX).
|
||||
- ShowClub/ClubSettings als nächste Club-Detail-Flows von Placeholder auf echte Implementierung heben.
|
||||
- ClubSettings weiter auf volle Vue-Parität heben (z. B. detailliertere Validierungen/Interaktionen).
|
||||
- Socket-Events in Members/Diary integrieren und State live aktualisieren.
|
||||
- Optional: DI (z. B. Hilt/Koin) für Repository/ApiService/ViewModels einführen, um Testbarkeit und Lifecycle-Konsistenz zu verbessern.
|
||||
- Entscheidung treffen, ob langfristig wieder auf `compileSdk=35` + aktualisierte Toolchain migriert werden soll.
|
||||
|
||||
## Risks
|
||||
- `runBlocking` im Interceptor beim Unauthorized-Handling kann in Randfällen Latenz verursachen.
|
||||
- Error-Mapping ist aktuell generisch; backend-spezifische Fehlertexte/Codes könnten in einzelnen Flows detaillierter benötigt werden.
|
||||
- Unit-Test-Abdeckung bleibt noch minimal und fokussiert nicht auf vollständige Auth-/DeepLink-End-to-End-Pfade.
|
||||
- Members ist aktuell nur als Basisliste umgesetzt; komplexe Geschäftslogik aus Vue fehlt noch.
|
||||
- Mit `compileSdk/targetSdk = 34` fehlen potenzielle neue Plattform-APIs/Verhaltensanpassungen aus API 35.
|
||||
108
docs/mobile_spec.md
Normal file
108
docs/mobile_spec.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Mobile Spec (Vue Frontend Analyse)
|
||||
|
||||
## 1) Routen / Screens (vue-router)
|
||||
Quelle: `frontend/src/router.js`
|
||||
|
||||
| Pfad | Screen-Komponente | Route-Parameter | Router-Guard |
|
||||
|---|---|---|---|
|
||||
| `/register` | `Register.vue` | - | keiner |
|
||||
| `/login` | `Login.vue` | - | keiner |
|
||||
| `/activate/:activationCode` | `Activate.vue` | `activationCode` | keiner |
|
||||
| `/forgot-password` | `ForgotPassword.vue` | - | keiner |
|
||||
| `/reset-password/:token` | `ResetPassword.vue` | `token` | keiner |
|
||||
| `/` | `Home.vue` | - | keiner |
|
||||
| `/createclub` | `CreateClub.vue` | - | keiner |
|
||||
| `/showclub/:clubId` | `ClubView.vue` | `clubId` | keiner |
|
||||
| `/members` | `MembersView.vue` | - | keiner |
|
||||
| `/diary` | `DiaryView.vue` | - | keiner |
|
||||
| `/pending-approvals` | `PendingApprovalsView.vue` | - | keiner |
|
||||
| `/schedule` | `ScheduleView.vue` | - | keiner |
|
||||
| `/tournaments` | `TournamentsView.vue` | - | keiner |
|
||||
| `/training-stats` | `TrainingStatsView.vue` | - | keiner |
|
||||
| `/club-settings` | `ClubSettings.vue` | - | keiner |
|
||||
| `/predefined-activities` | `PredefinedActivities.vue` | - | keiner |
|
||||
| `/mytischtennis-account` | `MyTischtennisAccount.vue` | - | keiner |
|
||||
| `/team-management` | `TeamManagementView.vue` | - | keiner |
|
||||
| `/permissions` | `PermissionsView.vue` | - | keiner |
|
||||
| `/logs` | `LogsView.vue` | - | keiner |
|
||||
| `/member-transfer-settings` | `MemberTransferSettingsView.vue` | - | keiner |
|
||||
| `/personal-settings` | `PersonalSettings.vue` | - | keiner |
|
||||
| `/impressum` | `Impressum.vue` | - | keiner |
|
||||
| `/datenschutz` | `Datenschutz.vue` | - | keiner |
|
||||
|
||||
## 2) Effektive Guard-Logik (außerhalb vue-router)
|
||||
Es gibt **keine** `router.beforeEach`, keine `beforeEnter`-Guards und keine `meta.requiresAuth`-Strategie.
|
||||
|
||||
Stattdessen wird Zugriff so gesteuert:
|
||||
|
||||
1. UI-/Navigations-Gating in `App.vue`
|
||||
- Menüeinträge werden abhängig von Vuex-Permissions (`hasPermission`, `isClubOwner`, `userRole`) ein-/ausgeblendet.
|
||||
- Nicht authentifizierte User sehen primär Login/Register-Navigation.
|
||||
|
||||
2. Imperative Redirects
|
||||
- `App.vue -> loadClub()`:
|
||||
- bei fehlender Statistik-Leseberechtigung Redirect auf `/showclub/:clubId`
|
||||
- sonst Redirect auf `/training-stats`
|
||||
- `App.vue -> watch(currentClub)`:
|
||||
- `currentClub === 'new'` -> `/createclub`
|
||||
- `TournamentTab.vue -> created()`:
|
||||
- wenn nicht authentifiziert -> `/login`
|
||||
|
||||
3. API-basierte Sitzungsprüfung
|
||||
- `App.vue` pollt `/session/status`.
|
||||
- `apiClient` Response-Interceptor triggert bei `401` automatisch `store.dispatch('logout')`.
|
||||
|
||||
## 3) State Management
|
||||
|
||||
### Ergebnis
|
||||
- **Pinia:** nicht verwendet
|
||||
- **Vuex:** verwendet (`frontend/src/store.js`)
|
||||
- Store-Struktur: **ein zentraler Root-Store**, keine Module
|
||||
|
||||
### Store: `frontend/src/store.js`
|
||||
|
||||
#### State-Struktur
|
||||
```ts
|
||||
state = {
|
||||
token: string | null, // sessionStorage
|
||||
username: string, // sessionStorage
|
||||
currentClub: string | null, // sessionStorage
|
||||
clubs: Array<Club>, // localStorage (JSON)
|
||||
permissions: {
|
||||
[clubId: string]: {
|
||||
role: string,
|
||||
isOwner: boolean,
|
||||
permissions: Record<string, Record<string, boolean>>
|
||||
}
|
||||
}, // localStorage (JSON)
|
||||
dialogs: Array<{
|
||||
id: number,
|
||||
...dialogProps,
|
||||
isMinimized: boolean,
|
||||
zIndex: number,
|
||||
position: { x: number, y: number }
|
||||
}>,
|
||||
dialogCounter: number,
|
||||
sidebarCollapsed: boolean, // localStorage
|
||||
language: string | null // localStorage
|
||||
}
|
||||
```
|
||||
|
||||
#### Mutations (Auszug)
|
||||
- Auth/Session: `setToken`, `clearToken`, `setUsername`, `clearUsername`, `setClub`
|
||||
- Club/Permission: `setClubsMutation`, `setPermissions`, `clearPermissions`
|
||||
- UI: `setSidebarCollapsed`, `setLanguage`
|
||||
- Dialog-System: `openDialog`, `closeDialog`, `minimizeDialog`, `restoreDialog`, `bringDialogToFront`
|
||||
|
||||
#### Actions (Auszug)
|
||||
- `login({ token, username })` -> setzt Auth-State, lädt `/clubs`
|
||||
- `logout()` -> State reset + Redirect `/login`
|
||||
- `setCurrentClub(club)` -> setzt Club + lädt Permissions
|
||||
- `loadPermissions(clubId)` -> `/permissions/:clubId`, Fallback auf read-only default bei Fehler
|
||||
|
||||
#### Getter (Auszug)
|
||||
- Auth: `isAuthenticated`, `token`, `username`
|
||||
- Club: `currentClub`, `clubs`, `currentClubName`
|
||||
- Permission: `currentPermissions`, `hasPermission(resource, action)`, `isClubOwner`, `userRole`
|
||||
- Dialog: `dialogs`, `minimizedDialogs`, `activeDialogs`
|
||||
|
||||
100
docs/plan_diaryview.md
Normal file
100
docs/plan_diaryview.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# DiaryView Port Plan (statt direkter Komplett-Implementierung)
|
||||
|
||||
## Entscheidung
|
||||
Die vollständige Rest-Portierung von `frontend/src/views/DiaryView.vue` in einem einzigen Durchlauf würde voraussichtlich **deutlich mehr als 40 Dateien** betreffen (UI-Module, Dialoge, Mapper/DTOs, Repository/Service/ViewModel-Erweiterungen, Socket-Handling, Strings, Tests, Doku).
|
||||
|
||||
Daher wurde gemäß Vorgabe (`Wenn mehr nötig: /docs/plan_diaryview.md statt Änderungen`) **kein weiterer Code geändert**.
|
||||
|
||||
## 1) Analyse von `DiaryView.vue`
|
||||
|
||||
### Verwendete Components
|
||||
- `CourtDrawingRender`
|
||||
- `CourtDrawingDialog`
|
||||
- `InfoDialog`
|
||||
- `ConfirmDialog`
|
||||
- `ImageDialog`
|
||||
- `BaseDialog`
|
||||
- `MemberNotesDialog`
|
||||
- `TagHistoryDialog`
|
||||
- `MemberActivityStatsDialog`
|
||||
- `AccidentFormDialog`
|
||||
- `QuickAddMemberDialog`
|
||||
- `MemberGalleryDialog`
|
||||
- `PDFGenerator` (JS-Utility)
|
||||
|
||||
### Services / Utilities / Composables
|
||||
- `apiClient` (viele REST-Aufrufe)
|
||||
- `socketService` (`connectSocket`, `disconnectSocket`, `on*`, `off*`)
|
||||
- Vuex `mapGetters`: `isAuthenticated`, `currentClub`, `currentClubName`
|
||||
- Sortable/Drag&Drop Logik (Order-Updates)
|
||||
|
||||
### Store-Abhängigkeiten
|
||||
- Auth/Session und Club-Kontext kommen aus Store-Gettern:
|
||||
- `isAuthenticated`
|
||||
- `currentClub`
|
||||
- `currentClubName`
|
||||
|
||||
### API-Aufrufe (relevant für Rest-Port)
|
||||
Bereits in Android teilweise umgesetzt, aber für Vollparität fehlen weiterhin größere Blöcke:
|
||||
- Diary Core: `/diary/{clubId}` (GET/POST/PUT/DELETE)
|
||||
- Participants: `/participants/*`, `/participants/{dateId}/{memberId}/group`
|
||||
- Activities: `/activities/{dateId}`, `/activities/add`
|
||||
- Tags/Notes: `/tags`, `/diary/tag/*`, `/diarymember/*`
|
||||
- Plan: `/diary-date-activities/*`, `/diary-date-activities/group/*`, `/diary-member-activities/*`
|
||||
- Groups: `/group/{clubId}/{dateId}`, `/group`, `/group/{groupId}`
|
||||
- Training context: `/training-groups/{clubId}`, `/training-times/{clubId}`
|
||||
- Member images/gallery: `/clubmembers/image/*`
|
||||
- Stats: `/member-activities/*`
|
||||
- Accident: `/accident`, `/accident/{clubId}/{dateId}`
|
||||
- Predefined activities + search + create/update: `/predefined-activities*`
|
||||
|
||||
### Socket-Events in DiaryView
|
||||
- Participants: `participant:added`, `participant:removed`, `participant:updated`
|
||||
- Diary: `diary:note:added`, `diary:note:deleted`, `diary:tag:added`, `diary:tag:removed`, `diary:date:updated`
|
||||
- Activity member: `activity-member:added`, `activity-member:removed`
|
||||
- Activity: `activity:changed`
|
||||
- Member: `member:changed`
|
||||
- Group: `group:changed`
|
||||
|
||||
## 2) Warum >40 Dateien realistisch sind
|
||||
Für „möglichst 1:1 Web-Funktionalität“ müssen zusätzlich zu bestehendem Stand mindestens folgende Pakete ausgebaut werden:
|
||||
- Mehrere dedizierte Compose-Dialoge (Drawing, Notes, Tag-Historie, Activity-Stats, Accident, QuickAdd, Gallery, Image/Confirm/Info)
|
||||
- Umfangreiche VM-State-Modelle pro Dialog/Tab/Flow
|
||||
- Zusätzliche Repository-Modelle und Mapper für Group-Activities, Member-Activities, Accident, Gallery/Image-Modelle
|
||||
- Socket-Event-spezifische State-Synchronisation im Diary-Flow
|
||||
- Zusätzliche Strings/Testfälle für viele Spezialpfade
|
||||
|
||||
Damit ist die 40-Dateien-Grenze in einem „alles in einem Durchlauf“-Block nicht sinnvoll einhaltbar, ohne die geforderte 1:1-Funktionalität zu unterlaufen.
|
||||
|
||||
## 3) Empfohlene Umsetzungsblöcke (priorisiert)
|
||||
|
||||
### Block A: Training-Plan Vollständigkeit
|
||||
- Group-Activities (`/diary-date-activities/group*`)
|
||||
- Activity-Member-Zuordnung (`/diary-member-activities/*`)
|
||||
- Plan-Item Group-Update, Duration/DurationText vollständig
|
||||
- Drag&Drop-Reorder final
|
||||
|
||||
### Block B: Teilnehmer- und Mitglieder-Dialoge
|
||||
- MemberNotesDialog-Äquivalent (inkl. Tag-Zuordnung auf Member+Date)
|
||||
- QuickAddMemberDialog inkl. `clubmembers/set`
|
||||
- Tag-History und Activity-Stats Dialoge
|
||||
|
||||
### Block C: Gruppenverwaltung + Vorschlagslogik
|
||||
- Gruppen CRUD (`/group*`) inklusive Lead
|
||||
- New-Date Dialog mit TrainingGroup/TrainingTimes-Vorschlag
|
||||
|
||||
### Block D: Bilder/Galerie + Export
|
||||
- MemberGalleryDialog + Bildaufrufe
|
||||
- ImageDialog
|
||||
- TrainingDay PDF-Export (oder Android-kompatible Alternative)
|
||||
|
||||
### Block E: Accident + Drawing
|
||||
- AccidentFormDialog (`/accident*`)
|
||||
- CourtDrawingDialog/Render-Flow mit PredefinedActivity-Sync
|
||||
|
||||
### Block F: Socket-Live-Updates
|
||||
- Nur Diary-relevante Event-Subsets robust in VM integrieren
|
||||
- Dokumentation in `/docs/socket_contract.md`
|
||||
|
||||
## 4) Konkreter nächster Umsetzungsschritt
|
||||
Wenn du fortsetzen möchtest, starte mit **Block A** (Training-Plan Vollständigkeit), da er den größten funktionalen Mehrwert für den täglichen Diary-Betrieb liefert und auf dem bereits migrierten Stand aufsetzt.
|
||||
87
docs/socket_contract.md
Normal file
87
docs/socket_contract.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Socket Contract (Vue <-> Android)
|
||||
|
||||
## Zielbild (Android, zentral)
|
||||
- Eine zentrale WebSocket-Implementierung in `SocketManager` (OkHttp).
|
||||
- Auth beim Connect über Session-Daten (`authcode`, `userid`).
|
||||
- Exponential Reconnect-Backoff.
|
||||
- Event-Stream als `SharedFlow`.
|
||||
- Repository mappt Transport-Events auf Domain-Events.
|
||||
- ViewModels abonnieren nur Domain-Events (kein direkter Socket-Zugriff).
|
||||
|
||||
## Relevante Android-Dateien
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/network/SocketManager.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/network/SocketModels.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/utils/SocketMessageUtils.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/repository/TrainingstagebuchRepository.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/services/DiaryService.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/viewmodel/DiaryViewModel.kt`
|
||||
- `android-app/app/src/main/java/de/trainingstagebuch/app/MainActivity.kt`
|
||||
|
||||
## Vue-Referenz
|
||||
- Quelle: `frontend/src/services/socketService.js`
|
||||
- Verbindungsaufbau über Socket.IO (`path: /socket.io/`), Events via `socket.on(...)`.
|
||||
- Club-Room-Handling via `join-club` / `leave-club`.
|
||||
|
||||
## URL / Transport
|
||||
- Android Basis: `http://10.0.2.2:3005`
|
||||
- Daraus abgeleitet für OkHttp WebSocket:
|
||||
- `ws://10.0.2.2:3005/socket.io/?EIO=4&transport=websocket&authcode=<token>&userid=<username>`
|
||||
|
||||
## Auth beim Connect
|
||||
Android sendet Auth auf mehreren kompatiblen Ebenen:
|
||||
1. Query-Parameter im Handshake: `authcode`, `userid`
|
||||
2. Direkt nach `onOpen`:
|
||||
- Socket.IO namespace connect payload: `40{"authcode":"...","userid":"..."}`
|
||||
- optionales Auth-Event: `42["auth",{"authcode":"...","userid":"..."}]`
|
||||
|
||||
Zusätzlich wird bei vorhandenem Club direkt `join-club` gesendet.
|
||||
|
||||
## Lifecycle / Connection-Strategie
|
||||
- `MainActivity.onStart()` -> `SocketManager.setAppInForeground(true)`
|
||||
- `MainActivity.onStop()` -> `SocketManager.setAppInForeground(false)`
|
||||
- Connect nur wenn:
|
||||
- App im Vordergrund
|
||||
- und `requestConnect()` gesetzt (Login-Pfad)
|
||||
- und Session vollständig (`token`, `username`)
|
||||
- Logout-Pfad ruft `requestDisconnect()` über Repository auf.
|
||||
|
||||
## Reconnect
|
||||
- Exponential Backoff, initial `1s`, max `30s`.
|
||||
- Reconnect nur solange Verbindung gewünscht ist (`shouldMaintainConnection=true`) und App im Vordergrund.
|
||||
|
||||
## Room-Verhalten (Club)
|
||||
- Bei bestehender Verbindung und Club-Wechsel:
|
||||
- `leave-club` für alten Club
|
||||
- `join-club` für neuen Club
|
||||
- Entspricht dem Vue-Verhalten beim Wechsel des aktiven Clubs.
|
||||
|
||||
## Eventtypen
|
||||
Vue-Events (und Android-Mapping):
|
||||
- `participant:added`
|
||||
- `participant:removed`
|
||||
- `participant:updated`
|
||||
- `diary:note:added`
|
||||
- `diary:note:updated`
|
||||
- `diary:note:deleted`
|
||||
- `diary:tag:added`
|
||||
- `diary:tag:removed`
|
||||
- `diary:date:updated`
|
||||
- `activity:member:added`
|
||||
- `activity:member:removed`
|
||||
- `activity:changed`
|
||||
- `member:changed`
|
||||
- `group:changed`
|
||||
- `tournament:changed`
|
||||
- `schedule:match:updated`
|
||||
- `schedule:match-report:submitted`
|
||||
|
||||
Kompatibilitätsaliase im Parser:
|
||||
- `activity-member:added` -> `activity:member:added`
|
||||
- `activity-member:removed` -> `activity:member:removed`
|
||||
|
||||
## Event-Pipeline in Android
|
||||
1. `SocketManager.events: SharedFlow<SocketEvent>` (Transport-Ebene)
|
||||
2. `TrainingstagebuchRepository.socketDomainEvents: SharedFlow<DomainSocketEvent>` (Domain-Ebene)
|
||||
3. Services/ViewModels abonnieren Domain-Events (z. B. `DiaryService`/`DiaryViewModel`).
|
||||
|
||||
Damit sind Socket-Duplikate aus dem Diary-Port entfernt: Diary-ViewModel nutzt keine direkte `SocketManager`-Abhängigkeit mehr.
|
||||
61
docs/training_block_port.md
Normal file
61
docs/training_block_port.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Training Block Port (Teil 2)
|
||||
|
||||
Stand: 2026-03-06
|
||||
|
||||
## Umgesetzt
|
||||
|
||||
### 1) PredefinedActivities.vue -> Android
|
||||
- Vollständiger Screen-Flow in Compose: Listenansicht, Suche, Auswahl, Editor, Save/Cancel.
|
||||
- Merge/Deduplicate mit Confirm-Dialogen.
|
||||
- Duplicate-Code-Check vor Create mit expliziter Bestätigung.
|
||||
- Bildliste + Bildlöschung (mit Confirm).
|
||||
- Loading/Error/Retry über bestehendes ErrorFeedback-Muster (`ErrorBanner` + `SnackbarHost`).
|
||||
- MVVM + StateFlow umgesetzt über:
|
||||
- `PredefinedActivitiesScreen.kt`
|
||||
- `PredefinedActivitiesViewModel.kt`
|
||||
- `PredefinedActivitiesService.kt`
|
||||
- `TrainingstagebuchRepository.kt`
|
||||
|
||||
### 2) TrainingStatsView.vue -> Android
|
||||
- Vollständiger Compose-Screen mit:
|
||||
- KPI-Übersicht (aktueller Monat, letzter Monat, stärkstes Quartal, Halbjahr, Jahr)
|
||||
- Collapsible Trainingstage
|
||||
- Collapsible Mitgliederbereich mit Sortierung
|
||||
- Detaildialog pro Mitglied
|
||||
- Detaildialog lädt zusätzlich echte API-Daten:
|
||||
- Activity-Stats
|
||||
- Last Participations
|
||||
- Loading/Error/Retry sowohl auf Seitenebene als auch im Detaildialog.
|
||||
- MVVM + StateFlow umgesetzt über:
|
||||
- `TrainingStatsScreen.kt`
|
||||
- `TrainingStatsViewModel.kt`
|
||||
- `TrainingStatsService.kt`
|
||||
- `TrainingstagebuchRepository.kt`
|
||||
|
||||
### 3) Zentrale Wiederverwendung
|
||||
- Gemeinsame periodische KPI-/Datumslogik als Utility zentralisiert:
|
||||
- `utils/TrainingStatsMetrics.kt`
|
||||
- Minimale Unit-Tests ergänzt:
|
||||
- `utils/TrainingStatsMetricsTest.kt`
|
||||
|
||||
## Genutzte API-Endpunkte
|
||||
|
||||
### Predefined Activities
|
||||
- `GET /predefined-activities`
|
||||
- `GET /predefined-activities/{id}`
|
||||
- `GET /predefined-activities/search/query?q={term}&limit={n}`
|
||||
- `POST /predefined-activities`
|
||||
- `PUT /predefined-activities/{id}`
|
||||
- `POST /predefined-activities/merge`
|
||||
- `POST /predefined-activities/deduplicate`
|
||||
- `DELETE /predefined-activities/{id}/image/{imageId}`
|
||||
|
||||
### Training Stats
|
||||
- `GET /training-stats/{clubId}`
|
||||
- `GET /member-activities/{clubId}/{memberId}?period=all`
|
||||
- `GET /member-activities/{clubId}/{memberId}/last-participations?limit=3`
|
||||
|
||||
## Unterschiede zur Vue-Version (offen)
|
||||
- `PredefinedActivities`: CourtDrawingDialog/CourtDrawingTool UI ist in Android noch nicht 1:1 vorhanden.
|
||||
- `PredefinedActivities`: Image-Upload-Flow (`POST/PUT /predefined-activities/{id}/image`) ist weiterhin offen.
|
||||
- `TrainingStats`: Web-`TrainingDetailsDialog`-Layout wurde funktional portiert, aber visuell nicht pixelgleich übernommen.
|
||||
60
docs/training_epic2_port.md
Normal file
60
docs/training_epic2_port.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Training Epic 2 Port (Android)
|
||||
|
||||
## Umgesetzte Features
|
||||
|
||||
### 1) TrainingStatsView Port
|
||||
- Neuer Compose-Screen: `TrainingStatsScreen`
|
||||
- Neuer ViewModel-Flow: `TrainingStatsViewModel` mit `StateFlow`
|
||||
- Daten laden über Repository/Service mit Club-Kontext aus Session
|
||||
- Abgebildete Vue-Funktionen:
|
||||
- KPI-Übersicht (aktive Mitglieder, Durchschnittswerte für Zeiträume)
|
||||
- Collapsible Bereiche für Trainingstage und Mitgliederliste
|
||||
- Sortierung der Mitglieder (Name, 12M, 3M, Gesamt, letztes Training)
|
||||
- Detaildialog pro Mitglied
|
||||
- Loading/Error Feedback via bestehendes ErrorFeedback-Muster (Banner + Snackbar)
|
||||
|
||||
### 2) PredefinedActivities Port
|
||||
- Neuer Compose-Screen: `PredefinedActivitiesScreen`
|
||||
- Neuer ViewModel-Flow: `PredefinedActivitiesViewModel` mit `StateFlow`
|
||||
- Abgebildete Vue-Funktionen:
|
||||
- Laden aller Activities
|
||||
- Server-Suche (`search/query`) + Clear Search
|
||||
- Auswahl und Detail-Laden einer Activity
|
||||
- Create/Update Editor (name, code, duration, durationText, description, imageLink, drawingData)
|
||||
- Merge (sourceId -> targetId)
|
||||
- Deduplicate
|
||||
- Anzeigen vorhandener Bilder + Bild löschen
|
||||
- Loading/Error/Success Feedback via bestehendes ErrorFeedback-Muster
|
||||
|
||||
### 3) Architektur-Integration
|
||||
- AppServices erweitert um:
|
||||
- `TrainingStatsService`
|
||||
- `PredefinedActivitiesService`
|
||||
- Placeholder-Ablösung für folgende Routen:
|
||||
- `/training-stats`
|
||||
- `/predefined-activities`
|
||||
- Repository/Mapper/DTOs für beide Flows ergänzt
|
||||
- Minimale Mapper-Tests ergänzt
|
||||
|
||||
## Offene TODOs
|
||||
- PredefinedActivities Image-Upload (`POST/PUT /predefined-activities/{id}/image`) ist in dieser Portierung noch nicht als Android-UI-Flow umgesetzt.
|
||||
- CourtDrawingDialog/CourtDrawingTool-Flow aus Vue (Zeichnen + Upload-Pipeline) ist für PredefinedActivities noch nicht 1:1 umgesetzt.
|
||||
- TrainingStats verwendet aktuell eine pragmatische, kompakte Compose-Darstellung; visuelle Parität zum Web-Layout kann weiter verfeinert werden.
|
||||
|
||||
## Genutzte Endpoints
|
||||
|
||||
### Training Stats
|
||||
- `GET /training-stats/{clubId}`
|
||||
|
||||
### Predefined Activities
|
||||
- `GET /predefined-activities`
|
||||
- `GET /predefined-activities/{id}`
|
||||
- `GET /predefined-activities/search/query?q={term}&limit={n}`
|
||||
- `POST /predefined-activities`
|
||||
- `PUT /predefined-activities/{id}`
|
||||
- `POST /predefined-activities/merge`
|
||||
- `POST /predefined-activities/deduplicate`
|
||||
- `DELETE /predefined-activities/{id}/image/{imageId}`
|
||||
|
||||
## Socket Events
|
||||
- Für `TrainingStatsView` und `PredefinedActivities` wurden in diesem Block keine zusätzlichen Socket-Events benötigt.
|
||||
Reference in New Issue
Block a user