diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs index 88145ee..55d4b59 100644 --- a/src/GmRelay.Web/Services/SessionService.cs +++ b/src/GmRelay.Web/Services/SessionService.cs @@ -169,6 +169,39 @@ public sealed class SessionService( new { GroupId = groupId, OwnerRole = GroupManagerRoleExtensions.OwnerValue })).ToList(); } + public async Task> GetGroupAttendanceStatsAsync(Guid groupId) + { + await using var conn = await dataSource.OpenConnectionAsync(); + return (await conn.QueryAsync( + """ + SELECT + p.id AS PlayerId, + p.display_name AS DisplayName, + p.telegram_username AS TelegramUsername, + COUNT(DISTINCT s.id) AS TotalSessions, + COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Confirmed' THEN s.id END) AS ConfirmedCount, + COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Declined' THEN s.id END) AS DeclinedCount, + COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Pending' THEN s.id END) AS NoResponseCount, + COUNT(DISTINCT CASE WHEN sp.registration_status = 'Waitlisted' THEN s.id END) AS WaitlistedCount, + COUNT(DISTINCT CASE WHEN s.status = 'Cancelled' AND sp.rsvp_status IN ('Confirmed','Declined') THEN s.id END) AS CancellationAffectedCount, + CASE WHEN COUNT(DISTINCT s.id) > 0 + THEN ROUND( + COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Confirmed' THEN s.id END) + * 100.0 / COUNT(DISTINCT s.id), 2) + ELSE 0 + END AS AttendanceRate + FROM players p + JOIN session_participants sp ON sp.player_id = p.id + JOIN sessions s ON s.id = sp.session_id + WHERE s.group_id = @GroupId + AND s.scheduled_at <= now() + AND sp.is_gm = false + GROUP BY p.id, p.display_name, p.telegram_username + ORDER BY AttendanceRate DESC, ConfirmedCount DESC + """, + new { GroupId = groupId })).ToList(); + } + public async Task AddGroupCoGmAsync( Guid groupId, long ownerTelegramId,