Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Product Manager #20

Merged
merged 27 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9f6fe0c
Auto generated API changes
A-Guldborg Nov 9, 2023
d520e09
Initial commit
A-Guldborg Nov 9, 2023
148944c
Bump MudBlazor to 6.11.0
duckth Nov 9, 2023
eb5b751
Convert ProductDto to ProductResponse
duckth Nov 9, 2023
4984eba
Add initial ProductManager page
duckth Nov 9, 2023
9e0564e
Get products from API
duckth Nov 9, 2023
4ac949f
Add and edit products
A-Guldborg Nov 9, 2023
d0b82e4
Global snackbar config
A-Guldborg Nov 9, 2023
6503eb6
Styling and product user groups
A-Guldborg Nov 12, 2023
67e051e
Auto-generated updated API
A-Guldborg Nov 28, 2023
2bd2152
Fix updating and creating products with user groups
A-Guldborg Nov 28, 2023
cc3019a
Implements user groups in both get and update products now with a sin…
A-Guldborg Nov 30, 2023
4eaa96a
Utilize new products/all endpoint for non-visible products
A-Guldborg Dec 1, 2023
b8f4932
Change API specs
A-Guldborg Dec 1, 2023
c0dd2a1
List of checkboxes instead of single select dropdown
A-Guldborg Dec 7, 2023
76b04d7
Remove User parameter in component
A-Guldborg Dec 7, 2023
8b4aee9
Documentation of the ProductService
A-Guldborg Dec 7, 2023
e61943b
Styling
A-Guldborg Dec 7, 2023
6eeb91c
Set visibility on edit and add item
A-Guldborg Dec 7, 2023
ebad4ca
Hide non-visible products switch
A-Guldborg Dec 7, 2023
b3a2acc
lots of things, sorry ○( ^皿^)っ Hehehe
A-Guldborg Dec 14, 2023
a17a976
Remove obsolete code
A-Guldborg Dec 14, 2023
bc92dea
Fix horizontal scrolling for MudDataGrid
marfavi Dec 28, 2023
29c23e4
change to LF
marfavi Dec 28, 2023
224d42a
Add vscode settings to gitignore
A-Guldborg Jan 18, 2024
28c356e
Use color palette for product manager
A-Guldborg Jan 18, 2024
8779d8d
Reintroduce success color on palette
A-Guldborg Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ bld/

# Visual Studio 2015 cache/options directory
.vs/
.vscode/

# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

Expand Down
5 changes: 0 additions & 5 deletions .vscode/settings.json

This file was deleted.

400 changes: 128 additions & 272 deletions Shifty.Api/Generated/AnalogCoreV1/AnalogCoreV1.cs

Large diffs are not rendered by default.

1,006 changes: 938 additions & 68 deletions Shifty.Api/Generated/AnalogCoreV2/AnalogCoreV2.cs

Large diffs are not rendered by default.

262 changes: 262 additions & 0 deletions Shifty.App/Components/ProductManager.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
@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 Components
@inject ISnackbar Snackbar
@inject IProductService ProductService

