Skip to content

Commit e65a9f7

Browse files
committed
Refactor mentor/grower filters to use query strings
- Update Mentor and Grower modules to v2.4.0 - Refactor Mentor Index.razor: filters, sort, and navigation now use query strings for state persistence; Edit links preserve filter context; improved responsive layout - Update Mentor Edit.razor: Back/Cancel links and post-save navigation retain filter/sort state - Add name filter to Grower Index.razor; remove ClearFilters/ApplyFilters; all filter state managed via query strings for consistency
1 parent 2a13128 commit e65a9f7

5 files changed

Lines changed: 91 additions & 54 deletions

File tree

Client/Modules/Grower/Index.razor

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
}
5050
</select>
5151
</div>
52-
<div class="d-flex align-items-end gap-2">
53-
<button class="btn btn-primary" @onclick="ApplyFilters">Filter</button>
54-
<button class="btn btn-secondary" @onclick="ClearFilters">@Localizer["Action.ClearFilters"]</button>
52+
<div>
53+
<label for="nameFilter" class="form-label">Name</label>
54+
<input id="nameFilter" type="text" class="form-control" @bind="_nameFilter" @bind:event="oninput" @bind:after="NavigateWithFilters" placeholder="Search…" />
5555
</div>
5656
</div>
5757
<!-- Sort (right) -->
@@ -180,6 +180,7 @@
180180
private string _statusFilter = "";
181181
private string _villageFilter = "";
182182
private string _cohortFilter = "";
183+
private string _nameFilter = "";
183184
private bool _isLoading = false;
184185
private string _sortBy = "lastname";
185186
private bool _sortAsc = true;
@@ -206,6 +207,7 @@
206207
{
207208
_statusFilter = PageState.QueryString.GetValueOrDefault("status", "");
208209
_villageFilter = PageState.QueryString.GetValueOrDefault("villageId", "");
210+
_nameFilter = PageState.QueryString.GetValueOrDefault("name", "");
209211
_sortBy = PageState.QueryString.GetValueOrDefault("sortBy", "lastname");
210212
_sortAsc = !PageState.QueryString.ContainsKey("sortDesc");
211213
await LoadVillagesAsync();
@@ -306,6 +308,13 @@
306308
_growers = _growers.Where(g => _cohortGrowerIds.Contains(g.GrowerId)).ToList();
307309
}
308310

311+
// Apply name filter
312+
if (!string.IsNullOrWhiteSpace(_nameFilter))
313+
{
314+
var f = _nameFilter.Trim().ToLowerInvariant();
315+
_growers = _growers.Where(g => g.GrowerName?.ToLowerInvariant().Contains(f) ?? false).ToList();
316+
}
317+
309318
await LoadSummaryAsync();
310319
StateHasChanged();
311320
}
@@ -331,16 +340,6 @@
331340
return _sortAsc ? sorted : sorted.Reverse();
332341
}
333342

334-
private void ClearFilters()
335-
{
336-
NavigationManager.NavigateTo(NavigateUrl());
337-
}
338-
339-
private void ApplyFilters()
340-
{
341-
NavigateWithFilters();
342-
}
343-
344343
private void ApplyStatusFilter(string status)
345344
{
346345
_statusFilter = status;
@@ -352,6 +351,7 @@
352351
var parts = new List<string>();
353352
if (!string.IsNullOrEmpty(_statusFilter)) parts.Add($"status={Uri.EscapeDataString(_statusFilter)}");
354353
if (!string.IsNullOrEmpty(_villageFilter)) parts.Add($"villageId={Uri.EscapeDataString(_villageFilter)}");
354+
if (!string.IsNullOrEmpty(_nameFilter)) parts.Add($"name={Uri.EscapeDataString(_nameFilter)}");
355355
if (_sortBy != "lastname") parts.Add($"sortBy={Uri.EscapeDataString(_sortBy)}");
356356
if (!_sortAsc) parts.Add("sortDesc=1");
357357
return string.Join("&", parts);
@@ -360,7 +360,7 @@
360360
private void NavigateWithFilters()
361361
{
362362
var queryString = BuildFilterQueryString();
363-
NavigationManager.NavigateTo(EditUrl("", queryString));
363+
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, queryString));
364364
}
365365

366366
private string GetVillageName(int villageId)

Client/Modules/Grower/ModuleInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ public class ModuleInfo : IModule
1010
{
1111
Name = "Grower",
1212
Description = "Grower management for 10 Trees program - track active status and program exits",
13-
Version = "2.3.0",
13+
Version = "2.4.0",
1414
ServerManagerType = "",
15-
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,1.0.5,2.1.0,2.2.0,2.3.0",
15+
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,1.0.5,2.1.0,2.2.0,2.3.0,2.4.0",
1616
Dependencies = "OpenEug.TenTrees.Module.Village.1.0.0",
1717
PackageName = "OpenEug.TenTrees.Module.Grower"
1818
};

