Skip to content

Commit

Permalink
Merge pull request #368 from NielsPilgaard/feature/enhanced-search
Browse files Browse the repository at this point in the history
Improved User & Group Search
  • Loading branch information
NielsPilgaard authored May 12, 2024
2 parents 5f92afc + 247d75b commit c98cbd3
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 270 deletions.
74 changes: 37 additions & 37 deletions src/shared/Jordnaer.Shared/Groups/GroupSearchFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,55 @@

namespace Jordnaer.Shared;

public class GroupSearchFilter
public record GroupSearchFilter
{
public string? Name { get; set; }
public string[]? Categories { get; set; }
public string? Name { get; set; }
public string[]? Categories { get; set; } = [];

/// <summary>
/// Only show group results within this many kilometers of the <see cref="Location"/>.
/// </summary>
[Range(1, 50, ErrorMessage = "Afstand skal være mellem 1 og 50 km")]
[LocationRequired]
public int? WithinRadiusKilometers { get; set; } = 5;
/// <summary>
/// Only show group results within this many kilometers of the <see cref="Location"/>.
/// </summary>
[Range(1, 50, ErrorMessage = "Afstand skal være mellem 1 og 50 km")]
[LocationRequired]
public int? WithinRadiusKilometers { get; set; }

[RadiusRequired]
public string? Location { get; set; }
[RadiusRequired]
public string? Location { get; set; }

public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
}

file class RadiusRequiredAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;

if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
{
return ValidationResult.Success!;
}

return userSearchFilter.WithinRadiusKilometers is null
? new ValidationResult("Radius skal vælges når et område er valgt.")
: ValidationResult.Success!;
}
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;

if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
{
return ValidationResult.Success!;
}

return userSearchFilter.WithinRadiusKilometers is null
? new ValidationResult("Radius skal vælges når et område er valgt.")
: ValidationResult.Success!;
}
}
file class LocationRequiredAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
var userSearchFilter = (GroupSearchFilter)validationContext.ObjectInstance;

if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
{
return ValidationResult.Success!;
if (userSearchFilter.WithinRadiusKilometers is null && string.IsNullOrEmpty(userSearchFilter.Location))
{
return ValidationResult.Success!;

}
}

