|
7 | 7 | @inherits ModuleBase |
8 | 8 | @inject IMentorService MentorService |
9 | 9 | @inject IVillageService VillageService |
| 10 | +@inject NavigationManager NavigationManager |
10 | 11 |
|
11 | 12 | @if (!_isLoaded) |
12 | 13 | { |
|
26 | 27 | <div class="d-flex flex-wrap gap-2"> |
27 | 28 | <div> |
28 | 29 | <label class="form-label" for="filterVillage">Village</label> |
29 | | - <select id="filterVillage" class="form-select" @bind="_villageFilter" @bind:after="@(() => _page = 1)"> |
| 30 | + <select id="filterVillage" class="form-select" @bind="_villageFilter" @bind:after="NavigateWithFilters"> |
30 | 31 | <option value="0">All Villages</option> |
31 | 32 | @foreach (var v in _villages) |
32 | 33 | { |
|
36 | 37 | </div> |
37 | 38 | <div> |
38 | 39 | <label class="form-label" for="filterName">Name / Username</label> |
39 | | - <input id="filterName" type="text" class="form-control" @bind="_nameFilter" @bind:event="oninput" @bind:after="@(() => _page = 1)" placeholder="Search…" /> |
| 40 | + <input id="filterName" type="text" class="form-control" @bind="_nameFilter" @bind:event="oninput" @bind:after="NavigateWithFilters" placeholder="Search…" /> |
40 | 41 | </div> |
41 | 42 | <div class="d-flex align-items-end"> |
42 | 43 | <div class="form-check"> |
43 | | - <input id="showInactive" type="checkbox" class="form-check-input" @bind="_showInactive" @bind:after="@(() => _page = 1)" /> |
| 44 | + <input id="showInactive" type="checkbox" class="form-check-input" @bind="_showInactive" @bind:after="NavigateWithFilters" /> |
44 | 45 | <label class="form-check-label" for="showInactive">Show inactive</label> |
45 | 46 | </div> |
46 | 47 | </div> |
|
49 | 50 | <div class="d-flex align-items-end gap-2"> |
50 | 51 | <div> |
51 | 52 | <label class="form-label" for="sortBy">Sort by</label> |
52 | | - <select id="sortBy" class="form-select" @bind="_sortBy"> |
| 53 | + <select id="sortBy" class="form-select" @bind="_sortBy" @bind:after="NavigateWithFilters"> |
53 | 54 | <option value="lastname">Last Name</option> |
54 | 55 | <option value="firstname">First Name</option> |
55 | 56 | <option value="username">Username</option> |
|
59 | 60 | <option value="status">Status</option> |
60 | 61 | </select> |
61 | 62 | </div> |
62 | | - <button class="btn btn-outline-secondary" @onclick="ToggleSortDir" title="@(_sortAsc ? "Ascending" : "Descending")" aria-label="Toggle sort direction"> |
| 63 | + <button class="btn btn-outline-secondary" @onclick="ToggleSortDirAndNavigate" title="@(_sortAsc ? "Ascending" : "Descending")" aria-label="Toggle sort direction"> |
63 | 64 | <span class="oi @(_sortAsc ? "oi-sort-ascending" : "oi-sort-descending")" aria-hidden="true"></span> |
64 | 65 | </button> |
65 | 66 | </div> |
@@ -126,26 +127,31 @@ else |
126 | 127 | @mentor.GrowerCount<span class="d-md-none"> grower@(mentor.GrowerCount == 1 ? "" : "s")</span> |
127 | 128 | </div> |
128 | 129 |
|
129 | | - <!-- Status badge --> |
130 | | - <div class="mentor-col-status mb-2 mb-md-0"> |
131 | | - @if (mentor.IsDeleted) |
132 | | - { |
133 | | - <span class="badge bg-secondary">Inactive</span> |
134 | | - } |
135 | | - else |
136 | | - { |
137 | | - <span class="badge bg-success">Active</span> |
138 | | - } |
139 | | - </div> |
| 130 | + <!-- Status + Actions (same row on mobile, separate columns on md+) --> |
| 131 | + <div class="d-flex d-md-contents align-items-center justify-content-between mt-1 mt-md-0"> |
| 132 | + |
| 133 | + <!-- Status badge --> |
| 134 | + <div class="mentor-col-status mb-0 mb-md-0"> |
| 135 | + @if (mentor.IsDeleted) |
| 136 | + { |
| 137 | + <span class="badge bg-secondary">Inactive</span> |
| 138 | + } |
| 139 | + else |
| 140 | + { |
| 141 | + <span class="badge bg-success">Active</span> |
| 142 | + } |
| 143 | + </div> |
| 144 | + |
| 145 | + <!-- Actions --> |
| 146 | + <div class="mentor-col-actions gap-2 flex-shrink-0"> |
| 147 | + <a href="@EditUrl("Edit",$"username={Uri.EscapeDataString(mentor.Username)}&{BuildFilterQueryString()}")" |
| 148 | + class="btn btn-sm btn-outline-primary" |
| 149 | + aria-label="@($"Edit {mentor.DisplayName}")"> |
| 150 | + <span class="oi oi-pencil" aria-hidden="true"></span> |
| 151 | + <span class="d-none d-lg-inline ms-1">Edit</span> |
| 152 | + </a> |
| 153 | + </div> |
140 | 154 |
|
141 | | - <!-- Actions --> |
142 | | - <div class="mentor-col-actions d-flex gap-2 flex-shrink-0"> |
143 | | - <a href="@EditUrl("Edit",$"username={Uri.EscapeDataString(mentor.Username)}")" |
144 | | - class="btn btn-sm btn-outline-primary" |
145 | | - aria-label="@($"Edit {mentor.DisplayName}")"> |
146 | | - <span class="oi oi-pencil" aria-hidden="true"></span> |
147 | | - <span class="d-none d-md-inline ms-1">Edit</span> |
148 | | - </a> |
149 | 155 | </div> |
150 | 156 |
|
151 | 157 | </div> |
@@ -196,9 +202,39 @@ else |
196 | 202 |
|
197 | 203 | private void ToggleSortDir() => _sortAsc = !_sortAsc; |
198 | 204 |
|
| 205 | + private void ToggleSortDirAndNavigate() |
| 206 | + { |
| 207 | + _sortAsc = !_sortAsc; |
| 208 | + NavigateWithFilters(); |
| 209 | + } |
| 210 | + |
| 211 | + private string BuildFilterQueryString() |
| 212 | + { |
| 213 | + var parts = new List<string>(); |
| 214 | + if (_villageFilter > 0) parts.Add($"villageId={_villageFilter}"); |
| 215 | + if (!string.IsNullOrEmpty(_nameFilter)) parts.Add($"name={Uri.EscapeDataString(_nameFilter)}"); |
| 216 | + if (_showInactive) parts.Add("showInactive=1"); |
| 217 | + if (_sortBy != "lastname") parts.Add($"sortBy={Uri.EscapeDataString(_sortBy)}"); |
| 218 | + if (!_sortAsc) parts.Add("sortDesc=1"); |
| 219 | + return string.Join("&", parts); |
| 220 | + } |
| 221 | + |
| 222 | + private void NavigateWithFilters() |
| 223 | + { |
| 224 | + _page = 1; |
| 225 | + NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, BuildFilterQueryString())); |
| 226 | + } |
| 227 | + |
199 | 228 | protected override async Task OnParametersSetAsync() |
200 | 229 | { |
201 | | - _mentors = await MentorService.GetMentorsAsync(ModuleState.ModuleId) ?? new List<MentorViewModel>(); |
| 230 | + if (PageState.QueryString.TryGetValue("villageId", out var vid) && int.TryParse(vid, out var villageId)) |
| 231 | + _villageFilter = villageId; |
| 232 | + _nameFilter = PageState.QueryString.GetValueOrDefault("name", ""); |
| 233 | + _showInactive = PageState.QueryString.ContainsKey("showInactive"); |
| 234 | + _sortBy = PageState.QueryString.GetValueOrDefault("sortBy", "lastname"); |
| 235 | + _sortAsc = !PageState.QueryString.ContainsKey("sortDesc"); |
| 236 | + |
| 237 | + _mentors = await MentorService.GetMentorsAsync(ModuleState.ModuleId) ?? new List<MentorViewModel>(); |
202 | 238 | _villages = await VillageService.GetVillagesAsync() ?? new List<Village>(); |
203 | 239 | _isLoaded = true; |
204 | 240 | } |
|
241 | 277 | var idx = trimmed.LastIndexOf(' '); |
242 | 278 | return idx < 0 ? (trimmed, string.Empty) : (trimmed[(idx + 1)..], trimmed[..idx]); |
243 | 279 | } |
244 | | - } |
245 | 280 | } |
246 | 281 |
|
247 | 282 | <style> |
|
252 | 287 | .mentor-col-village { flex: 0 0 16%; min-width: 0; padding-right: .75rem; } |
253 | 288 | .mentor-col-growers { flex: 0 0 9%; min-width: 0; padding-right: .75rem; } |
254 | 289 | .mentor-col-status { flex: 0 0 5%; min-width: 0; padding-right: .75rem; } |
255 | | - .mentor-col-actions { flex: 0 0 8%; min-width: 0; white-space: nowrap; } |
| 290 | + .mentor-col-actions { flex: 1 1 auto; min-width: 0; white-space: nowrap; display: flex; justify-content: flex-end; } |
256 | 291 | </style> |
0 commit comments