Client/Modules/Mentor/Edit.razor

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ else
1818
{
1919
<div class="container">
2020
<div class="d-flex justify-content-between align-items-center mb-3">
21-
<h4>
22-
<span class="oi oi-person me-2" aria-hidden="true"></span>
23-
@(_isCreate ? "Add Mentor" : $"Edit Mentor — {_model.DisplayName}")
24-
</h4>
21+
<div class="d-flex align-items-center gap-2">
22+
<NavLink href="@NavigateUrl(PageState.Page.Path, PageState.QueryString)" class="btn btn-outline-secondary btn-sm">
23+
<span class="oi oi-arrow-left me-1" aria-hidden="true"></span> Back
24+
</NavLink>
25+
<h4 class="mb-0">
26+
<span class="oi oi-person me-2" aria-hidden="true"></span>
27+
@(_isCreate ? "Add Mentor" : $"Edit Mentor — {_model.DisplayName}")
28+
</h4>
29+
</div>
2530
<div class="d-flex gap-2">
2631
@if (!_isCreate)
2732
{
@@ -38,9 +43,6 @@ else
3843
</button>
3944
}
4045
}
41-
<NavLink href="@NavigateUrl()" class="btn btn-outline-secondary btn-sm">
42-
<span class="oi oi-arrow-left me-1" aria-hidden="true"></span> Back to Mentors
43-
</NavLink>
4446
</div>
4547
</div>
4648

@@ -123,7 +125,7 @@ else
123125
<span>Save</span>
124126
}
125127
</button>
126-
<NavLink href="@NavigateUrl()" class="btn btn-outline-secondary">Cancel</NavLink>
128+
<NavLink href="@NavigateUrl(PageState.Page.Path, PageState.QueryString)" class="btn btn-outline-secondary">Cancel</NavLink>
127129
</div>
128130
</div>
129131
</div>
@@ -284,7 +286,7 @@ else
284286
await ScrollToPageTop();
285287
return;
286288
}
287-
NavigationManager.NavigateTo(NavigateUrl());
289+
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, PageState.QueryString));
288290
}
289291
else
290292
{

Client/Modules/Mentor/Index.razor

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@inherits ModuleBase
88
@inject IMentorService MentorService
99
@inject IVillageService VillageService
10+
@inject NavigationManager NavigationManager
1011

1112
@if (!_isLoaded)
1213
{
@@ -26,7 +27,7 @@ else
2627
<div class="d-flex flex-wrap gap-2">
2728
<div>
2829
<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">
3031
<option value="0">All Villages</option>
3132
@foreach (var v in _villages)
3233
{
@@ -36,11 +37,11 @@ else
3637
</div>
3738
<div>
3839
<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…" />
4041
</div>
4142
<div class="d-flex align-items-end">
4243
<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" />
4445
<label class="form-check-label" for="showInactive">Show inactive</label>
4546
</div>
4647
</div>
@@ -49,7 +50,7 @@ else
4950
<div class="d-flex align-items-end gap-2">
5051
<div>
5152
<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">
5354
<option value="lastname">Last Name</option>
5455
<option value="firstname">First Name</option>
5556
<option value="username">Username</option>
@@ -59,7 +60,7 @@ else
5960
<option value="status">Status</option>
6061
</select>
6162
</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">
6364
<span class="oi @(_sortAsc ? "oi-sort-ascending" : "oi-sort-descending")" aria-hidden="true"></span>
6465
</button>
6566
</div>
@@ -126,26 +127,31 @@ else
126127
@mentor.GrowerCount<span class="d-md-none"> grower@(mentor.GrowerCount == 1 ? "" : "s")</span>
127128
</div>
128129

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>
140154

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>
149155
</div>
150156

151157
</div>
@@ -196,9 +202,39 @@ else
196202

197203
private void ToggleSortDir() => _sortAsc = !_sortAsc;
198204

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+
199228
protected override async Task OnParametersSetAsync()
200229
{
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>();
202238
_villages = await VillageService.GetVillagesAsync() ?? new List<Village>();
203239
_isLoaded = true;
204240
}
@@ -241,7 +277,6 @@ else
241277
var idx = trimmed.LastIndexOf(' ');
242278
return idx < 0 ? (trimmed, string.Empty) : (trimmed[(idx + 1)..], trimmed[..idx]);
243279
}
244-
}
245280
}
246281

247282
<style>
@@ -252,5 +287,5 @@ else
252287
.mentor-col-village { flex: 0 0 16%; min-width: 0; padding-right: .75rem; }
253288
.mentor-col-growers { flex: 0 0 9%; min-width: 0; padding-right: .75rem; }
254289
.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; }
256291
</style>

Client/Modules/Mentor/ModuleInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public class ModuleInfo : IModule
99
{
1010
Name = "Mentor",
1111
Description = "Mentor user management — create, edit, activate/deactivate mentors and assign growers",
12-
Version = "2.3.0",
12+
Version = "2.4.0",
1313
ServerManagerType = "",
14-
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,1.0.5,2.1.0,2.2.0,2.3.0",
14+
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,1.0.5,2.1.0,2.2.0,2.3.0,2.4.0",
1515
Dependencies = "OpenEug.TenTrees.Module.Village.1.0.0",
1616
PackageName = "OpenEug.TenTrees.Module.Mentor"
1717
};

0 commit comments

Comments
 (0)