return string.IsNullOrEmpty(userSearchFilter.Location)
? new ValidationResult("Område skal vælges når en radius er valgt.")
: ValidationResult.Success!;
}
return string.IsNullOrEmpty(userSearchFilter.Location)
? new ValidationResult("Område skal vælges når en radius er valgt.")
: ValidationResult.Success!;
}
}
2 changes: 1 addition & 1 deletion src/shared/Jordnaer.Shared/UserSearch/UserSearchFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Jordnaer.Shared;
public record UserSearchFilter
{
public string? Name { get; set; }
public string[]? Categories { get; set; }
public string[]? Categories { get; set; } = [];

/// <summary>
/// Only show user results within this many kilometers of the <see cref="Location"/>.
Expand Down
2 changes: 1 addition & 1 deletion src/web/Jordnaer/Features/Category/CategorySelector.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<MudSelectExtended ItemCollection="_categories"
T="string"
Label="Personer som er interesserede i"
Label="Kategorier"
Placeholder="Vælg"
MultiSelection="true"
ValuePresenter="ValuePresenter.Chip"
Expand Down
83 changes: 46 additions & 37 deletions src/web/Jordnaer/Features/GroupSearch/GroupSearchForm.razor
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
@using Blazored.SessionStorage
@inject ISessionStorageService SessionStorage
@inject NavigationManager Navigation

<MudContainer MaxWidth="MaxWidth.Small">
<MudPaper Elevation="3" Class="pa-10 mt-5">
<MudText Typo="Typo.h6">Gå på opdagelse i Mini Møders grupper</MudText>
<p class="font-open-sans-light" style="color: @JordnaerPalette.RedHeader; font-size: 20px;">
Find Grupper nær dig
</p>

<MudDivider Class="mb-5 mt-1" />

<EditForm OnValidSubmit="@OnValidSubmit" Model="Filter">

<MudGrid Justify="Justify.SpaceAround" Spacing="2">
<MudGrid Justify="Justify.SpaceAround" Spacing="3">

<MudItem xs="12">
<MudTextField @bind-Value="Filter.Name" Label="Navn" Clearable/>
</MudItem>
<MudItem xs="8">
<AddressAutoComplete
For="() => Filter.Location"
Location="@Filter.Location"
LocationChanged="LocationChanged" />
</MudItem>
<MudItem xs="4">
<MudNumericField For="() => Filter.WithinRadiusKilometers"
@bind-Value="Filter.WithinRadiusKilometers"
Label="km"
Placeholder="Radius">
</MudNumericField>
</MudItem>

<MudItem xs="12">
<CategorySelector @bind-Categories="Filter.Categories" />
</MudItem>

<MudItem xs="12" Class="mt-8 pb-0">
<MudText Typo="Typo.h6"><MudIcon Class="mr-2" Icon="@Icons.Material.Filled.Place" /> Område</MudText>
</MudItem>

<MudItem xs="8">
<AddressAutoComplete For="@(() => Filter.Location)" @bind-Location="Filter.Location" />
</MudItem>
<MudItem xs="4">
<MudNumericField For="() => Filter.WithinRadiusKilometers"
@bind-Value="Filter.WithinRadiusKilometers"
Adornment="Adornment.End"
Label="Radius"
AdornmentText="km">
</MudNumericField>

<MudItem xs="12">
<MudTextField @bind-Value="Filter.Name" Placeholder="Gruppenavn" Label="Søg på navn" Clearable/>
</MudItem>

<MudItem xs="12" sm="11" md="10" lg="9" xl="8" Class="mt-8">

<MudButtonGroup OverrideStyles="false" Style="width: 100%;">
<MudButton FullWidth
Variant="Variant.Filled"
Color="Color.Primary"
ButtonType="ButtonType.Submit">
<MudIcon Icon="@Icons.Material.Filled.Search" />
</MudButton>
<MudButton OnClick="ClearFilter"
Color="Color.Info"
Variant="Variant.Filled">
<MudIcon Icon="@Icons.Material.Filled.Clear" />
</MudButton>
</MudButtonGroup>
<MudButtonGroup OverrideStyles="false" Style="width: 100%;">
<MudButton FullWidth
Variant="Variant.Filled"
Color="Color.Success"
ButtonType="ButtonType.Submit">
<MudIcon Icon="@Icons.Material.Filled.Search" />
</MudButton>
<MudButton OnClick="ClearFilter"
Color="Color.Transparent"
Variant="Variant.Filled"
ButtonType="ButtonType.Reset">
<MudIcon Icon="@Icons.Material.Filled.Clear" />
</MudButton>
</MudButtonGroup>

</MudItem>
</MudGrid>
</EditForm>
Expand All @@ -72,7 +73,15 @@
Filter = new GroupSearchFilter();
await FilterChanged.InvokeAsync(Filter);

await SessionStorage.RemoveItemAsync(nameof(UserSearchResult));
await SessionStorage.RemoveItemAsync(nameof(UserSearchFilter));
var uriWithQuery = new Uri(Navigation.Uri);
var uriWithoutQuery = uriWithQuery.GetLeftPart(UriPartial.Path);

Navigation.NavigateTo(uriWithoutQuery);
}

