"Back" now works for search results (#3)
- Made the application only retrieve the list of continents once per visit - Update the verbiage for phase 3 completion
This commit is contained in:
parent
feb3c5fd4a
commit
4155072990
|
@ -1,5 +1,8 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Client
|
namespace JobsJobsJobs.Client
|
||||||
{
|
{
|
||||||
|
@ -45,6 +48,33 @@ namespace JobsJobsJobs.Client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Continent>? _continents = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a list of continents (only retrieves once per application load)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="http">The HTTP client to use to obtain continents the first time</param>
|
||||||
|
/// <returns>The list of continents</returns>
|
||||||
|
/// <exception cref="ApplicationException">If the continents cannot be loaded</exception>
|
||||||
|
public async Task<IEnumerable<Continent>> GetContinents(HttpClient http)
|
||||||
|
{
|
||||||
|
if (_continents == null)
|
||||||
|
{
|
||||||
|
ServerApi.SetJwt(http, this);
|
||||||
|
var continentResult = await ServerApi.RetrieveMany<Continent>(http, "continent/all");
|
||||||
|
|
||||||
|
if (continentResult.IsOk)
|
||||||
|
{
|
||||||
|
_continents = continentResult.Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ApplicationException($"Could not load continents - {continentResult.Error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _continents;
|
||||||
|
}
|
||||||
|
|
||||||
public AppState() { }
|
public AppState() { }
|
||||||
|
|
||||||
private void NotifyChanged() => OnChange.Invoke();
|
private void NotifyChanged() => OnChange.Invoke();
|
||||||
|
|
|
@ -28,29 +28,30 @@ else
|
||||||
started!
|
started!
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
@{
|
|
||||||
/**
|
|
||||||
This is phase 3 stuff...
|
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
There @(ProfileCount == 1 ? "is" : "are") @(ProfileCount == 0 ? "no" : ProfileCount) employment
|
There @(ProfileCount == 1 ? "is" : "are") @(ProfileCount == 0 ? "no" : ProfileCount) employment
|
||||||
profile@(ProfileCount != 1 ? "s" : "") from citizens of Gitmo Nation.
|
profile@(ProfileCount != 1 ? "s" : "") from citizens of Gitmo Nation.
|
||||||
@if (ProfileCount > 0)
|
@if (ProfileCount > 0)
|
||||||
{
|
{
|
||||||
<text>Take a look around and see if you can help them find work!</text> <em>(coming soon)</em>
|
<text>Take a look around and see if you can help them find work!</text>
|
||||||
}
|
}
|
||||||
</p> */
|
</p>
|
||||||
}
|
|
||||||
</ErrorList>
|
</ErrorList>
|
||||||
}
|
}
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Phase 3 – What Works <small><em>(<span class="text-uppercase">In Progress</span> ~~ Last Updated January 10<sup>th</sup>, 2021)</em></small></h4>
|
<h4>
|
||||||
|
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/3" target="_blank">Phase 3</a> – What Works
|
||||||
|
<small><em>(v0.8 – Last Updated January 19<sup>th</sup>, 2021)</em></small>
|
||||||
|
</h4>
|
||||||
<p>
|
<p>
|
||||||
The “View Profiles” link at the side does not have any search capabilities, but it does provide a list of
|
The “View Profiles” link at the side now allows you to search for profiles by continent, the
|
||||||
citizens who have filled out profiles, along with a way to view those profiles.
|
citizen’s desire for remote work, a skill, or any text in their professional biography and experience. If you
|
||||||
|
find someone with whom you’d like to discuss potential opportunities, the name at the top of the profile links
|
||||||
|
to their No Agenda Social account, where you can use its features to get in touch.
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Phase 2 – What Works <small><em>(Last Updated January 8<sup>th</sup>, 2021)</em></small></h4>
|
<h4>Phase 2 – What Works <small><em>(v0.7 – Last Updated January 8<sup>th</sup>, 2021)</em></small></h4>
|
||||||
<p>
|
<p>
|
||||||
If you’ve gotten this far, you’ve already passed
|
If you’ve gotten this far, you’ve already passed
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/1" target="_blank">Phase 1</a>, which enabled users of
|
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/1" target="_blank">Phase 1</a>, which enabled users of
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
<InputSelect id="continentId" @bind-Value=@ProfileForm.ContinentId class="form-control">
|
<InputSelect id="continentId" @bind-Value=@ProfileForm.ContinentId class="form-control">
|
||||||
<option>– Select –</option>
|
<option>– Select –</option>
|
||||||
@foreach (var (id, name) in Continents)
|
@foreach (var (id, name) in Continents)
|
||||||
{
|
{
|
||||||
<option value="@id">@name</option>
|
<option value="@id">@name</option>
|
||||||
}
|
}
|
||||||
</InputSelect>
|
</InputSelect>
|
||||||
<ValidationMessage For=@(() => ProfileForm.ContinentId) />
|
<ValidationMessage For=@(() => ProfileForm.ContinentId) />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,19 +46,12 @@ namespace JobsJobsJobs.Client.Pages.Citizen
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
ServerApi.SetJwt(http, state);
|
ServerApi.SetJwt(http, state);
|
||||||
var continentTask = ServerApi.RetrieveMany<Continent>(http, "continent/all");
|
var continentTask = state.GetContinents(http);
|
||||||
var profileTask = ServerApi.RetrieveProfile(http, state);
|
var profileTask = ServerApi.RetrieveProfile(http, state);
|
||||||
|
|
||||||
await Task.WhenAll(continentTask, profileTask);
|
await Task.WhenAll(continentTask, profileTask);
|
||||||
|
|
||||||
if (continentTask.Result.IsOk)
|
Continents = continentTask.Result;
|
||||||
{
|
|
||||||
Continents = continentTask.Result.Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ErrorMessages.Add(continentTask.Result.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (profileTask.Result.IsOk)
|
if (profileTask.Result.IsOk)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
@page "/profile/search"
|
@page "/profile/search"
|
||||||
@inject HttpClient http
|
@inject HttpClient http
|
||||||
|
@inject NavigationManager nav
|
||||||
@inject AppState state
|
@inject AppState state
|
||||||
|
|
||||||
<PageTitle Title="Search Profiles" />
|
<PageTitle Title="Search Profiles" />
|
||||||
|
@ -17,53 +18,7 @@
|
||||||
<p>Enter one or more criteria to filter results, or just click “Search” to list all profiles.</p>
|
<p>Enter one or more criteria to filter results, or just click “Search” to list all profiles.</p>
|
||||||
}
|
}
|
||||||
<Collapsible HeaderText="Search Criteria" Collapsed=@(Searched && SearchResults.Any())>
|
<Collapsible HeaderText="Search Criteria" Collapsed=@(Searched && SearchResults.Any())>
|
||||||
<EditForm Model=@Criteria OnValidSubmit=@RetrieveProfiles>
|
<ProfileSearchForm Criteria=@Criteria OnSearch=@DoSearch Continents=@Continents />
|
||||||
<div class="form-row">
|
|
||||||
<div class="col col-12 col-sm-6 col-md-4 col-lg-3">
|
|
||||||
<label for="continentId" class="jjj-label">Continent</label>
|
|
||||||
<InputSelect id="continentId" @bind-Value=@Criteria.ContinentId class="form-control form-control-sm">
|
|
||||||
<option value="">– Any –</option>
|
|
||||||
@foreach (var (id, name) in Continents)
|
|
||||||
{
|
|
||||||
<option value="@id">@name</option>
|
|
||||||
}
|
|
||||||
</InputSelect>
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-sm-6 offset-md-2 col-lg-3 offset-lg-0">
|
|
||||||
<label class="jjj-label">Seeking Remote Work?</label><br>
|
|
||||||
<InputRadioGroup @bind-Value=@Criteria.RemoteWork>
|
|
||||||
<div class="form-check form-check-inline">
|
|
||||||
<InputRadio id="remoteNull" Value=@("") class="form-check-input" />
|
|
||||||
<label for="remoteNull" class="form-check-label">No Selection</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-check-inline">
|
|
||||||
<InputRadio id="remoteYes" Value=@("yes") class="form-check-input" />
|
|
||||||
<label for="remoteYes" class="form-check-label">Yes</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-check-inline">
|
|
||||||
<InputRadio id="remoteNo" Value=@("no") class="form-check-input" />
|
|
||||||
<label for="remoteNo" class="form-check-label">No</label>
|
|
||||||
</div>
|
|
||||||
</InputRadioGroup>
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-sm-6 col-lg-3">
|
|
||||||
<label for="skillSearch" class="jjj-label">Skill</label>
|
|
||||||
<InputText id="skillSearch" @bind-Value=@Criteria.Skill class="form-control form-control-sm"
|
|
||||||
placeholder="(free-form text)" />
|
|
||||||
</div>
|
|
||||||
<div class="col col-12 col-sm-6 col-lg-3">
|
|
||||||
<label for="bioSearch" class="jjj-label">Bio / Experience</label>
|
|
||||||
<InputText id="bioSearch" @bind-Value=@Criteria.BioExperience class="form-control form-control-sm"
|
|
||||||
placeholder="(free-form text)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<br>
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-primary">Search</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</EditForm>
|
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
<br>
|
<br>
|
||||||
@if (SearchResults.Any())
|
@if (SearchResults.Any())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
using JobsJobsJobs.Shared.Api;
|
using JobsJobsJobs.Shared.Api;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -43,19 +44,39 @@ namespace JobsJobsJobs.Client.Pages.Profile
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
ServerApi.SetJwt(http, state);
|
Continents = await state.GetContinents(http);
|
||||||
var continentResult = await ServerApi.RetrieveMany<Continent>(http, "continent/all");
|
|
||||||
|
|
||||||
if (continentResult.IsOk)
|
// Determine if we have searched before
|
||||||
|
var query = QueryHelpers.ParseQuery(nav.ToAbsoluteUri(nav.Uri).Query);
|
||||||
|
|
||||||
|
if (query.TryGetValue("Searched", out var searched))
|
||||||
{
|
{
|
||||||
Continents = continentResult.Ok;
|
Searched = Convert.ToBoolean(searched);
|
||||||
}
|
void setPart(string part, Action<string> func)
|
||||||
else
|
{
|
||||||
{
|
if (query.TryGetValue(part, out var partValue)) func(partValue);
|
||||||
ErrorMessages.Add(continentResult.Error);
|
}
|
||||||
|
setPart("ContinentId", x => Criteria.ContinentId = x);
|
||||||
|
setPart("Skill", x => Criteria.Skill = x);
|
||||||
|
setPart("BioExperience", x => Criteria.BioExperience = x);
|
||||||
|
setPart("RemoteWork", x => Criteria.RemoteWork = x);
|
||||||
|
|
||||||
|
await RetrieveProfiles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do a search
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This navigates with the parameters in the URL; this should trigger a search</remarks>
|
||||||
|
private async Task DoSearch()
|
||||||
|
{
|
||||||
|
var query = SearchQuery();
|
||||||
|
query.Add("Searched", "True");
|
||||||
|
nav.NavigateTo(QueryHelpers.AddQueryString("/profile/search", query));
|
||||||
|
await RetrieveProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retreive profiles matching the current search criteria
|
/// Retreive profiles matching the current search criteria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -64,7 +85,7 @@ namespace JobsJobsJobs.Client.Pages.Profile
|
||||||
Searching = true;
|
Searching = true;
|
||||||
|
|
||||||
var searchResult = await ServerApi.RetrieveMany<ProfileSearchResult>(http,
|
var searchResult = await ServerApi.RetrieveMany<ProfileSearchResult>(http,
|
||||||
$"profile/search{SearchQuery()}");
|
QueryHelpers.AddQueryString("profile/search", SearchQuery()));
|
||||||
|
|
||||||
if (searchResult.IsOk)
|
if (searchResult.IsOk)
|
||||||
{
|
{
|
||||||
|
@ -93,22 +114,22 @@ namespace JobsJobsJobs.Client.Pages.Profile
|
||||||
/// Create a search query string from the currently-entered criteria
|
/// Create a search query string from the currently-entered criteria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The query string for the currently-entered criteria</returns>
|
/// <returns>The query string for the currently-entered criteria</returns>
|
||||||
private string SearchQuery()
|
private IDictionary<string, string?> SearchQuery()
|
||||||
{
|
{
|
||||||
if (Criteria.IsEmptySearch) return "";
|
var dict = new Dictionary<string, string?>();
|
||||||
|
if (Criteria.IsEmptySearch) return dict;
|
||||||
|
|
||||||
string part(string name, Func<ProfileSearch, string?> func) =>
|
void part(string name, Func<ProfileSearch, string?> func)
|
||||||
string.IsNullOrEmpty(func(Criteria)) ? "" : $"{name}={WebUtility.UrlEncode(func(Criteria))}";
|
|
||||||
|
|
||||||
IEnumerable<string> parts()
|
|
||||||
{
|
{
|
||||||
yield return part("ContinentId", it => it.ContinentId);
|
if (!string.IsNullOrEmpty(func(Criteria))) dict.Add(name, func(Criteria));
|
||||||
yield return part("Skill", it => it.Skill);
|
|
||||||
yield return part("BioExperience", it => it.BioExperience);
|
|
||||||
yield return part("RemoteWork", it => it.RemoteWork);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"?{string.Join("&", parts().Where(it => !string.IsNullOrEmpty(it)).ToArray())}";
|
part("ContinentId", it => it.ContinentId);
|
||||||
|
part("Skill", it => it.Skill);
|
||||||
|
part("BioExperience", it => it.BioExperience);
|
||||||
|
part("RemoteWork", it => it.RemoteWork);
|
||||||
|
|
||||||
|
return dict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
src/JobsJobsJobs/Client/Shared/ProfileSearchForm.razor
Normal file
60
src/JobsJobsJobs/Client/Shared/ProfileSearchForm.razor
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
@using JobsJobsJobs.Shared.Api
|
||||||
|
|
||||||
|
<EditForm Model=@Criteria OnValidSubmit=@OnSearch>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col col-12 col-sm-6 col-md-4 col-lg-3">
|
||||||
|
<label for="continentId" class="jjj-label">Continent</label>
|
||||||
|
<InputSelect id="continentId" @bind-Value=@Criteria.ContinentId class="form-control form-control-sm">
|
||||||
|
<option value="">– Any –</option>
|
||||||
|
@foreach (var (id, name) in Continents)
|
||||||
|
{
|
||||||
|
<option value="@id">@name</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 offset-md-2 col-lg-3 offset-lg-0">
|
||||||
|
<label class="jjj-label">Seeking Remote Work?</label><br>
|
||||||
|
<InputRadioGroup @bind-Value=@Criteria.RemoteWork>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteNull" Value=@("") class="form-check-input" />
|
||||||
|
<label for="remoteNull" class="form-check-label">No Selection</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteYes" Value=@("yes") class="form-check-input" />
|
||||||
|
<label for="remoteYes" class="form-check-label">Yes</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteNo" Value=@("no") class="form-check-input" />
|
||||||
|
<label for="remoteNo" class="form-check-label">No</label>
|
||||||
|
</div>
|
||||||
|
</InputRadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 col-lg-3">
|
||||||
|
<label for="skillSearch" class="jjj-label">Skill</label>
|
||||||
|
<InputText id="skillSearch" @bind-Value=@Criteria.Skill class="form-control form-control-sm"
|
||||||
|
placeholder="(free-form text)" />
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 col-lg-3">
|
||||||
|
<label for="bioSearch" class="jjj-label">Bio / Experience</label>
|
||||||
|
<InputText id="bioSearch" @bind-Value=@Criteria.BioExperience class="form-control form-control-sm"
|
||||||
|
placeholder="(free-form text)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<br>
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-primary">Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public ProfileSearch Criteria { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback OnSearch { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public IEnumerable<Continent> Continents { get; set; } = default!;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user