<style>
.mud-table-cell {
padding: 0.5em 0.75em !important;
}
</style>
<MudPaper Elevation="15" Style="margin: 2em; border-radius: 5px;">
@if (_loading)
{
<MudContainer Style="width: 100%; display: flex;">
<LoadingIndicator Height="400px" />
</MudContainer>
}
<MudDataGrid
@ref="_dataGrid"
T="ProductResponse"
Items="@_products"
EditMode="DataGridEditMode.Form"
ReadOnly="false"
StartedEditingItem="@StartedEditingItem"
CanceledEditingItem="@CanceledEditingItem"
CommittedItemChanges="@CommittedItemChanges"
EditTrigger="DataGridEditTrigger.Manual"
QuickFilter="e => _showNonvisible || e.Visible"
RowStyleFunc="@_RowStyleFunc"
FixedHeader="true"
Height="calc(100vh - 250px)"
Dense="true"
SortMode="MudBlazor.SortMode.None">
<Columns>
<TemplateColumn Title="Edit">
<CellTemplate>
<MudIconButton
Size="@Size.Medium"
Icon="@Icons.Material.Outlined.Edit"
Color="Color.Primary"
OnClick="@context.Actions.StartEditingItemAsync" />
</CellTemplate>
</TemplateColumn>
<PropertyColumn Property="x => x.Visible" Title="" InitialDirection="SortDirection.Descending">
<CellTemplate>
@{
if (context.Item.Visible)
{
<MudIcon Size="@Size.Small" Color="Color.Dark"
Icon="@Icons.Material.Filled.Visibility" />
}
else
{
<MudIcon Size="@Size.Small" Style="@($"color:{Colors.Grey.Default};")"
Icon="@Icons.Material.Filled.VisibilityOff" />
}
}
</CellTemplate>
<EditTemplate>
<MudSwitch Label="Visible" Color="Color.Primary" @bind-Checked="context.Item.Visible" />
</EditTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Id" Title="Id" IsEditable="false" />
<PropertyColumn Property="x => x.Name" Title="Name" IsEditable="true" />
<PropertyColumn Property="x => x.NumberOfTickets" Title="Tickets" IsEditable="true" />
<PropertyColumn Property="x => x.Price" Title="Price" IsEditable="true" />
<PropertyColumn Property='x => string.Join(", ", x.AllowedUserGroups.Select(e => e.ToString()))'
Title="User groups" IsEditable="true">
<EditTemplate>
<MudText Typo="Typo.subtitle1">Visible to:</MudText>
@foreach (var group in Enum.GetValues<UserGroup>())
{
<MudCheckBox Dense="true" Color="Color.Primary" @bind-Checked="UserGroupDict[group]" Value="@group">
@group</MudCheckBox>
}
</EditTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.IsPerk" Title="Perk?" IsEditable="false">
<CellTemplate>
@{
if (context.Item.IsPerk)
{
<MudIcon Style="color:gold;" Icon="@Icons.Material.Filled.Star" Title="IsPerk" />
}
else
{
<MudIcon Icon="@Icons.Material.Filled.StarOutline" Title="IsPerk" />
}
}
</CellTemplate>
</PropertyColumn>
<PropertyColumn Property="x => x.Description" Title="Description" IsEditable="true" />
</Columns>
</MudDataGrid>

<MudToolBar>
<MudSpacer />
<MudButton
Color="Color.Primary"
Variant="Variant.Filled"
EndIcon="@Icons.Material.Outlined.Add"
OnClick="@AddItemToDataGrid">
Add Product
</MudButton>
</MudToolBar>
</MudPaper>
@code
{
private MudDataGrid<ProductResponse> _dataGrid;
private IEnumerable<ProductResponse> _products = new List<ProductResponse>();
private Dictionary<UserGroup, bool> UserGroupDict = new();

private void StartedEditingItem(ProductResponse item)
{
UserGroupDict = new();
foreach (var group in Enum.GetValues<UserGroup>())
{
UserGroupDict.Add(group, item.AllowedUserGroups.Contains(group));
}
}

private bool _loading = true;
private bool _showNonvisible = true;
protected override async Task OnInitializedAsync()
{
var result = await ProductService.GetProducts();
_loading = false;
result.Match(
Succ: products => {
_products = products;
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
}
);
}

void CanceledEditingItem(ProductResponse item)
{
Snackbar.Add("Cancelled product changes", Severity.Info);
}

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

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

result.Match(
Succ: async 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);
}
);
},
Fail: error => {
Snackbar.Add(error.Message, Severity.Error);
}
);
}
else
{
var result = await updateProduct(item, AllowedUserGroups);

result.Match(
Succ: async result =>
{
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);
}
);
},
Fail: error =>
{
Snackbar.Add(error.Message, Severity.Warning);
}
);
}
}

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{
Id = 0,
Name = "",
Description = "",
IsPerk = false,
NumberOfTickets = 1,
Price = 0,
Visible = true,
AllowedUserGroups = new List<UserGroup>()
});
_dataGrid.SetEditingItemAsync(_products.Last());
}

