feat(discord): add DiscordRescheduleVotingRenderer and replace inline helper

This commit is contained in:
2026-05-20 12:23:25 +03:00
parent fdfc73ae9c
commit 9712fe125b
3 changed files with 71 additions and 41 deletions
@@ -2,6 +2,7 @@ namespace GmRelay.DiscordBot.Features.Sessions;
using Dapper;
using GmRelay.DiscordBot.Infrastructure.Discord;
using GmRelay.DiscordBot.Rendering;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Sessions.RescheduleSession;
using GmRelay.Shared.Platform;
@@ -89,7 +90,7 @@ public sealed class DiscordRescheduleHandler(
var optionDtos = options.Select((o, i) => new RescheduleOptionDto(Guid.NewGuid(), i + 1, o)).ToList();
// 7. Build and send Discord vote message BEFORE transaction
var (embed, actionRow) = BuildVoteMessage(session.Title, session.CurrentScheduledAt, deadline, optionDtos, participants, []);
var (embed, actionRow) = DiscordRescheduleVotingRenderer.Render(session.Title, session.CurrentScheduledAt, deadline, optionDtos, participants, []);
var channelIdUlong = ulong.Parse(channelId);
@@ -149,45 +150,6 @@ public sealed class DiscordRescheduleHandler(
return new DiscordRescheduleResult(proposalId, optionDtos, deadline);
}
// internal for now — temporary duplication until DiscordRescheduleVotingRenderer is extracted in Task 6
internal static (EmbedProperties Embed, ActionRowProperties ActionRow) BuildVoteMessage(
string title, DateTime currentTime, DateTimeOffset deadline,
IReadOnlyList<RescheduleOptionDto> options, IReadOnlyList<VoteParticipantDto> participants, IReadOnlyList<RescheduleOptionVoteDto> votes)
{
var sb = new System.Text.StringBuilder();
sb.AppendLine($"📅 Текущее время: {currentTime.FormatMoscow()} (МСК)");
sb.AppendLine($"⏳ Дедлайн: {deadline.FormatMoscow()} (МСК)");
sb.AppendLine();
sb.AppendLine("Выберите один из вариантов:");
foreach (var option in options.OrderBy(o => o.DisplayOrder))
{
var count = votes.Count(v => v.OptionId == option.OptionId);
sb.AppendLine($"{option.DisplayOrder}. **{option.ProposedAt.FormatMoscow()}** (МСК) — {count} голосов");
}
if (participants.Count > 0)
{
sb.AppendLine();
sb.AppendLine($"Голосов: {votes.Count}/{participants.Count}");
}
var embed = new EmbedProperties()
.WithTitle($"🔄 Перенос сессии «{title}»")
.WithDescription(sb.ToString())
.WithColor(new NetCord.Color(0xFEE75C));
var actionRow = new ActionRowProperties();
foreach (var option in options.OrderBy(o => o.DisplayOrder))
{
actionRow.Add(new ButtonProperties(
$"reschedule_vote:{option.OptionId}",
$"{option.DisplayOrder}. {option.ProposedAt.ToOffset(TimeSpan.FromHours(3)):dd.MM HH:mm}",
ButtonStyle.Primary));
}
return (embed, actionRow);
}
}
internal sealed record RescheduleSessionInfoDto(string Title, DateTime CurrentScheduledAt);
@@ -1,6 +1,7 @@
namespace GmRelay.DiscordBot.Features.Sessions;
using Dapper;
using GmRelay.DiscordBot.Rendering;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Sessions.RescheduleSession;
using GmRelay.Shared.Platform;
@@ -105,7 +106,7 @@ public sealed class DiscordRescheduleVoteHandler(
await transaction.CommitAsync(ct);
// 5. Re-render and update Discord vote message
var (embed, actionRow) = DiscordRescheduleHandler.BuildVoteMessage(
var (embed, actionRow) = DiscordRescheduleVotingRenderer.Render(
proposal.Title, proposal.CurrentScheduledAt, proposal.VotingDeadlineAt,
options, participants, votes);