diff --git a/src/Client.Infrastructure/ApiClient/FSHApi.cs b/src/Client.Infrastructure/ApiClient/FSHApi.cs index a74762bd..c9fb7102 100644 --- a/src/Client.Infrastructure/ApiClient/FSHApi.cs +++ b/src/Client.Infrastructure/ApiClient/FSHApi.cs @@ -1998,6 +1998,32 @@ public partial interface IUsersClient : IApiService /// A server side error occurred. System.Threading.Tasks.Task GetByIdAsync(string? id, System.Threading.CancellationToken cancellationToken); + /// + /// Update profile details of an user. + /// + /// A server side error occurred. + System.Threading.Tasks.Task UpdateUserAsync(string id, UpdateUserRequest request); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Update profile details of an user. + /// + /// A server side error occurred. + System.Threading.Tasks.Task UpdateUserAsync(string id, UpdateUserRequest request, System.Threading.CancellationToken cancellationToken); + + /// + /// Delete a user. + /// + /// A server side error occurred. + System.Threading.Tasks.Task DeleteAsync(string id); + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Delete a user. + /// + /// A server side error occurred. + System.Threading.Tasks.Task DeleteAsync(string id, System.Threading.CancellationToken cancellationToken); + /// /// Get a user's roles. /// @@ -2105,7 +2131,6 @@ public partial interface IUsersClient : IApiService /// /// A server side error occurred. System.Threading.Tasks.Task ResetPasswordAsync(ResetPasswordRequest request, System.Threading.CancellationToken cancellationToken); - } [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] @@ -2418,6 +2443,192 @@ public virtual async System.Threading.Tasks.Task GetByIdAsync(st } } + /// + /// Update profile details of an user. + /// + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateUserAsync(string id, UpdateUserRequest request) + { + return UpdateUserAsync(id, request, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Update profile details of an user. + /// + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateUserAsync(string id, UpdateUserRequest request, System.Threading.CancellationToken cancellationToken) + { + if (request == null) + throw new System.ArgumentNullException("request"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("api/users/{id}"); + urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("PUT"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Delete a user. + /// + /// A server side error occurred. + public virtual System.Threading.Tasks.Task DeleteAsync(string id) + { + return DeleteAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Delete a user. + /// + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task DeleteAsync(string id, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append("api/users/{id}"); + urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("A server side error occurred.", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get a user's roles. /// @@ -6353,10 +6564,17 @@ public partial class ResetPasswordRequest [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "13.15.10.0 (NJsonSchema v10.6.10.0 (Newtonsoft.Json v13.0.0.0))")] public partial class UpdateUserRequest { - [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Always)] - [System.ComponentModel.DataAnnotations.Required] + //[Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + + //[System.ComponentModel.DataAnnotations.Required] public string Id { get; set; } = default!; + [Newtonsoft.Json.JsonProperty("userName", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + [System.ComponentModel.DataAnnotations.StringLength(75, MinimumLength = 1)] + public string UserName { get; set; } = default!; + [Newtonsoft.Json.JsonProperty("firstName", Required = Newtonsoft.Json.Required.Always)] [System.ComponentModel.DataAnnotations.Required] [System.ComponentModel.DataAnnotations.StringLength(75, MinimumLength = 1)] diff --git a/src/Client.Infrastructure/Client.Infrastructure.csproj b/src/Client.Infrastructure/Client.Infrastructure.csproj index e1301f99..9a8aac8a 100644 --- a/src/Client.Infrastructure/Client.Infrastructure.csproj +++ b/src/Client.Infrastructure/Client.Infrastructure.csproj @@ -20,7 +20,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,9 +32,7 @@ - + \ No newline at end of file diff --git a/src/Client/Components/EntityTable/EntityTable.razor b/src/Client/Components/EntityTable/EntityTable.razor index 68221bd9..ab093265 100644 --- a/src/Client/Components/EntityTable/EntityTable.razor +++ b/src/Client/Components/EntityTable/EntityTable.razor @@ -17,18 +17,18 @@ @if (_canSearch && (Context.AdvancedSearchEnabled || AdvancedSearchContent is not null)) { + Style="padding:10px!important; margin-bottom:10px!important;border-radius: var(--mud-default-borderradius) !important;" + Class="mud-elevation-25" Text="@L["Advanced Search"]"> + Placeholder="@($"{L["Search for"]} {Context.EntityNamePlural}")" Adornment="Adornment.Start" + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" + Style="flex:none!important;margin:0px!important" TextChanged="OnSearchStringChanged"> @if (Context.AdvancedSearchEnabled) {
+ CheckedChanged="Context.AllColumnsCheckChanged"> @foreach (var field in Context.Fields) { @@ -41,33 +41,33 @@ } + Loading="@Loading" LoadingProgressColor="@Color.Secondary" ChildRowContent="@ChildRowContent">
@if (_canCreate) { @L["Create"] + OnClick="(() => InvokeModal())" StartIcon="@Icons.Material.Filled.Add" + IconColor="Color.Surface">@L["Create"] } @if (_canExport) { @L["Export"] + OnClick="(() => ExportAsync())" StartIcon="@Icons.Material.Filled.ImportExport" + Style="margin-left: 5px;" IconColor="Color.Surface">@L["Export"] } @L["Reload"] + StartIcon="@Icons.Material.Filled.Refresh" IconColor="Color.Surface" Color="Color.Secondary" + Style="margin-left: 5px;">@L["Reload"]
@if (_canSearch && !_advancedSearchExpanded) { + Placeholder="@($"{L["Search for"]} {Context.EntityNamePlural}")" Adornment="Adornment.End" + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0 mb-3" + TextChanged="OnSearchStringChanged"> }
@@ -110,37 +110,33 @@ } } - - @if (ActionsContent is not null) + + @if (HasActions) { - @ActionsContent(context) - } - else if (HasActions) - { - - @if (CanUpdateEntity(context)) - { - @L["Edit"] - } - @if (CanDeleteEntity(context)) - { - @L["Delete"] - } - @if (ExtraActions is not null) - { - @ExtraActions(context) - } - - } - else - { - - @L["No Allowed Actions"] - + if (CanUpdateEntity(context)) + { + + + + } + @if (CanDeleteEntity(context)) + { + + + + } + @if (ActionsContent is not null) + { + @ActionsContent(context) + } + @if (ExtraActions is not null) + { + + + @ExtraActions(context) + + + } } diff --git a/src/Client/Pages/Identity/Users/Users.razor b/src/Client/Pages/Identity/Users/Users.razor index f2d93ec9..25fcdc0c 100644 --- a/src/Client/Pages/Identity/Users/Users.razor +++ b/src/Client/Pages/Identity/Users/Users.razor @@ -5,16 +5,22 @@ - + @L["View Profile"] @if (_canViewRoles) { - @L["Manage Roles"] + @L["View Role"] } + @if (!Context.AddEditModal.IsCreate) + { + + + + } @@ -26,21 +32,24 @@ + Label="@L["Email"]" /> - + - - - - - - + Label="@L["Phone Number"]" /> + @if (Context.AddEditModal.IsCreate) + { + + + + + + + } \ No newline at end of file diff --git a/src/Client/Pages/Identity/Users/Users.razor.cs b/src/Client/Pages/Identity/Users/Users.razor.cs index c0417399..67fc30ef 100644 --- a/src/Client/Pages/Identity/Users/Users.razor.cs +++ b/src/Client/Pages/Identity/Users/Users.razor.cs @@ -2,6 +2,7 @@ using FSH.BlazorWebAssembly.Client.Infrastructure.ApiClient; using FSH.BlazorWebAssembly.Client.Infrastructure.Auth; using FSH.WebApi.Shared.Authorization; +using Mapster; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; @@ -13,25 +14,27 @@ public partial class Users { [CascadingParameter] protected Task AuthState { get; set; } = default!; + [Inject] protected IAuthorizationService AuthService { get; set; } = default!; [Inject] protected IUsersClient UsersClient { get; set; } = default!; - protected EntityClientTableContext Context { get; set; } = default!; + protected EntityClientTableContext Context { get; set; } = default!; private bool _canExportUsers; private bool _canViewRoles; // Fields for editform protected string Password { get; set; } = string.Empty; + protected string ConfirmPassword { get; set; } = string.Empty; + private readonly bool _passwordTogleFormVisibility; private bool _passwordVisibility; private InputType _passwordInput = InputType.Password; private string _passwordInputIcon = Icons.Material.Filled.VisibilityOff; - protected override async Task OnInitializedAsync() { var user = (await AuthState).User; @@ -43,8 +46,8 @@ protected override async Task OnInitializedAsync() entityNamePlural: L["Users"], entityResource: FSHResource.Users, searchAction: FSHAction.View, - updateAction: string.Empty, - deleteAction: string.Empty, + updateAction: FSHAction.Update, + deleteAction: FSHAction.Delete, fields: new() { new(user => user.FirstName, L["First Name"]), @@ -64,7 +67,9 @@ protected override async Task OnInitializedAsync() || user.Email?.Contains(searchString, StringComparison.OrdinalIgnoreCase) == true || user.PhoneNumber?.Contains(searchString, StringComparison.OrdinalIgnoreCase) == true || user.UserName?.Contains(searchString, StringComparison.OrdinalIgnoreCase) == true, - createFunc: user => UsersClient.CreateAsync(user), + createFunc: async user => await UsersClient.CreateAsync(user.Adapt()), + updateFunc: async (id, user) => await UsersClient.UpdateUserAsync(id.ToString(), user), + deleteFunc: async id => await UsersClient.DeleteAsync(id.ToString()), hasExtraActionsFunc: () => true, exportAction: string.Empty); } @@ -92,4 +97,10 @@ private void TogglePasswordVisibility() Context.AddEditModal.ForceRender(); } + + public class UserViewModel : UpdateUserRequest + { + public string? Password { get; set; } + public string? ConfirmPassword { get; set; } + } } \ No newline at end of file diff --git a/src/Client/Shared/NavMenu.razor b/src/Client/Shared/NavMenu.razor index 9ccb1ba2..f4d71a36 100644 --- a/src/Client/Shared/NavMenu.razor +++ b/src/Client/Shared/NavMenu.razor @@ -45,7 +45,7 @@ } @if (_canViewRoles) { - @L["Roles"] + @L["Roles"] } @if(_canViewTenants) { diff --git a/src/Client/wwwroot/css/fsh.css b/src/Client/wwwroot/css/fsh.css index a44b6344..7e03f9ff 100644 --- a/src/Client/wwwroot/css/fsh.css +++ b/src/Client/wwwroot/css/fsh.css @@ -69,4 +69,10 @@ } .fsh-nav-child { padding-left: 10px !important; -} \ No newline at end of file +} +.fsh-td-action { + white-space: nowrap; +} + .fsh-td-action .mud-menu { + display: block; + } \ No newline at end of file