Index.cshtml
7,522 bytes
| 1 | @model List<WebApp.Controllers.BudgetCategoryViewModel> |
|---|---|
| 2 | |
| 3 | @{ |
| 4 | ViewData["Title"] = Localizer["Budget"]; |
| 5 | var tripId = (Guid)ViewData["TripId"]!; |
| 6 | var tripName = (string)ViewData["TripName"]!; |
| 7 | var totalPlanned = (decimal)ViewData["TotalPlanned"]!; |
| 8 | var totalSpent = (decimal)ViewData["TotalSpent"]!; |
| 9 | var remaining = totalPlanned - totalSpent; |
| 10 | var overallPct = totalPlanned > 0 ? (int)(totalSpent * 100 / totalPlanned) : 0; |
| 11 | var isOrganizer = (bool)ViewData["IsOrganizer"]!; |
| 12 | } |
| 13 | |
| 14 | <!-- Page Header --> |
| 15 | <div class="sa-gradient-header" style="background: linear-gradient(135deg, #22c55e 0%, #1a9e8f 100%);"> |
| 16 | <div class="d-flex justify-content-between align-items-center flex-wrap gap-3"> |
| 17 | <div> |
| 18 | <h1 style="margin-bottom: 4px;">@Localizer["Budget"]</h1> |
| 19 | <p class="text-muted mb-0">@tripName</p> |
| 20 | </div> |
| 21 | <div class="d-flex gap-2"> |
| 22 | @if (isOrganizer) |
| 23 | { |
| 24 | <a asp-action="CreateCategory" 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);"> |
| 25 | <i class="bi bi-plus-lg"></i> @Localizer["Add Category"] |
| 26 | </a> |
| 27 | } |
| 28 | <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);"> |
| 29 | <i class="bi bi-arrow-left"></i> |
| 30 | </a> |
| 31 | </div> |
| 32 | </div> |
| 33 | </div> |
| 34 | |
| 35 | <!-- Summary Cards --> |
| 36 | <div class="row g-3 mb-4"> |
| 37 | <div class="col-md-4"> |
| 38 | <div class="sa-card-static"> |
| 39 | <div class="sa-stat"> |
| 40 | <div class="sa-stat-icon" style="color: var(--sa-info);"><i class="bi bi-bullseye"></i></div> |
| 41 | <div class="sa-stat-value">@totalPlanned.ToString("N2")</div> |
| 42 | <div class="sa-stat-label">@Localizer["Total Planned"]</div> |
| 43 | </div> |
| 44 | </div> |
| 45 | </div> |
| 46 | <div class="col-md-4"> |
| 47 | <div class="sa-card-static"> |
| 48 | <div class="sa-stat"> |
| 49 | <div class="sa-stat-icon" style="color: @(totalSpent > totalPlanned && totalPlanned > 0 ? "var(--sa-danger)" : "var(--sa-accent)");"><i class="bi bi-credit-card"></i></div> |
| 50 | <div class="sa-stat-value" style="color: @(totalSpent > totalPlanned && totalPlanned > 0 ? "var(--sa-danger)" : "inherit");">@totalSpent.ToString("N2")</div> |
| 51 | <div class="sa-stat-label">@Localizer["Total Spent"]</div> |
| 52 | </div> |
| 53 | </div> |
| 54 | </div> |
| 55 | <div class="col-md-4"> |
| 56 | <div class="sa-card-static"> |
| 57 | <div class="sa-stat"> |
| 58 | <div class="sa-stat-icon" style="color: @(remaining >= 0 ? "var(--sa-success)" : "var(--sa-danger)");"><i class="bi bi-piggy-bank"></i></div> |
| 59 | <div class="sa-stat-value" style="color: @(remaining >= 0 ? "var(--sa-success)" : "var(--sa-danger)");">@remaining.ToString("N2")</div> |
| 60 | <div class="sa-stat-label">@Localizer["Remaining"]</div> |
| 61 | </div> |
| 62 | </div> |
| 63 | </div> |
| 64 | </div> |
| 65 | |
| 66 | <!-- Overall progress --> |
| 67 | @if (totalPlanned > 0) |
| 68 | { |
| 69 | <div class="sa-card-static mb-4"> |
| 70 | <div class="sa-card-body"> |
| 71 | <div class="d-flex justify-content-between mb-2"> |
| 72 | <span style="font-weight: 600; font-size: 0.9rem;">@Localizer["Overall Progress"]</span> |
| 73 | <span class="@(overallPct > 100 ? "text-danger fw-bold" : "sa-text-muted")" style="font-size: 0.85rem;">@overallPct%</span> |
| 74 | </div> |
| 75 | @if (overallPct > 100) |
| 76 | { |
| 77 | <small style="color: var(--sa-danger); font-weight: 600;">@Localizer["Over budget!"]</small> |
| 78 | } |
| 79 | @{ |
| 80 | var barClass = overallPct <= 60 ? "sa-progress-bar-success" : overallPct <= 85 ? "sa-progress-bar-warning" : "sa-progress-bar-danger"; |
| 81 | } |
| 82 | <div class="sa-progress sa-progress-lg"> |
| 83 | <div class="sa-progress-bar @barClass" data-width="@Math.Min(overallPct, 100)%" style="width: 0%;"></div> |
| 84 | </div> |
| 85 | </div> |
| 86 | </div> |
| 87 | } |
| 88 | |
| 89 | @if (!Model.Any()) |
| 90 | { |
| 91 | <div class="sa-empty"> |
| 92 | <div class="sa-empty-icon"><i class="bi bi-wallet2"></i></div> |
| 93 | <div class="sa-empty-title">@Localizer["No budget categories yet"]</div> |
| 94 | <p class="sa-empty-text">@Localizer["Add categories to track your trip budget."]</p> |
| 95 | <a asp-action="CreateCategory" asp-route-tripId="@tripId" class="sa-btn sa-btn-secondary sa-btn-pill"> |
| 96 | <i class="bi bi-plus-lg"></i> @Localizer["Add Category"] |
| 97 | </a> |
| 98 | </div> |
| 99 | } |
| 100 | else |
| 101 | { |
| 102 | <div class="d-flex flex-column gap-3"> |
| 103 | @foreach (var category in Model) |
| 104 | { |
| 105 | var catPct = category.ProgressPercentage; |
| 106 | var catBarClass = catPct <= 60 ? "sa-progress-bar-success" : catPct <= 85 ? "sa-progress-bar-warning" : "sa-progress-bar-danger"; |
| 107 | |
| 108 | <div class="sa-card-static"> |
| 109 | <div class="sa-card-body"> |
| 110 | <div class="d-flex justify-content-between align-items-center mb-3"> |
| 111 | <div class="d-flex align-items-center gap-3"> |
| 112 | <div class="sa-expense-icon sa-category-default"> |
| 113 | @if (!string.IsNullOrEmpty(category.IconName)) |
| 114 | { |
| 115 | <i class="bi bi-@category.IconName"></i> |
| 116 | } |
| 117 | else |
| 118 | { |
| 119 | <i class="bi bi-tag"></i> |
| 120 | } |
| 121 | </div> |
| 122 | <div> |
| 123 | <div style="font-weight: 700;">@category.Name</div> |
| 124 | <div style="font-size: 0.85rem; color: var(--sa-gray-500);"> |
| 125 | @category.SpentAmount.ToString("N2") / @category.PlannedAmount.ToString("N2") |
| 126 | </div> |
| 127 | </div> |
| 128 | </div> |
| 129 | @if (isOrganizer) |
| 130 | { |
| 131 | <div class="d-flex gap-2"> |
| 132 | <a asp-action="EditCategory" asp-route-id="@category.Id" class="sa-btn sa-btn-ghost sa-btn-icon sa-btn-sm" title="@Localizer["Edit"]"> |
| 133 | <i class="bi bi-pencil"></i> |
| 134 | </a> |
| 135 | <a asp-action="DeleteCategory" asp-route-id="@category.Id" class="sa-btn sa-btn-ghost sa-btn-icon sa-btn-sm" title="@Localizer["Delete"]" style="color: var(--sa-danger);"> |
| 136 | <i class="bi bi-trash3"></i> |
| 137 | </a> |
| 138 | </div> |
| 139 | } |
| 140 | </div> |
| 141 | <div class="d-flex justify-content-between mb-1"> |
| 142 | <small class="sa-text-muted">@catPct%</small> |
| 143 | @if (catPct > 100) |
| 144 | { |
| 145 | <small style="color: var(--sa-danger); font-weight: 600;">@Localizer["Over budget!"]</small> |
| 146 | } |
| 147 | </div> |
| 148 | <div class="sa-progress"> |
| 149 | <div class="sa-progress-bar @catBarClass" data-width="@Math.Min(catPct, 100)%"></div> |
| 150 | </div> |
| 151 | </div> |
| 152 | </div> |
| 153 | } |
| 154 | </div> |
| 155 | } |
| 156 | |