Skip to content

Commit

Permalink
Extend product manager with Menuitems (#25)
Browse files Browse the repository at this point in the history
* Initial menu item groundwork

* Adds MenuItem service and repository

* Adds menu items property to products

* Small fix

* Adding ComboBox for menuitems

* Address breaking changes in products API

* Convert voucher to filter on domain model

* Update API specs

* Revert ObservableCollection to IEnumerable

* Remove ToString on domain models

* Remove unused dict

* Refactor lambda

* Changes menuitem to record

* Update API json

---------

Co-authored-by: Frederik Petersen <[email protected]>
Co-authored-by: Andreas Trøstrup <[email protected]>
  • Loading branch information
3 people authored Mar 5, 2024
1 parent 3684863 commit b2341a2
Show file tree
Hide file tree
Showing 20 changed files with 1,872 additions and 442 deletions.
1,000 changes: 853 additions & 147 deletions Shifty.Api/Generated/AnalogCoreV2/AnalogCoreV2.cs

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions Shifty.App/Components/MenuItems.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@namespace Components
@using System.ComponentModel.DataAnnotations
@using Shifty.App.Services
@using Shifty.Api.Generated.AnalogCoreV1
@using Shifty.Api.Generated.AnalogCoreV2
@using Shared
@using LanguageExt.UnsafeValueAccess
@using Shifty.App.Repositories
@inject ISnackbar Snackbar
@inject IJSRuntime JSRuntime

<MudPaper Elevation="15" Style="margin: 2em; border-radius: 5px;">
@if (_loading)
{
<MudContainer Style="width: 100%; display: flex;">
<LoadingIndicator Height="400px" />
</MudContainer>
}
</MudPaper>

@code
{
private bool _loading = true;

protected override async Task OnInitializedAsync()

Check warning on line 25 in Shifty.App/Components/MenuItems.razor

View workflow job for this annotation

GitHub Actions / dev-deploy / Build codebase / Build webapp / Build and test Webapp

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 25 in Shifty.App/Components/MenuItems.razor

View workflow job for this annotation

GitHub Actions / dev-deploy / Build codebase / Build webapp / Build and test Webapp

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
}
}
130 changes: 62 additions & 68 deletions Shifty.App/Components/ProductManager.razor
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
@namespace Components
@using System.ComponentModel.DataAnnotations
@using MudExtensions
@using Shifty.App.Services
@using Shifty.Api.Generated.AnalogCoreV1
@using Shifty.Api.Generated.AnalogCoreV2
@using Shared
@using LanguageExt.UnsafeValueAccess
@using Components
@using Shifty.App.DomainModels
@using System.Collections.ObjectModel
@inject ISnackbar Snackbar
@inject IProductService ProductService
@inject IJSRuntime JSRuntime
@inject IMenuItemService MenuItemService

<style>
.mud-table-cell {
Expand All @@ -24,7 +28,7 @@
}
<MudDataGrid
@ref="_dataGrid"
T="ProductResponse"
T="Product"
Items="@_products"
EditMode="DataGridEditMode.Form"
ReadOnly="false"
Expand All @@ -33,7 +37,7 @@
CommittedItemChanges="@CommittedItemChanges"
EditTrigger="DataGridEditTrigger.Manual"
QuickFilter="e => _showNonvisible || e.Visible"
RowStyleFunc="@_RowStyleFunc"
RowStyleFunc="@RowStyleFunc"
FixedHeader="true"
Height="calc(100vh - 250px)"
Dense="true"
Expand Down Expand Up @@ -97,6 +101,21 @@
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Description" Title="Description" IsEditable="true" />
<PropertyColumn Property="x => x.EligibleMenuItems" Title="Eligible menu items" IsEditable="true" Hidden>
<EditTemplate>
<MudComboBox
T="MenuItem"
Label="Eligible menu items"
MultiSelection="true"
Editable="true"
@bind-SelectedValues="context.Item.EligibleMenuItems">
@foreach (var mi in _allMenuItems)
{
<MudComboBoxItem Text="@mi.Name" Value="@mi">@mi.Name</MudComboBoxItem>
}
</MudComboBox>
</EditTemplate>
</PropertyColumn>
</Columns>
</MudDataGrid>

