Index.cshtml
6,501 bytes
| 1 | @model List<App.BLL.DTO.TripParticipantBllDto> |
|---|---|
| 2 | |
| 3 | @{ |
| 4 | ViewData["Title"] = Localizer["Members"]; |
| 5 | var tripId = (Guid)ViewData["TripId"]!; |
| 6 | var tripName = (string)ViewData["TripName"]!; |
| 7 | var currentUserId = (Guid)ViewData["CurrentUserId"]!; |
| 8 | var isOrganizer = (bool)ViewData["IsOrganizer"]!; |
| 9 | var pendingInvitations = ViewData["PendingInvitations"] as List<App.BLL.DTO.TripInvitationBllDto> ?? new(); |
| 10 | } |
| 11 | |
| 12 | <!-- Page Header --> |
| 13 | <div class="sa-gradient-header" style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);"> |
| 14 | <div class="d-flex justify-content-between align-items-center flex-wrap gap-3"> |
| 15 | <div> |
| 16 | <h1 style="margin-bottom: 4px;">@Localizer["Members"]</h1> |
| 17 | <p class="text-muted mb-0">@tripName · @Model.Count @Localizer["member(s)"]</p> |
| 18 | </div> |
| 19 | <div class="d-flex gap-2"> |
| 20 | @if (isOrganizer) |
| 21 | { |
| 22 | <a asp-action="Invite" asp-route-tripId="@tripId" class="sa-btn sa-btn-sm" style="background: rgba(255,255,255,0.2); color: #fff; border: 1.5px solid rgba(255,255,255,0.4);"> |
| 23 | <i class="bi bi-person-plus-fill"></i> @Localizer["Invite"] |
| 24 | </a> |
| 25 | } |
| 26 | <a asp-controller="Trips" asp-action="Details" asp-route-id="@tripId" class="sa-btn sa-btn-sm" style="background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.9);"> |
| 27 | <i class="bi bi-arrow-left"></i> |
| 28 | </a> |
| 29 | </div> |
| 30 | </div> |
| 31 | </div> |
| 32 | |
| 33 | <!-- Members List --> |
| 34 | <div class="sa-card-static mb-4"> |
| 35 | <div class="sa-card-header"> |
| 36 | <i class="bi bi-people-fill me-2"></i>@Localizer["Current Members"] |
| 37 | </div> |
| 38 | <div> |
| 39 | @{ var pIdx = 0; } |
| 40 | @foreach (var participant in Model) |
| 41 | { |
| 42 | var name = participant.User != null ? $"{participant.User.FirstName} {participant.User.LastName}" : Localizer["Unknown"].Value; |
| 43 | var initial = participant.User != null ? $"{participant.User.FirstName?[0]}{participant.User.LastName?[0]}" : "?"; |
| 44 | var email = participant.User?.Email ?? "-"; |
| 45 | |
| 46 | <div class="d-flex align-items-center gap-3 px-4 py-3" style="border-bottom: 1px solid var(--sa-gray-100);"> |
| 47 | <span class="sa-avatar sa-avatar-@((pIdx % 8) + 1)" style="border: none;">@initial</span> |
| 48 | <div style="flex: 1; min-width: 0;"> |
| 49 | <div style="font-weight: 600;"> |
| 50 | @name |
| 51 | @if (!string.IsNullOrEmpty(participant.Nickname)) |
| 52 | { |
| 53 | <small class="sa-text-muted">(@participant.Nickname)</small> |
| 54 | } |
| 55 | </div> |
| 56 | <div style="font-size: 0.85rem; color: var(--sa-gray-500);">@email</div> |
| 57 | </div> |
| 58 | <div class="d-flex align-items-center gap-2"> |
| 59 | @if (participant.Role == App.Domain.EParticipantRole.Organizer) |
| 60 | { |
| 61 | <span class="sa-badge sa-badge-accent"> |
| 62 | <i class="bi bi-star-fill" style="font-size: 0.6rem;"></i> @Localizer["Organizer"] |
| 63 | </span> |
| 64 | } |
| 65 | else |
| 66 | { |
| 67 | <span class="sa-badge sa-badge-neutral">@Localizer["Participant"]</span> |
| 68 | } |
| 69 | <span class="sa-text-muted sa-hide-mobile" style="font-size: 0.8rem;"> |
| 70 | @participant.JoinedAt.ToString("MMM dd, yyyy") |
| 71 | </span> |
| 72 | @if (isOrganizer && participant.UserId != currentUserId && participant.Role != App.Domain.EParticipantRole.Organizer) |
| 73 | { |
| 74 | <form asp-action="Remove" method="post" style="display:inline" |
| 75 | onsubmit="return confirm('@Localizer["Are you sure you want to remove this member?"]');"> |
| 76 | <input type="hidden" name="tripId" value="@tripId" /> |
| 77 | <input type="hidden" name="participantId" value="@participant.Id" /> |
| 78 | <button type="submit" class="sa-btn sa-btn-ghost sa-btn-icon sa-btn-sm" title="@Localizer["Remove"]" style="color: var(--sa-danger);"> |
| 79 | <i class="bi bi-x-lg"></i> |
| 80 | </button> |
| 81 | </form> |
| 82 | } |
| 83 | </div> |
| 84 | </div> |
| 85 | pIdx++; |
| 86 | } |
| 87 | </div> |
| 88 | </div> |
| 89 | |
| 90 | <!-- Pending Invitations --> |
| 91 | @if (isOrganizer && pendingInvitations.Any()) |
| 92 | { |
| 93 | <div class="sa-card-static"> |
| 94 | <div class="sa-card-header"> |
| 95 | <i class="bi bi-hourglass-split me-2"></i>@Localizer["Pending Invitations"] |
| 96 | </div> |
| 97 | <div> |
| 98 | @foreach (var inv in pendingInvitations) |
| 99 | { |
| 100 | <div class="d-flex align-items-center gap-3 px-4 py-3" style="border-bottom: 1px solid var(--sa-gray-100);"> |
| 101 | <div class="sa-expense-icon" style="background: var(--sa-info-light); color: var(--sa-info);"> |
| 102 | <i class="bi bi-envelope-paper"></i> |
| 103 | </div> |
| 104 | <div style="flex: 1;"> |
| 105 | <div style="font-weight: 500; font-size: 0.9rem;"> |
| 106 | @Localizer["Invited by"] @(inv.InvitedByUser != null ? $"{inv.InvitedByUser.FirstName} {inv.InvitedByUser.LastName}" : "?") |
| 107 | </div> |
| 108 | <div style="font-size: 0.8rem; color: var(--sa-gray-500);"> |
| 109 | @Localizer["Expires"] @inv.ExpiresAt.ToString("MMM dd, yyyy HH:mm") |
| 110 | </div> |
| 111 | </div> |
| 112 | <span class="sa-badge sa-badge-info">@WebApp.Helpers.EnumHelper.GetDisplayName(inv.Status)</span> |
| 113 | <form asp-action="RevokeInvitation" method="post" style="display:inline;"> |
| 114 | <input type="hidden" name="id" value="@inv.Id" /> |
| 115 | <input type="hidden" name="tripId" value="@tripId" /> |
| 116 | <button type="submit" class="sa-btn sa-btn-ghost sa-btn-icon sa-btn-sm" title="@Localizer["Revoke"]" style="color: var(--sa-danger);" |
| 117 | onclick="return confirm('@Localizer["AreYouSure"]');"> |
| 118 | <i class="bi bi-x-circle"></i> |
| 119 | </button> |
| 120 | </form> |
| 121 | </div> |
| 122 | } |
| 123 | </div> |
| 124 | </div> |
| 125 | } |
| 126 | |