private void LocationChanged(string location)
{
Filter.Location = location;
Filter.WithinRadiusKilometers ??= 10;
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation

<MudGrid Justify="Justify.SpaceEvenly" Spacing="1">
@foreach (var group in SearchResult.Groups)
{
<MudItem xs="6" sm="4" md="4" lg="3" xl="3" xxl="3">
<MudNavLink Class="card-link" Href="@($"/groups/{group.Name}")">
<MudCard Class="pa-3 my-3" Elevation="3">
<MudCardContent Class="d-flex flex-column align-center">
<MudTextField Label="Navn" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.AlternateEmail" ReadOnly T="string" Text="@group.Name" Class="mb-5" />
<MudImage Fluid Width="200" Style="border-radius: 25%" Src="@group.ProfilePictureUrl" loading="lazy" />
<MudTextField Label="Område" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Place" ReadOnly T="string" Text="@group.DisplayLocation()" />
@foreach (var group in SearchResult.Groups)
{
<MudItem xs="6" sm="4" md="4" lg="3" xl="3" xxl="3">
<MudNavLink Class="card-link" OnClick="async ()=> await SaveScrollPositionAndNavigateTo(group)">
<MudCard Class="pa-3 my-3" Elevation="3">
<MudCardContent Class="d-flex flex-column align-center">
<MudTextField Label="Navn" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.AlternateEmail" ReadOnly T="string" Text="@group.Name" Class="mb-5" />
<MudImage Fluid Width="200" Style="border-radius: 25%" Src="@group.ProfilePictureUrl" loading="lazy" />
<MudTextField Label="Område" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Place" ReadOnly T="string" Text="@group.DisplayLocation()" />

@if (group.Categories.Any())
{
<MudDivider Class="my-4" />
@if (group.Categories.Any())
{
<MudDivider Class="my-4" />

<MudText Typo="Typo.h6"><MudIcon Class="mr-2" Icon="@Icons.Material.Filled.Star" />Kategorier</MudText>
<MudChipSet ReadOnly Class="d-flex flex-wrap justify-center flex-grow-1">
@foreach (var category in group.Categories)
{
<MudChip Color="Color.Tertiary">@category</MudChip>
}
</MudChipSet>
}
</MudCardContent>
</MudCard>
</MudNavLink>
</MudItem>
}
<MudText Typo="Typo.h6"><MudIcon Class="mr-2" Icon="@Icons.Material.Filled.Star" />Kategorier</MudText>
<MudChipSet ReadOnly Class="d-flex flex-wrap justify-center flex-grow-1">
@foreach (var category in group.Categories)
{
<MudChip Color="Color.Tertiary">@category</MudChip>
}
</MudChipSet>
}
</MudCardContent>
</MudCard>
</MudNavLink>
</MudItem>
}
</MudGrid>
<MudPagination Class="mt-5 d-flex justify-center"
BoundaryCount="0"
MiddleCount="3"
UserAttributes="@(new Dictionary<string, object> {["title"] = $"Viser {Filter.PageSize} ud af {SearchResult.TotalCount} resultater."})"
SelectedChanged="@SelectedPageChanged"
Variant="Variant.Filled"
Count="@(SearchResult.TotalCount / Filter.PageSize)"
Selected="Filter.PageNumber" />
@code {
[Parameter]
public required GroupSearchFilter Filter { get; set; }
Expand All @@ -36,4 +47,18 @@

[Parameter]
public EventCallback<int> SelectedPageChanged { get; set; }

private async Task SaveScrollPositionAndNavigateTo(GroupSlim group)
{
await JsRuntime.InvokeVoidAsync("scrollFunctions.saveScrollPosition");
Navigation.NavigateTo($"/groups/{group.Name}");
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JsRuntime.InvokeVoidAsync("scrollFunctions.loadScrollPosition");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
@using System.Linq.Expressions
@inject IDataForsyningenClient DataForsyningenClient

<MudAutocomplete T="string"
ValueChanged="SearchValueChanged"
Label="Søg i nærheden af"
Placeholder="Søg i nærheden af"
Value="Location"
SearchFuncWithCancel="@SearchForAddresses"
SelectValueOnTab
ShowProgressIndicator
OpenIcon="@string.Empty"
AdornmentIcon="@Icons.Material.Filled.Place"
Adornment="Adornment.Start"
DebounceInterval="250"
Clearable
For="@For"
Expand All @@ -24,7 +25,6 @@
[Parameter]
public required Expression<Func<string?>> For { get; set; }


public async Task<IEnumerable<string>> SearchForAddresses(string value, CancellationToken cancellationToken)
{
var searchResponse = await DataForsyningenClient.GetAddressesWithAutoComplete(value, cancellationToken);
Expand Down
Loading

0 comments on commit c98cbd3

Please sign in to comment.