Expand All @@ -113,11 +132,12 @@
</MudPaper>
@code
{
private MudDataGrid<ProductResponse> _dataGrid;
private IEnumerable<ProductResponse> _products = new List<ProductResponse>();
private MudDataGrid<Product> _dataGrid;
private IEnumerable<Product> _products = new List<Product>();
private Dictionary<UserGroup, bool> UserGroupDict = new();

private void StartedEditingItem(ProductResponse item)
private IEnumerable<MenuItem> _allMenuItems = new List<MenuItem>();

private void StartedEditingItem(Product item)
{
UserGroupDict = new();
foreach (var group in Enum.GetValues<UserGroup>())
Expand All @@ -131,19 +151,32 @@
private bool _showNonvisible = true;
protected override async Task OnInitializedAsync()
{
var result = await ProductService.GetProducts();
_loading = false;
result.Match(
var productsResult = await ProductService.GetProducts();
productsResult.Match(
Succ: products => {
_products = products;
_products = new List<Product>(products);
StateHasChanged();
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
}
);

var menuItemsResult = await MenuItemService.GetMenuItems();

menuItemsResult.Match(
Succ: menuItems => {
_allMenuItems = menuItems;
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
}
);

_loading = false;
}

void CanceledEditingItem(ProductResponse item)
void CanceledEditingItem(Product item)
{
Snackbar.Add("Cancelled product changes", Severity.Info);
}
Expand All @@ -153,35 +186,31 @@
await JSRuntime.InvokeVoidAsync("window.focusElement", "first-edit-element");
}

async Task CommittedItemChanges(ProductResponse item)
async Task CommittedItemChanges(Product item)
{
List<UserGroup> AllowedUserGroups =
UserGroupDict.Where(e => e.Value)
.ToDictionary(kv => kv.Key, kv => kv.Value)
.Keys
.ToList();

item.AllowedUserGroups = AllowedUserGroups;

if (item.Id == 0)
{
var result = await addProduct(item, AllowedUserGroups);
var result = await ProductService.AddProduct(item);

result.Match(
Succ: async product =>
Succ: product =>
{
// Succesfully added product
Snackbar.Add("Product added", Severity.Success);

// Retrieve all items again in order to update id
var retrieveItems = await ProductService.GetProducts();
retrieveItems.Match(
Succ: items => {
_products = items;
},
Fail: error => {
// Errors while re-retrieving items, non-fatal (just means _products are slightly outdated)
Snackbar.Add(error.Message, Severity.Warning);
}
);
// Override the created product with the placeholder item
_products = _products.Where(p => p.Id != 0)
.Append(product);

StateHasChanged();
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
Expand All @@ -190,23 +219,13 @@
}
else
{
var result = await updateProduct(item, AllowedUserGroups);
var result = await ProductService.UpdateProduct(item, item.Id);

result.Match(
Succ: async result =>
Succ: product =>
{
Snackbar.Add("Product updated", Severity.Success);

var retrieveItems = await ProductService.GetProducts();
retrieveItems.Match(
Succ: items => {
_products = items;
},
Fail: error => {
// Errors while re-retrieving items, non-fatal (just means _products are slightly outdated)
Snackbar.Add(error.Message, Severity.Warning);
}
);
_products.FirstOrDefault(p => p.Id == item.Id).IsPerk = product.IsPerk;
},
Fail: error =>
{
Expand All @@ -216,49 +235,24 @@
}
}

async Task<LanguageExt.Try<ChangedProductResponse>> updateProduct(ProductResponse item, List<UserGroup>
AllowedUserGroups)
{
return await ProductService.UpdateProduct(new UpdateProductRequest{
Id = item.Id,
Name = item.Name,
Description = item.Description,
NumberOfTickets = item.NumberOfTickets,
Price = item.Price,
Visible = item.Visible,
AllowedUserGroups = AllowedUserGroups
});
}

async Task<LanguageExt.Try<ChangedProductResponse>> addProduct(ProductResponse item, List<UserGroup>
AllowedUserGroups)
{
return await ProductService.AddProduct(new AddProductRequest{
Name = item.Name,
Description = item.Description,
NumberOfTickets = item.NumberOfTickets,
Price = item.Price,
Visible = item.Visible,
AllowedUserGroups = AllowedUserGroups
});
}

void AddItemToDataGrid()
{
_products = _products.Append<ProductResponse>(new ProductResponse{
_products = _products.Append(new Product{
Id = 0,
Name = "",
Description = "",
IsPerk = false,
NumberOfTickets = 1,
Price = 0,
Visible = true,
AllowedUserGroups = new List<UserGroup>()
AllowedUserGroups = new List<UserGroup>(),
EligibleMenuItems = new List<MenuItem>(),
});
StateHasChanged();
_dataGrid.SetEditingItemAsync(_products.Last());
}

private Func<ProductResponse, int, string> _RowStyleFunc => (x, i) =>
private Func<Product, int, string> RowStyleFunc => (x, i) =>
{
if (!x.Visible)
{
Expand Down
13 changes: 7 additions & 6 deletions Shifty.App/Components/Voucher.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@using Shared
@using LanguageExt.UnsafeValueAccess
@using Shifty.App.Repositories
@using Shifty.App.DomainModels
@inject IProductService _productService
@inject IVoucherService _voucherService
@inject ISnackbar Snackbar
Expand All @@ -16,7 +17,7 @@
<MudCardContent>
<MudText Align="Align.Center" Class="mb-n4">Issue Voucher Form</MudText>
<MudForm @bind-IsValid="@_isFormValid" >
<MudAutocomplete T="ProductResponse"
<MudAutocomplete T="Product"
Required="true"
RequiredError="Product is required"
Placeholder="Select product"
Expand Down Expand Up @@ -100,7 +101,7 @@
private VoucherForm _voucherForm = new();
private bool _isFormValid = false;
private bool _showProgressBar = false;
private IEnumerable<ProductResponse> _products = new List<ProductResponse>();
private IEnumerable<Product> _products = new List<Product>();
private IEnumerable<IssueVoucherResponse> _vouchers;
private string _voucherCodes;
private MudTextField<string> _multilineReference;
Expand All @@ -109,7 +110,7 @@
{
[Required]
public string Description { get; set; }
public ProductResponse Product { get; set; }
public Product Product { get; set; }
public int Amount { get; set; } = 1;
public string Requester { get; set; }
public string Prefix { get; set; }
Expand All @@ -121,7 +122,7 @@

result.Match(
Succ: products => {
_products = products.Filter<ProductResponse>(p => p.Visible);
_products = products.Filter<Product>(p => p.Visible);
_voucherForm.Prefix = User.Claims.Single(el => el.Type.Contains("email")).Value[..3].ToUpper();
},
Fail: error => {
Expand All @@ -130,7 +131,7 @@
);
}

private async Task<IEnumerable<ProductResponse>> Products(string value)
private async Task<IEnumerable<Product>> Products(string value)

Check warning on line 134 in Shifty.App/Components/Voucher.razor

View workflow job for this annotation

GitHub Actions / dev-deploy / Build codebase / Build webapp / Build and test Webapp

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 134 in Shifty.App/Components/Voucher.razor

View workflow job for this annotation

GitHub Actions / dev-deploy / Build codebase / Build webapp / Build and test Webapp

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
if (string.IsNullOrEmpty(value))
return _products;
Expand Down Expand Up @@ -168,6 +169,6 @@
);
}

Func<ProductResponse,string> _converter = p => p != null ? $"{p.Name} - {p.NumberOfTickets} ticket" + (p.NumberOfTickets == 1 ? "" : "s") : "";
Func<Product,string> _converter = p => p != null ? $"{p.Name} - {p.NumberOfTickets} ticket" + (p.NumberOfTickets == 1 ? "" : "s") : "";
Func<string, string> _prefixValidation = str => str.Length == 3 ? null : "Prefix must be 3 letters";
}
36 changes: 36 additions & 0 deletions Shifty.App/DomainModels/MenuItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Data.Common;
using Shifty.Api.Generated.AnalogCoreV1;
using Shifty.Api.Generated.AnalogCoreV2;

namespace Shifty.App.DomainModels
{
public record MenuItem {
public int Id { get; init; }
public string Name { get; set; }
public static MenuItem FromDto(MenuItemResponse dto)
{
return new MenuItem()
{
Id = dto.Id,
Name = dto.Name
};
}

public static AddMenuItemRequest ToAddRequest(MenuItem menuItem)
{
return new AddMenuItemRequest()
{
Name = menuItem.Name
};
}

public static UpdateMenuItemRequest ToUpdateRequest(MenuItem menuItem)
{
return new UpdateMenuItemRequest()
{
Name = menuItem.Name
};
}
}
}
Loading

0 comments on commit b2341a2

Please sign in to comment.