115 lines
4.8 KiB
C#
115 lines
4.8 KiB
C#
using Dapper;
|
|
using GmRelay.Shared.Domain;
|
|
using Npgsql;
|
|
using Telegram.Bot;
|
|
using Telegram.Bot.Types;
|
|
using Telegram.Bot.Types.ReplyMarkups;
|
|
|
|
namespace GmRelay.Bot.Features.Sessions.ListSessions;
|
|
|
|
internal sealed record SessionListItemDto(Guid Id, string Title, DateTime ScheduledAt, string Status, int? MaxPlayers, int PlayerCount, int WaitlistCount, bool CanManage);
|
|
|
|
internal static class SessionListMessageRenderer
|
|
{
|
|
public static (string Text, InlineKeyboardMarkup? Markup) Render(IReadOnlyList<SessionListItemDto> sessions)
|
|
{
|
|
var text = "📅 <b>Ближайшие игры:</b>\n\n";
|
|
foreach (var session in sessions)
|
|
{
|
|
var seats = session.MaxPlayers.HasValue
|
|
? $"{session.PlayerCount}/{session.MaxPlayers.Value}"
|
|
: session.PlayerCount.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
|
var waitlist = session.WaitlistCount > 0 ? $", ожидание: {session.WaitlistCount}" : string.Empty;
|
|
text += $"🔹 <b>{session.ScheduledAt.FormatMoscow()}</b> — {System.Net.WebUtility.HtmlEncode(session.Title)} (Места: {seats}{waitlist})\n";
|
|
}
|
|
|
|
var canManage = sessions.Count > 0 && sessions.First().CanManage;
|
|
if (!canManage)
|
|
{
|
|
return (text, null);
|
|
}
|
|
|
|
var buttons = new List<InlineKeyboardButton[]>();
|
|
foreach (var session in sessions)
|
|
{
|
|
var dateTitle = session.ScheduledAt.FormatMoscowShort();
|
|
buttons.Add(
|
|
[
|
|
InlineKeyboardButton.WithCallbackData($"❌ {dateTitle}", $"cancel_session:{session.Id}"),
|
|
InlineKeyboardButton.WithCallbackData($"⏰ {dateTitle}", $"reschedule_session:{session.Id}")
|
|
]);
|
|
|
|
if (SessionCapacityRules.CanPromoteWaitlistedPlayer(session.MaxPlayers, session.PlayerCount, session.WaitlistCount))
|
|
{
|
|
buttons.Add(
|
|
[
|
|
InlineKeyboardButton.WithCallbackData($"⬆️ Из ожидания {dateTitle}", $"promote_waitlist:{session.Id}")
|
|
]);
|
|
}
|
|
|
|
buttons.Add(
|
|
[
|
|
InlineKeyboardButton.WithCallbackData($"🗑 Удалить {dateTitle}", $"delete_session:{session.Id}")
|
|
]);
|
|
}
|
|
|
|
return (text, new InlineKeyboardMarkup(buttons));
|
|
}
|
|
}
|
|
|
|
public sealed class ListSessionsHandler(
|
|
NpgsqlDataSource dataSource,
|
|
ITelegramBotClient botClient)
|
|
{
|
|
public async Task HandleAsync(Message message, CancellationToken cancellationToken)
|
|
{
|
|
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
|
|
|
|
var sessions = await connection.QueryAsync<SessionListItemDto>(
|
|
@"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt, s.status as Status, s.max_players as MaxPlayers,
|
|
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Active) as PlayerCount,
|
|
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Waitlisted) as WaitlistCount,
|
|
EXISTS (
|
|
SELECT 1
|
|
FROM group_managers gm
|
|
JOIN players manager_player ON manager_player.id = gm.player_id
|
|
WHERE gm.group_id = s.group_id
|
|
AND manager_player.telegram_id = @TelegramUserId
|
|
) AS CanManage
|
|
FROM sessions s
|
|
JOIN game_groups g ON s.group_id = g.id
|
|
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
|
WHERE g.telegram_chat_id = @ChatId AND s.status != @Cancelled AND s.scheduled_at > NOW()
|
|
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
|
ORDER BY s.scheduled_at ASC",
|
|
new
|
|
{
|
|
ChatId = message.Chat.Id,
|
|
TelegramUserId = message.From?.Id,
|
|
Cancelled = SessionStatus.Cancelled,
|
|
Active = ParticipantRegistrationStatus.Active,
|
|
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
|
});
|
|
|
|
var sessionsList = sessions.ToList();
|
|
|
|
if (sessionsList.Count == 0)
|
|
{
|
|
await botClient.SendMessage(
|
|
chatId: message.Chat.Id,
|
|
text: "📭 В этой группе нет предстоящих игр.",
|
|
cancellationToken: cancellationToken);
|
|
return;
|
|
}
|
|
|
|
var renderResult = SessionListMessageRenderer.Render(sessionsList);
|
|
|
|
await botClient.SendMessage(
|
|
chatId: message.Chat.Id,
|
|
text: renderResult.Text,
|
|
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
|
replyMarkup: renderResult.Markup,
|
|
cancellationToken: cancellationToken);
|
|
}
|
|
}
|