private Func<ProductResponse, int, string> _RowStyleFunc => (x, i) =>
{
if (!x.Visible)
{
return "background-color:#ebebeb;font-style:italic;font-color:#8a8686;";
}
return "";
};
}
20 changes: 10 additions & 10 deletions Shifty.App/Components/Voucher.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@using Shifty.Api.Generated.AnalogCoreV2
@using Shared
@using LanguageExt.UnsafeValueAccess
@using Shifty.App.Repositories
@inject IProductService _productService
@inject IVoucherService _voucherService
@inject ISnackbar Snackbar
Expand All @@ -15,7 +16,7 @@
<MudCardContent>
<MudText Align="Align.Center" Class="mb-n4">Issue Voucher Form</MudText>
<MudForm @bind-IsValid="@_isFormValid" >
<MudAutocomplete T="ProductDto"
<MudAutocomplete T="ProductResponse"
Required="true"
RequiredError="Product is required"
Placeholder="Select product"
Expand All @@ -25,7 +26,7 @@
Label="Product"
SearchFunc="@Products"
ToStringFunc="@_converter"
@bind-Value=_voucherForm.Product/>
@bind-Value="_voucherForm.Product"/>

<MudNumericField @bind-Value="_voucherForm.Amount"
Placeholder="1"
Expand All @@ -51,7 +52,7 @@
<MudTextField T="string"
@bind-Value="_voucherForm.Prefix"
Label="Voucher prefix"
Validation="@(new Func<string,string>(prefixValidation))"
Validation="@(new Func<string,string>(_prefixValidation))"
Required="true"
Counter="3"
MaxLength="3"
Expand Down Expand Up @@ -99,7 +100,7 @@
private VoucherForm _voucherForm = new();
private bool _isFormValid = false;
private bool _showProgressBar = false;
private IEnumerable<ProductDto> _products = new List<ProductDto>();
private IEnumerable<ProductResponse> _products = new List<ProductResponse>();
private IEnumerable<IssueVoucherResponse> _vouchers;
private string _voucherCodes;
private MudTextField<string> _multilineReference;
Expand All @@ -108,15 +109,14 @@
{
[Required]
public string Description { get; set; }
public ProductDto Product { get; set; }
public ProductResponse Product { get; set; }
public int Amount { get; set; } = 1;
public string Requester { get; set; }
public string Prefix { get; set; }
}

protected override async Task OnInitializedAsync()
{
Snackbar.Configuration.PositionClass = Defaults.Classes.Position.BottomRight;
var result = await _productService.GetProducts();

result.Match(
Expand All @@ -130,7 +130,7 @@
);
}

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

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

View workflow job for this annotation

GitHub Actions / build / 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 133 in Shifty.App/Components/Voucher.razor

View workflow job for this annotation

GitHub Actions / build / 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 @@ -159,7 +159,7 @@
Succ: response => {
_showProgressBar = false;
_vouchers = response;
_voucherCodes = string.Join("\n", _vouchers.Select(response => response.VoucherCode));
_voucherCodes = string.Join("\n", _vouchers.Select(issueVoucherResponse => issueVoucherResponse.VoucherCode));
},
Fail: ex => {
_showProgressBar = false;
Expand All @@ -168,6 +168,6 @@
);
}

Func<ProductDto,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";
Func<ProductResponse,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";
}
24 changes: 24 additions & 0 deletions Shifty.App/Pages/ProductManagement.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@page "/Products"
@using Components
@inject NavigationManager NavManager

@if (_user is not null && _user.IsInRole("Board"))
{
<ProductManager/>
}

@code {
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
private System.Security.Claims.ClaimsPrincipal _user;

protected override async Task OnInitializedAsync()
{
var authState = await AuthTask;
_user = authState.User;

if (_user is null || !_user.IsInRole("Board"))
{
NavManager.NavigateTo("/");
}
}
}
Loading
Loading