Compare commits
3 Commits
c2f85cca57
...
7a5580e57d
| Author | SHA1 | Date |
|---|---|---|
|
|
7a5580e57d | |
|
|
51b8c2dbd4 | |
|
|
f9e72d095b |
|
|
@ -1,15 +1,15 @@
|
|||
1. Allgemeine Rahmenbedingungen
|
||||
Die Finanzverwaltung wird als Modul / Erweiterung einer bestehenden Software umgesetzt.
|
||||
Die Finanzverwaltung wird als Modul / Erweiterung der bestehenden Software umgesetzt.
|
||||
|
||||
Eine Benutzerverwaltung existiert bereits und wird genutzt.
|
||||
|
||||
Der Kegelverein ist kein eingetragener Verein, es bestehen keine steuerlichen oder rechtlichen Sonderanforderungen.
|
||||
|
||||
Es gibt ein Vereinskonto und eine Währung (EUR).
|
||||
Es gibt ein Vereinskonto und eine Währung (EUR) pro angelegtem Verin.
|
||||
|
||||
2. Benutzer- und Rollenmodell
|
||||
2.1 Rollen
|
||||
Es wird eine zusätzliche Rolle für Finanzdaten integriert (z. B. „Kassenwart“, „Finanzverwaltung“).
|
||||
Es wird eine zusätzliche Rolle für Finanzdaten integriert (z. B. „Kassenwart“ oder „Finanzverwaltung“).
|
||||
|
||||
Nur Benutzer mit dieser Rolle dürfen:
|
||||
|
||||
|
|
@ -23,275 +23,132 @@ Finanzdaten ändern oder löschen
|
|||
|
||||
Berichte exportieren
|
||||
|
||||
2.2 Zugriffsrechte
|
||||
Benutzer ohne Finanzrolle haben keinen Zugriff auf Finanzdaten.
|
||||
## 2.2 Zugriffsrechte
|
||||
Benutzer ohne Finanzrolle haben Änderungsrechte auf Finanzdaten.
|
||||
|
||||
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein (falls von der bestehenden Software unterstützt).
|
||||
Leserechte und Schreibrechte müssen klar getrennt umsetzbar sein.
|
||||
|
||||
3. Kategorienverwaltung
|
||||
3.1 Kategorien für Einnahmen und Ausgaben
|
||||
# 3. Kategorienverwaltung
|
||||
## 3.1 Kategorien für Einnahmen und Ausgaben
|
||||
Einnahmen und Ausgaben müssen kategorisiert werden.
|
||||
|
||||
Kategorien sind:
|
||||
- frei definierbar
|
||||
- beliebig erweiterbar
|
||||
- editierbar (Name ändern)
|
||||
- deaktivierbar, ohne bestehende Buchungen zu verändern
|
||||
|
||||
frei definierbar
|
||||
|
||||
beliebig erweiterbar
|
||||
|
||||
editierbar (Name ändern)
|
||||
|
||||
deaktivierbar, ohne bestehende Buchungen zu verändern
|
||||
|
||||
3.2 Kategorietypen
|
||||
## 3.2 Kategorietypen
|
||||
Es muss mindestens eine Unterscheidung geben zwischen:
|
||||
- Einnahmen-Kategorien
|
||||
- Ausgaben-Kategorien
|
||||
- Kategorien sollten für bessere Usability zusätzlich mit einer Farbe und einer Gruppierung oder Icons versehbar sein
|
||||
|
||||
Einnahmen-Kategorien
|
||||
|
||||
Ausgaben-Kategorien
|
||||
|
||||
4. Buchungen (Kassenbuch)
|
||||
4.1 Erfassung von Einnahmen und Ausgaben
|
||||
# 4. Buchungen (Kassenbuch)
|
||||
## 4.1 Erfassung von Einnahmen und Ausgaben
|
||||
Für jede Buchung müssen folgende Felder vorhanden sein:
|
||||
|
||||
Buchungsart: Einnahme / Ausgabe
|
||||
- Buchungsart: Einnahme / Ausgabe
|
||||
- Betrag (positiv, in EUR)
|
||||
- Kategorie
|
||||
- Buchungsdatum (frei wählbar, nicht zwingend aktuelles Datum)
|
||||
- Optionaler Kommentar / Beschreibung
|
||||
- Optionaler externer Belegverweis (z. B. Belegnummer oder Text)
|
||||
|
||||
Betrag (positiv, in EUR)
|
||||
|
||||
Kategorie
|
||||
|
||||
Buchungsdatum (frei wählbar, nicht zwingend aktuelles Datum)
|
||||
|
||||
Optionaler Kommentar / Beschreibung
|
||||
|
||||
Optionaler externer Belegverweis (z. B. Belegnummer oder Text)
|
||||
|
||||
4.2 Belege
|
||||
## 4.2 Belege
|
||||
Kein Upload von Dokumenten
|
||||
|
||||
Stattdessen:
|
||||
- Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
||||
|
||||
Freitextfeld oder Referenz zur Verknüpfung mit extern abgelegten Belegen
|
||||
|
||||
5. Kontostand und Kontenabgleich
|
||||
5.1 Kontostand
|
||||
# 5. Kontostand und Kontenabgleich
|
||||
## 5.1 Kontostand
|
||||
Die Software führt einen internen Kontostand auf Basis der erfassten Buchungen.
|
||||
|
||||
Es existiert genau ein Konto.
|
||||
Es existiert genau ein Konto pro Verein.
|
||||
|
||||
5.2 Manueller Abgleich
|
||||
## 5.2 Manueller Abgleich
|
||||
Der Abgleich mit dem realen Bankkonto erfolgt manuell.
|
||||
|
||||
Abweichungen können durch Korrekturbuchungen ausgeglichen werden.
|
||||
|
||||
Korrekturbuchungen:
|
||||
- sind normale Buchungen
|
||||
- müssen eindeutig als solche erkennbar sein (z. B. spezielle Kategorie oder Kennzeichnung)
|
||||
- Dafür werden doe Kategorien "Korrekturbuchung" und "Saldoanpassung" mit ausgeliefert, die nicht löschbar ist
|
||||
|
||||
sind normale Buchungen
|
||||
|
||||
müssen eindeutig als solche erkennbar sein (z. B. spezielle Kategorie oder Kennzeichnung)
|
||||
|
||||
6. Auswertungen und Berichte
|
||||
6.1 Standardberichte
|
||||
# 6. Auswertungen und Berichte
|
||||
## 6.1 Standardberichte
|
||||
Folgende Berichte müssen verfügbar sein:
|
||||
- Monatsübersicht
|
||||
- Jahresübersicht
|
||||
|
||||
Monatsübersicht
|
||||
## 6.2 Inhalt der Berichte
|
||||
- Anfangs- und Endsaldo
|
||||
- Summe der Einnahmen
|
||||
- Summe der Ausgaben
|
||||
- Aufschlüsselung nach Kategorien
|
||||
- Optionale Detailansicht aller Buchungen im Zeitraum
|
||||
|
||||
Jahresübersicht
|
||||
|
||||
6.2 Inhalt der Berichte
|
||||
Anfangs- und Endsaldo
|
||||
|
||||
Summe der Einnahmen
|
||||
|
||||
Summe der Ausgaben
|
||||
|
||||
Aufschlüsselung nach Kategorien
|
||||
|
||||
Optionale Detailansicht aller Buchungen im Zeitraum
|
||||
|
||||
6.3 Export
|
||||
## 6.3 Export
|
||||
Berichte müssen exportierbar sein als:
|
||||
- Excel (z. B. XLSX)
|
||||
- PDF
|
||||
|
||||
Excel (z. B. XLSX)
|
||||
|
||||
PDF
|
||||
|
||||
7. Änderungsprotokoll (Audit-Log)
|
||||
7.1 Protokollierung
|
||||
# 7. Änderungsprotokoll (Audit-Log)
|
||||
## 7.1 Protokollierung
|
||||
Alle Änderungen an Finanzdaten müssen protokolliert werden:
|
||||
- Neuanlage von Buchungen
|
||||
- Änderungen an Buchungen
|
||||
- Löschungen von Buchungen
|
||||
Hierfür kann die BaseEntity genutzt werden. Es kein zusätzlicher Aufwand erforderlich.
|
||||
|
||||
Neuanlage von Buchungen
|
||||
|
||||
Änderungen an Buchungen
|
||||
|
||||
Löschungen von Buchungen
|
||||
|
||||
Änderungen an Kategorien
|
||||
|
||||
7.2 Protokollinhalt
|
||||
Das Protokoll enthält mindestens:
|
||||
|
||||
Datum und Uhrzeit der Änderung
|
||||
|
||||
Benutzer
|
||||
|
||||
Art der Änderung (Anlage / Änderung / Löschung)
|
||||
|
||||
Alte und neue Werte (soweit sinnvoll darstellbar)
|
||||
|
||||
7.3 Einsicht
|
||||
Das Änderungsprotokoll ist für berechtigte Benutzer einsehbar.
|
||||
|
||||
8. Bedienbarkeit und Usability
|
||||
# 8. Bedienbarkeit und Usability
|
||||
Zielgruppe sind softwareerfahrene Benutzer.
|
||||
|
||||
Es wird keine Benutzeranleitung benötigt.
|
||||
Es wird kleine Benutzeranleitung benötigt.
|
||||
|
||||
Fokus liegt auf:
|
||||
|
||||
klaren Masken
|
||||
|
||||
übersichtlichen Tabellen
|
||||
|
||||
schneller Datenerfassung
|
||||
- klaren Masken
|
||||
- übersichtlichen Tabellen
|
||||
- schneller Datenerfassung
|
||||
|
||||
Konsistenz mit der bestehenden Software-Oberfläche ist wünschenswert.
|
||||
|
||||
9. Technische und funktionale Abgrenzungen (Nicht-Ziele)
|
||||
# 9. Technische und funktionale Abgrenzungen (Nicht-Ziele)
|
||||
Folgende Funktionen sind nicht Bestandteil der ersten Version:
|
||||
|
||||
Automatische Zahlungserinnerungen
|
||||
- Automatische Zahlungserinnerungen
|
||||
- Import von Bankumsätzen
|
||||
- Schnittstellen zu anderen Systemen
|
||||
- Dokumenten-Uploads
|
||||
- Mehrere Konten oder Währungen
|
||||
- Steuerliche oder rechtliche Auswertungen
|
||||
|
||||
Import von Bankumsätzen
|
||||
|
||||
Schnittstellen zu anderen Systemen
|
||||
|
||||
Dokumenten-Uploads
|
||||
|
||||
Mehrere Konten oder Währungen
|
||||
|
||||
Steuerliche oder rechtliche Auswertungen
|
||||
|
||||
10. Erweiterbarkeit (optional, aber empfehlenswert)
|
||||
# 10. Erweiterbarkeit (optional, aber empfehlenswert)
|
||||
Die Architektur sollte so gestaltet sein, dass spätere Erweiterungen möglich sind, z. B.:
|
||||
|
||||
Automatischer Kontoimport
|
||||
- Automatischer Kontoimport
|
||||
- Mehrere Konten
|
||||
- Erinnerungen
|
||||
- Schnittstellen
|
||||
|
||||
Mehrere Konten
|
||||
# 11. Integration in bestehen Anwendung
|
||||
|
||||
Erinnerungen
|
||||
## 11.1
|
||||
Abschluss eines Spieltages erzeugt neue Buchung
|
||||
- beim Abschluss eines Spieltages wird die Strafen-Summe pro Spieler und Tag dem Kassenbuch hinzugefügt
|
||||
- Dafür wird eine Kategorie "Spielstrafe" mit ausgeliefert, die nicht löschbar ist
|
||||
- Die Strafen die die Buchung auslösen werden damit als erledigt gekennzeichnet
|
||||
|
||||
Schnittstellen
|
||||
## 11.2
|
||||
Mitgliedsbeiträge
|
||||
- Die Vereineinstellungen werden um einen monatlichen Mitgliedsbeitrag erweitert
|
||||
- Dafür wird eine Kategorie "Mitgliedsbeitrag" mit ausgeliefert, die nicht löschbar ist
|
||||
- Die Mitgliedsbeiträge können über einen Button für einen Montag ermittelt und ins Kassenbuch aufgenommen werden
|
||||
|
||||
Wenn du möchtest, kann ich dir im nächsten Schritt z. B.
|
||||
|
||||
ein Datenmodell (Tabellen / Entitäten)
|
||||
|
||||
User Stories
|
||||
|
||||
oder eine Minimal-Feature-Liste für ein MVP
|
||||
ableiten – sag einfach Bescheid.
|
||||
|
||||
Du:
|
||||
ja, mach das
|
||||
ChatGPT:
|
||||
Perfekt 👍
|
||||
Dann liefere ich dir alle drei Punkte in sinnvoller Reihenfolge und aufeinander aufbauend:
|
||||
|
||||
Datenmodell (Entitäten & Felder)
|
||||
|
||||
User Stories
|
||||
|
||||
MVP-Feature-Liste (klar abgegrenzt)
|
||||
|
||||
So kannst du direkt mit Design oder Implementierung starten.
|
||||
|
||||
1. Datenmodell (fachlich / logisch)
|
||||
1.1 Benutzer
|
||||
Existiert bereits – wird hier nur referenziert
|
||||
|
||||
User
|
||||
|
||||
user_id (PK)
|
||||
|
||||
name
|
||||
|
||||
… (bestehende Felder)
|
||||
|
||||
rolle_finanz (Boolean oder Rollenreferenz)
|
||||
|
||||
1.2 Kategorien
|
||||
Kategorie
|
||||
|
||||
kategorie_id (PK)
|
||||
|
||||
name
|
||||
|
||||
typ (EINNAHME | AUSGABE)
|
||||
|
||||
aktiv (Boolean)
|
||||
|
||||
erstellt_am
|
||||
|
||||
erstellt_von (user_id)
|
||||
|
||||
1.3 Buchungen (Kassenbuch)
|
||||
Buchung
|
||||
|
||||
buchung_id (PK)
|
||||
|
||||
buchungsart (EINNAHME | AUSGABE)
|
||||
|
||||
betrag (Decimal, > 0)
|
||||
|
||||
buchungsdatum (Date)
|
||||
|
||||
kategorie_id (FK)
|
||||
|
||||
kommentar (Text, optional)
|
||||
|
||||
belegreferenz (Text, optional)
|
||||
|
||||
ist_korrekturbuchung (Boolean)
|
||||
|
||||
erstellt_am (Timestamp)
|
||||
|
||||
erstellt_von (user_id)
|
||||
|
||||
geändert_am (Timestamp, optional)
|
||||
|
||||
geändert_von (user_id, optional)
|
||||
|
||||
gelöscht (Boolean, Soft Delete empfohlen)
|
||||
|
||||
1.4 Änderungsprotokoll (Audit-Log)
|
||||
Finanz_Audit_Log
|
||||
|
||||
log_id (PK)
|
||||
|
||||
entität (BUCHUNG | KATEGORIE)
|
||||
|
||||
entität_id
|
||||
|
||||
aktion (CREATE | UPDATE | DELETE)
|
||||
|
||||
feldname (optional, bei UPDATE)
|
||||
|
||||
alter_wert (Text)
|
||||
|
||||
neuer_wert (Text)
|
||||
|
||||
zeitpunkt (Timestamp)
|
||||
|
||||
benutzer_id (user_id)
|
||||
|
||||
Empfehlung: bei UPDATE pro Feld ein Logeintrag
|
||||
|
||||
1.5 Abgeleitete Werte (kein persistentes Objekt)
|
||||
Kontostand
|
||||
|
||||
wird dynamisch berechnet:
|
||||
|
||||
Summe(Einnahmen) – Summe(Ausgaben)
|
||||
2. User Stories (fachlich, umsetzungsnah)
|
||||
Kategorien
|
||||
Als Finanznutzer möchte ich Kategorien für Einnahmen und Ausgaben anlegen,
|
||||
|
|
@ -330,51 +187,3 @@ um sie weiterzugeben oder zu archivieren.
|
|||
Sicherheit & Nachvollziehbarkeit
|
||||
Als Verein möchten wir, dass alle Änderungen an Finanzdaten protokolliert werden,
|
||||
damit Transparenz und Nachvollziehbarkeit gewährleistet sind.
|
||||
|
||||
3. MVP – Minimal Viable Product
|
||||
3.1 Muss-Funktionen (Version 1.0)
|
||||
✅ Pflicht
|
||||
|
||||
Finanzrolle integrieren
|
||||
|
||||
Kategorien anlegen / bearbeiten / deaktivieren
|
||||
|
||||
Einnahmen & Ausgaben erfassen
|
||||
|
||||
Buchungen bearbeiten & (soft) löschen
|
||||
|
||||
Korrekturbuchungen
|
||||
|
||||
Kontostandberechnung
|
||||
|
||||
Monats- & Jahresübersichten
|
||||
|
||||
Export als Excel & PDF
|
||||
|
||||
Vollständiges Änderungsprotokoll
|
||||
|
||||
3.2 Sollte-Funktionen (Version 1.1)
|
||||
🔶 Optional, aber sinnvoll
|
||||
|
||||
Filter (Zeitraum, Kategorie)
|
||||
|
||||
Summen pro Kategorie
|
||||
|
||||
Kennzeichnung auffälliger Buchungen (z. B. hohe Beträge)
|
||||
|
||||
Schnellanlage über Standardkategorien
|
||||
|
||||
3.3 Explizit ausgeschlossen (vorerst)
|
||||
❌ Nicht Teil des MVP
|
||||
|
||||
Bankimport
|
||||
|
||||
Mehrere Konten
|
||||
|
||||
Mehrere Währungen
|
||||
|
||||
Dokumentenupload
|
||||
|
||||
Automatische Erinnerungen
|
||||
|
||||
Externe Schnittstellen
|
||||
|
|
@ -613,6 +613,9 @@ public class UserService : IUserService
|
|||
|
||||
await _appDb.SaveChangesAsync(ct);
|
||||
|
||||
// Assign default Viewer role to new member
|
||||
await AssignClubRoleAsync(userProfileId, clubId, "Viewer", approvedById, ct);
|
||||
|
||||
// Send notification to user
|
||||
var identityUser = await _userManager.FindByIdAsync(membership.UserProfile.IdentityUserId.ToString());
|
||||
if (identityUser?.Email != null)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,14 @@
|
|||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
|
||||
? new InteractiveServerRenderMode(prerender: false)
|
||||
: new InteractiveServerRenderMode(prerender: true);
|
||||
private IComponentRenderMode? RenderModeForPage
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = HttpContext.Request.Path;
|
||||
var disablePrerender = path.StartsWithSegments("/Account")
|
||||
|| path.StartsWithSegments("/settings");
|
||||
return new InteractiveServerRenderMode(prerender: !disablePrerender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
@using Koogle.Application.DTOs
|
||||
@using Koogle.Application.Interfaces
|
||||
@using Koogle.Domain.Enums
|
||||
|
||||
@inject IUserService UserService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-3">Rollen fuer @User.DisplayName</MudText>
|
||||
|
||||
<MudPaper Class="pa-3" Elevation="1">
|
||||
<MudStack Row="true" Spacing="2" Wrap="Wrap.Wrap">
|
||||
@foreach (var role in _availableRoles)
|
||||
{
|
||||
var hasRole = _currentRoles.Contains(role);
|
||||
<MudChip T="string"
|
||||
Color="@(hasRole ? Color.Primary : Color.Default)"
|
||||
Variant="@(hasRole ? Variant.Filled : Variant.Outlined)"
|
||||
OnClick="@(() => ToggleRole(role, hasRole))"
|
||||
Disabled="_isProcessing">
|
||||
@GetRoleDisplayName(role)
|
||||
</MudChip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
Variant="Variant.Text"
|
||||
StartIcon="@Icons.Material.Filled.RemoveCircle"
|
||||
OnClick="RemoveFromClub"
|
||||
Disabled="_isProcessing">
|
||||
Aus Verein entfernen
|
||||
</MudButton>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Close">Schliessen</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter] public UserDto User { get; set; } = null!;
|
||||
[Parameter] public Guid ClubId { get; set; }
|
||||
[Parameter] public Guid CurrentUserProfileId { get; set; }
|
||||
|
||||
// Available roles for club-admin (no SuperAdmin)
|
||||
private readonly string[] _availableRoles = { UserRole.Viewer, UserRole.Editor, UserRole.Admin };
|
||||
private List<string> _currentRoles = new();
|
||||
private bool _isProcessing;
|
||||
private bool _hasChanges;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var membership = User.ClubMemberships.FirstOrDefault(m => m.ClubId == ClubId);
|
||||
_currentRoles = membership?.Roles.ToList() ?? new List<string>();
|
||||
}
|
||||
|
||||
private static string GetRoleDisplayName(string role) => role switch
|
||||
{
|
||||
UserRole.Viewer => "Betrachter",
|
||||
UserRole.Editor => "Bearbeiter",
|
||||
UserRole.Admin => "Administrator",
|
||||
_ => role
|
||||
};
|
||||
|
||||
private async Task ToggleRole(string role, bool currentlyHasRole)
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
bool success;
|
||||
if (currentlyHasRole)
|
||||
{
|
||||
success = await UserService.RemoveClubRoleAsync(User.ProfileId, ClubId, role);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = await UserService.AssignClubRoleAsync(User.ProfileId, ClubId, role, CurrentUserProfileId);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (currentlyHasRole)
|
||||
_currentRoles.Remove(role);
|
||||
else
|
||||
_currentRoles.Add(role);
|
||||
|
||||
_hasChanges = true;
|
||||
Snackbar.Add(currentlyHasRole ? "Rolle entfernt" : "Rolle hinzugefuegt", Severity.Success);
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Fehler beim Aendern der Rolle", Severity.Error);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveFromClub()
|
||||
{
|
||||
_isProcessing = true;
|
||||
try
|
||||
{
|
||||
var success = await UserService.RemoveUserFromClubAsync(User.ProfileId, ClubId);
|
||||
if (success)
|
||||
{
|
||||
_hasChanges = true;
|
||||
Snackbar.Add("Aus Verein entfernt", Severity.Success);
|
||||
MudDialog.Close(DialogResult.Ok(_hasChanges));
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Fehler beim Entfernen", Severity.Error);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Close() => MudDialog.Close(DialogResult.Ok(_hasChanges));
|
||||
}
|
||||
|
|
@ -144,6 +144,15 @@
|
|||
</MudStack>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCard Elevation="2" Class="pa-4 rounded-lg" Style="height: 100%;">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Warning" Size="Size.Large" />
|
||||
<MudText Typo="Typo.subtitle1" Class="font-weight-bold" Align="Align.Center">Kassenbuch</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Color="Color.Secondary">autom. Buchungen, Kontoabgleich u.v.m wird euer Kassenwart lieben</MudText>
|
||||
</MudStack>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
|
||||
|
|
@ -155,7 +164,7 @@
|
|||
<MudStack Spacing="2">
|
||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
||||
<MudText>Rollenbasierte Berechtigungen (Viewer/Editor/Admin)</MudText>
|
||||
<MudText>Rollenbasierte Berechtigungen (Viewer/Editor/Schatzmeister/Admin)</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" />
|
||||
|
|
|
|||
|
|
@ -196,6 +196,61 @@ else
|
|||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="Mitglieder" BadgeData="@(_clubMembers.Count)" BadgeColor="Color.Primary">
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardContent>
|
||||
@if (_isLoadingMembers)
|
||||
{
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
|
||||
}
|
||||
else if (!_clubMembers.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">Keine Mitglieder vorhanden.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="_clubMembers" Dense="true" Hover="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>E-Mail</MudTh>
|
||||
<MudTh>Rollen</MudTh>
|
||||
<MudTh>Aktionen</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.DisplayName</MudTd>
|
||||
<MudTd DataLabel="E-Mail">@context.Identity.Email</MudTd>
|
||||
<MudTd DataLabel="Rollen">
|
||||
@{
|
||||
var membership = context.ClubMemberships.FirstOrDefault(m => m.ClubId == CurrentClubContext.ClubId);
|
||||
if (membership != null && membership.Roles.Any())
|
||||
{
|
||||
@foreach (var role in membership.Roles)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Small" Color="GetRoleColor(role)" Class="mr-1">
|
||||
@GetRoleDisplayName(role)
|
||||
</MudChip>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Warning">Keine Rolle</MudText>
|
||||
}
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Aktionen">
|
||||
<MudTooltip Text="Rollen verwalten">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Security"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenMemberRolesDialog(context))" />
|
||||
</MudTooltip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
}
|
||||
|
||||
|
|
@ -204,9 +259,11 @@ else
|
|||
private bool _isLoading = true;
|
||||
private bool _isLoadingInvitations = true;
|
||||
private bool _isLoadingPending = true;
|
||||
private bool _isLoadingMembers = true;
|
||||
private int _activeTabIndex = 0;
|
||||
private List<ClubInvitationDto> _invitations = new();
|
||||
private List<PendingMembershipDto> _pendingMemberships = new();
|
||||
private List<UserDto> _clubMembers = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
|
@ -217,19 +274,34 @@ else
|
|||
private async Task LoadAllDataAsync()
|
||||
{
|
||||
_isLoading = true;
|
||||
_isLoadingInvitations = true;
|
||||
_isLoadingPending = true;
|
||||
_isLoadingMembers = true;
|
||||
|
||||
try
|
||||
{
|
||||
var clubId = CurrentClubContext.ClubId;
|
||||
if (clubId != Guid.Empty)
|
||||
{
|
||||
_club = await ClubService.GetByIdAsync(clubId);
|
||||
await LoadInvitationsAsync();
|
||||
await LoadPendingMembershipsAsync();
|
||||
_isLoading = false;
|
||||
|
||||
_invitations = (await ClubService.GetInvitationsByClubAsync(clubId)).ToList();
|
||||
_isLoadingInvitations = false;
|
||||
|
||||
_pendingMemberships = (await UserService.GetPendingMembershipsAsync(clubId)).ToList();
|
||||
_isLoadingPending = false;
|
||||
|
||||
_clubMembers = (await UserService.GetUsersByClubAsync(clubId)).ToList();
|
||||
_isLoadingMembers = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
_isLoadingInvitations = false;
|
||||
_isLoadingPending = false;
|
||||
_isLoadingMembers = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +339,23 @@ else
|
|||
}
|
||||
}
|
||||
|
||||
private async Task LoadMembersAsync()
|
||||
{
|
||||
_isLoadingMembers = true;
|
||||
try
|
||||
{
|
||||
var clubId = CurrentClubContext.ClubId;
|
||||
if (clubId != Guid.Empty)
|
||||
{
|
||||
_clubMembers = (await UserService.GetUsersByClubAsync(clubId)).ToList();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoadingMembers = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearError()
|
||||
{
|
||||
Dispatcher.Dispatch(new ClearClubErrorAction());
|
||||
|
|
@ -461,4 +550,47 @@ else
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRoleDisplayName(string role) => role switch
|
||||
{
|
||||
UserRole.Viewer => "Betrachter",
|
||||
UserRole.Editor => "Bearbeiter",
|
||||
UserRole.Admin => "Administrator",
|
||||
_ => role
|
||||
};
|
||||
|
||||
private static Color GetRoleColor(string role) => role switch
|
||||
{
|
||||
UserRole.Admin => Color.Error,
|
||||
UserRole.Editor => Color.Warning,
|
||||
UserRole.Viewer => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
private async Task OpenMemberRolesDialog(UserDto user)
|
||||
{
|
||||
var currentUser = await UserService.GetCurrentUserAsync();
|
||||
if (currentUser == null)
|
||||
{
|
||||
Snackbar.Add("Nicht angemeldet", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ "User", user },
|
||||
{ "ClubId", CurrentClubContext.ClubId },
|
||||
{ "CurrentUserProfileId", currentUser.ProfileId }
|
||||
};
|
||||
|
||||
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small };
|
||||
var dialog = await DialogService.ShowAsync<ClubMemberRolesDialog>("Rollen verwalten", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result != null && !result.Canceled && result.Data is bool hasChanges && hasChanges)
|
||||
{
|
||||
await LoadMembersAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Globalization;
|
||||
using ApexCharts;
|
||||
using Fluxor;
|
||||
using Fluxor.Blazor.Web.ReduxDevTools;
|
||||
|
|
@ -140,4 +141,8 @@ await BootstrapSeeder.SeedTriggersAsync(app.Services);
|
|||
await DemoSeeder.SeedAsync(app.Services, app.Configuration, app.Environment);
|
||||
|
||||
|
||||
var culture = new CultureInfo("de-DE");
|
||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
Loading…
Reference in New Issue