Details.cshtml
6,904 bytes
| 1 | @model App.BLL.DTO.TripPollBllDto |
|---|---|
| 2 | |
| 3 | @{ |
| 4 | ViewData["Title"] = Localizer["Poll Results"]; |
| 5 | var tripId = (Guid)ViewData["TripId"]!; |
| 6 | var userId = (Guid)ViewData["UserId"]!; |
| 7 | var isCreator = (bool)ViewData["IsCreator"]!; |
| 8 | var isClosed = Model.ClosedAt != null; |
| 9 | var totalVotes = Model.Options?.Sum(o => o.VoteCount) ?? 0; |
| 10 | var maxVotes = Model.Options?.Max(o => o.VoteCount) ?? 0; |
| 11 | } |
| 12 | |
| 13 | <div class="row justify-content-center"> |
| 14 | <div class="col-lg-8"> |
| 15 | <!-- Header --> |
| 16 | <div class="d-flex justify-content-between align-items-start mb-4 flex-wrap gap-3"> |
| 17 | <div> |
| 18 | <h1 style="font-size: 1.5rem; margin-bottom: var(--sa-space-2);">@Model.Question</h1> |
| 19 | <div class="d-flex align-items-center gap-3" style="font-size: 0.9rem; color: var(--sa-gray-500);"> |
| 20 | @if (isClosed) |
| 21 | { |
| 22 | <span class="d-flex align-items-center gap-1"> |
| 23 | <span class="sa-status-dot sa-status-dot-pending"></span> @Localizer["Closed"] |
| 24 | </span> |
| 25 | } |
| 26 | else |
| 27 | { |
| 28 | <span class="d-flex align-items-center gap-1"> |
| 29 | <span class="sa-status-dot sa-status-dot-active sa-status-dot-pulse"></span> @Localizer["Open"] |
| 30 | </span> |
| 31 | } |
| 32 | <span>@Localizer["Created by"] @(Model.CreatedByUser != null ? $"{Model.CreatedByUser.FirstName} {Model.CreatedByUser.LastName}" : "?")</span> |
| 33 | <span>@totalVotes @Localizer["total votes"]</span> |
| 34 | @if (Model.AllowMultipleVotes) |
| 35 | { |
| 36 | <span><i class="bi bi-check2-all me-1"></i>@Localizer["Multiple votes allowed"]</span> |
| 37 | } |
| 38 | </div> |
| 39 | </div> |
| 40 | <div class="d-flex gap-2"> |
| 41 | @if (isCreator && !isClosed) |
| 42 | { |
| 43 | <form asp-action="Close" asp-route-id="@Model.Id" method="post" style="display:inline"> |
| 44 | <button type="submit" class="sa-btn sa-btn-sm" style="background: var(--sa-warning); color: #fff;"> |
| 45 | <i class="bi bi-lock-fill"></i> @Localizer["Close Poll"] |
| 46 | </button> |
| 47 | </form> |
| 48 | } |
| 49 | <a asp-action="Index" asp-route-tripId="@tripId" class="sa-btn sa-btn-ghost sa-btn-sm"> |
| 50 | <i class="bi bi-arrow-left"></i> @Localizer["Back"] |
| 51 | </a> |
| 52 | </div> |
| 53 | </div> |
| 54 | |
| 55 | <!-- Options --> |
| 56 | @if (Model.Options != null) |
| 57 | { |
| 58 | <div class="d-flex flex-column gap-3"> |
| 59 | @foreach (var option in Model.Options.OrderBy(o => o.DisplayOrder)) |
| 60 | { |
| 61 | var voteCount = option.VoteCount; |
| 62 | var percentage = totalVotes > 0 ? (int)(voteCount * 100.0 / totalVotes) : 0; |
| 63 | var userVoted = option.VoterUserIds.Contains(userId); |
| 64 | var isWinning = voteCount == maxVotes && maxVotes > 0; |
| 65 | |
| 66 | <div class="sa-poll-option @(userVoted ? "sa-poll-option-voted" : "") @(isWinning && isClosed ? "sa-poll-option-winner" : "")"> |
| 67 | <div class="d-flex justify-content-between align-items-center mb-3"> |
| 68 | <div class="d-flex align-items-center gap-2"> |
| 69 | <strong style="font-size: 1rem;">@option.Text</strong> |
| 70 | @if (userVoted) |
| 71 | { |
| 72 | <span class="sa-badge sa-badge-solid-secondary" style="font-size: 0.65rem;"> |
| 73 | <i class="bi bi-check2"></i> @Localizer["Your vote"] |
| 74 | </span> |
| 75 | } |
| 76 | @if (isWinning && isClosed) |
| 77 | { |
| 78 | <span class="sa-badge sa-badge-accent" style="font-size: 0.65rem;"> |
| 79 | <i class="bi bi-trophy-fill"></i> @Localizer["Winner"] |
| 80 | </span> |
| 81 | } |
| 82 | </div> |
| 83 | <div class="d-flex align-items-center gap-3"> |
| 84 | <span style="font-size: 0.9rem; color: var(--sa-gray-500);"> |
| 85 | @voteCount @Localizer["votes"] (@percentage%) |
| 86 | </span> |
| 87 | @if (!isClosed) |
| 88 | { |
| 89 | <form asp-action="Vote" method="post" style="display:inline"> |
| 90 | <input type="hidden" name="pollId" value="@Model.Id" /> |
| 91 | <input type="hidden" name="optionId" value="@option.Id" /> |
| 92 | <button type="submit" class="sa-btn sa-btn-sm sa-btn-pill @(userVoted ? "sa-btn-secondary" : "sa-btn-ghost")"> |
| 93 | @(userVoted ? Localizer["Unvote"] : Localizer["Vote"]) |
| 94 | </button> |
| 95 | </form> |
| 96 | } |
| 97 | </div> |
| 98 | </div> |
| 99 | |
| 100 | @{ |
| 101 | var barClass = isWinning && isClosed ? "sa-progress-bar-success" |
| 102 | : userVoted ? "sa-progress-bar" |
| 103 | : "sa-progress-bar-primary"; |
| 104 | } |
| 105 | <div class="sa-progress"> |
| 106 | <div class="sa-progress-bar @barClass" data-width="@percentage%"></div> |
| 107 | </div> |
| 108 | |
| 109 | @if (!Model.IsAnonymous && option.Voters.Any()) |
| 110 | { |
| 111 | <div class="d-flex gap-1 mt-2 flex-wrap"> |
| 112 | @foreach (var voter in option.Voters) |
| 113 | { |
| 114 | var voterName = $"{voter.FirstName} {voter.LastName}".Trim(); |
| 115 | var voterInitial = $"{(voter.FirstName.Length > 0 ? voter.FirstName[0].ToString() : "")}{(voter.LastName.Length > 0 ? voter.LastName[0].ToString() : "")}"; |
| 116 | <span class="sa-avatar sa-avatar-sm sa-avatar-@((voter.Id.GetHashCode() % 8 + 8) % 8 + 1)" |
| 117 | title="@voterName" style="border: none; width: 26px; height: 26px; font-size: 0.6rem;">@voterInitial</span> |
| 118 | } |
| 119 | </div> |
| 120 | } |
| 121 | </div> |
| 122 | } |
| 123 | </div> |
| 124 | } |
| 125 | </div> |
| 126 | </div> |
| 127 | |