@page "/group/{GroupId:guid}/stats" @using GmRelay.Web.Services @using GmRelay.Shared.Domain @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using System.Security.Claims @attribute [Authorize] @inject AuthorizedSessionService SessionService @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager Navigation Статистика — GM-Relay
@if (!string.IsNullOrEmpty(errorMessage)) {
⚠️ @errorMessage
} @if (stats is null) {
⏳ Загружаем статистику…
} else if (stats.Count == 0) {
📈

Пока нет данных

После первых сессий здесь появится аналитика.

} else {
@stats.Count
Игроков
@TotalSessions
Сессий
@AvgAttendanceRate%
Средняя посещаемость
@topPlayer?.DisplayName
Самый стабильный
@foreach (var s in sortedStats) { }
Игрок @(sortColumn == "player" ? (sortDesc ? "▼" : "▲") : "") Всего @(sortColumn == "total" ? (sortDesc ? "▼" : "▲") : "") ✅ @(sortColumn == "confirmed" ? (sortDesc ? "▼" : "▲") : "") ❌ @(sortColumn == "declined" ? (sortDesc ? "▼" : "▲") : "") 💤 @(sortColumn == "noresponse" ? (sortDesc ? "▼" : "▲") : "") ⏳ @(sortColumn == "waitlist" ? (sortDesc ? "▼" : "▲") : "") % @(sortColumn == "rate" ? (sortDesc ? "▼" : "▲") : "") 🚫 @(sortColumn == "cancelled" ? (sortDesc ? "▼" : "▲") : "")
@s.DisplayName @if (!string.IsNullOrEmpty(s.ExternalUsername)) { @@@s.ExternalUsername }
@s.TotalSessions @s.ConfirmedCount @s.DeclinedCount @s.NoResponseCount @s.WaitlistedCount @s.AttendanceRate% @s.CancellationAffectedCount
}
@code { [Parameter] public Guid GroupId { get; set; } private List? stats; private List sortedStats = new(); private string? errorMessage; private string sortColumn = "confirmed"; private bool sortDesc = true; private int TotalSessions => stats?.Count > 0 ? (int)(stats.Max(s => s.TotalSessions)) : 0; private int AvgAttendanceRate => stats?.Count > 0 ? (int)(stats.Average(s => s.AttendanceRate)) : 0; private PlayerAttendanceStats? topPlayer => stats?.OrderByDescending(s => s.AttendanceRate).ThenByDescending(s => s.ConfirmedCount).FirstOrDefault(); protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var user = authState.User; if (!user.Identity?.IsAuthenticated ?? true) { Navigation.NavigateTo("/login"); return; } if (!user.TryGetPlatformIdentity(out var platform, out var externalUserId)) { Navigation.NavigateTo("/login"); return; } try { var groupManagement = await SessionService.GetGroupManagementForCurrentUserAsync(GroupId); if (groupManagement is null) { Navigation.NavigateTo("/access-denied"); return; } stats = await SessionService.GetGroupAttendanceStatsForCurrentUserAsync(GroupId) ?? new(); UpdateSortedStats(); } catch (Exception ex) { errorMessage = $"Ошибка загрузки статистики: {ex.Message}"; } } private void SortBy(string column) { if (sortColumn == column) sortDesc = !sortDesc; else { sortColumn = column; sortDesc = true; } UpdateSortedStats(); } private void UpdateSortedStats() { if (stats is null) { sortedStats = new(); return; } IOrderedEnumerable ordered = sortColumn switch { "player" => sortDesc ? stats.OrderByDescending(s => s.DisplayName) : stats.OrderBy(s => s.DisplayName), "total" => sortDesc ? stats.OrderByDescending(s => s.TotalSessions) : stats.OrderBy(s => s.TotalSessions), "confirmed" => sortDesc ? stats.OrderByDescending(s => s.ConfirmedCount) : stats.OrderBy(s => s.ConfirmedCount), "declined" => sortDesc ? stats.OrderByDescending(s => s.DeclinedCount) : stats.OrderBy(s => s.DeclinedCount), "noresponse" => sortDesc ? stats.OrderByDescending(s => s.NoResponseCount) : stats.OrderBy(s => s.NoResponseCount), "waitlist" => sortDesc ? stats.OrderByDescending(s => s.WaitlistedCount) : stats.OrderBy(s => s.WaitlistedCount), "rate" => sortDesc ? stats.OrderByDescending(s => s.AttendanceRate) : stats.OrderBy(s => s.AttendanceRate), "cancelled" => sortDesc ? stats.OrderByDescending(s => s.CancellationAffectedCount) : stats.OrderBy(s => s.CancellationAffectedCount), _ => stats.OrderByDescending(s => s.ConfirmedCount) }; sortedStats = ordered.ToList(); } private string SortIndicator(string column) => sortColumn == column ? (sortDesc ? "▼" : "▲") : ""; private string AttendanceBadgeClass(decimal rate) => rate switch { >= 75m => "rate-excellent", >= 50m => "rate-good", _ => "rate-poor" }; }