MRB webassembly
This commit is contained in:
64
MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor
Normal file
64
MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor
Normal file
@ -0,0 +1,64 @@
|
||||
@page "/redirect"
|
||||
@attribute [AllowAnonymous]
|
||||
@inject MesaFabApprovalAuthStateProvider authStateProvider
|
||||
@inject IAuthenticationService authService
|
||||
@inject IUserService userService
|
||||
@inject ISnackbar snackbar
|
||||
@inject MesaFabApprovalAuthStateProvider authStateProvider
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
@code {
|
||||
private string? _jwt;
|
||||
private string? _refreshToken;
|
||||
private string? _redirectPath;
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
try {
|
||||
Uri uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
|
||||
|
||||
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("jwt", out var jwt)) {
|
||||
_jwt = System.Net.WebUtility.UrlDecode(jwt);
|
||||
}
|
||||
|
||||
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("refreshToken", out var refreshToken)) {
|
||||
_refreshToken = System.Net.WebUtility.UrlDecode(refreshToken);
|
||||
}
|
||||
|
||||
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("redirectPath", out var redirectPath)) {
|
||||
_redirectPath = System.Net.WebUtility.UrlDecode(redirectPath);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_jwt) && !string.IsNullOrWhiteSpace(_refreshToken)) {
|
||||
await authService.SetTokens(_jwt, _refreshToken);
|
||||
|
||||
ClaimsPrincipal principal = authService.GetClaimsPrincipalFromJwt(_jwt);
|
||||
|
||||
string loginId = userService.GetLoginIdFromClaimsPrincipal(principal);
|
||||
|
||||
await authService.SetLoginId(loginId);
|
||||
await authService.SetTokens(_jwt, _refreshToken);
|
||||
|
||||
User? user = await userService.GetUserByLoginId(loginId);
|
||||
await authService.SetCurrentUser(user);
|
||||
|
||||
await authStateProvider.StateHasChanged(principal);
|
||||
}
|
||||
|
||||
if (authStateProvider.CurrentUser is not null && !string.IsNullOrWhiteSpace(_redirectPath)) {
|
||||
navigationManager.NavigateTo(_redirectPath);
|
||||
} else {
|
||||
await authStateProvider.Logout();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_redirectPath)) {
|
||||
navigationManager.NavigateTo($"login/{_redirectPath}");
|
||||
} else {
|
||||
navigationManager.NavigateTo("login");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add($"Redirect failed, because {ex.Message}", Severity.Error);
|
||||
navigationManager.NavigateTo("login");
|
||||
}
|
||||
}
|
||||
}
|
69
MesaFabApproval.Client/Pages/Components/Comments.razor
Normal file
69
MesaFabApproval.Client/Pages/Components/Comments.razor
Normal file
@ -0,0 +1,69 @@
|
||||
@inject ISnackbar snackbar
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudPaper Class="p-2">
|
||||
<MudForm @bind-Errors="@errors">
|
||||
<MudTextField T="string"
|
||||
Label="Comments"
|
||||
Required="true"
|
||||
RequiredError="You must provide a comment"
|
||||
@bind-Value="@comments"
|
||||
@bind-Text="@comments"
|
||||
Immediate="true"
|
||||
AutoGrow
|
||||
AutoFocus/>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
Class="m1-auto"
|
||||
OnClick=SubmitComments>
|
||||
@if (processing) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
} else {
|
||||
<MudText>Ok</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Class="grey text-black m1-auto"
|
||||
OnClick=Cancel>
|
||||
Cancel
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string comments { get; set; } = "";
|
||||
|
||||
private string[] errors = { };
|
||||
|
||||
private bool processing = false;
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
comments = string.Empty;
|
||||
}
|
||||
|
||||
private void SubmitComments() {
|
||||
processing = true;
|
||||
try {
|
||||
if (string.IsNullOrWhiteSpace(comments) || comments.Length < 5)
|
||||
throw new Exception("Comments must be at least five characters long");
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(comments));
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
|
||||
private void Cancel() {
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
166
MesaFabApproval.Client/Pages/Components/MRBActionForm.razor
Normal file
166
MesaFabApproval.Client/Pages/Components/MRBActionForm.razor
Normal file
@ -0,0 +1,166 @@
|
||||
@inject IMRBService mrbService
|
||||
@inject ISnackbar snackbar
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudPaper Class="p-2">
|
||||
<MudForm @bind-Errors="@errors">
|
||||
<MudSelect T="string"
|
||||
Label="Action"
|
||||
Required="true"
|
||||
RequiredError="You must select an action!"
|
||||
@bind-Value="@mrbAction.Action"
|
||||
Text="@mrbAction.Action">
|
||||
<MudSelectItem Value="@("Block")" />
|
||||
<MudSelectItem Value="@("Convert")" />
|
||||
<MudSelectItem Value="@("Other")" />
|
||||
<MudSelectItem Value="@("Recall")" />
|
||||
<MudSelectItem Value="@("Scrap")" />
|
||||
<MudSelectItem Value="@("Unblock")" />
|
||||
<MudSelectItem Value="@("Waiver")" />
|
||||
</MudSelect>
|
||||
<MudTextField T="string"
|
||||
Label="Customer / Vendor"
|
||||
Required="true"
|
||||
RequiredError="Customer / Vendor required!"
|
||||
@bind-Value="@mrbAction.Customer"
|
||||
Text="@mrbAction.Customer" />
|
||||
<MudTextField T="int"
|
||||
Label="Qty"
|
||||
Required="true"
|
||||
RequiredError="You must supply a quantity!"
|
||||
@bind-Value=mrbAction.Quantity
|
||||
Text="@mrbAction.Quantity.ToString()" />
|
||||
<MudTextField T="string"
|
||||
Label="Part Number"
|
||||
Required="true"
|
||||
RequiredError="Part number required!"
|
||||
@bind-Value="@mrbAction.PartNumber"
|
||||
Text="@mrbAction.PartNumber" />
|
||||
<MudTextField T="string"
|
||||
Label="Batch Number / Lot Number"
|
||||
Required="true"
|
||||
RequiredError="Batch number / Lot number required!"
|
||||
@bind-Value="@mrbAction.LotNumber"
|
||||
Text="@mrbAction.LotNumber" />
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
Class="m1-auto"
|
||||
OnClick=SaveMRBAction>
|
||||
@if (processingSave) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
} else {
|
||||
<MudText>Save</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
@if (mrbAction is not null && mrbAction.ActionID > 0) {
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
Disabled="@(mrbAction is null || (mrbAction is not null && mrbAction.ActionID <= 0))"
|
||||
Class="m1-auto"
|
||||
OnClick=DeleteMRBAction>
|
||||
@if (processingDelete) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
} else {
|
||||
<MudText>Delete</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
}
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Class="grey text-black m1-auto"
|
||||
OnClick=Cancel>
|
||||
Cancel
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public MRBAction mrbAction { get; set; }
|
||||
|
||||
private bool isVisible { get; set; }
|
||||
private string[] errors = { };
|
||||
private bool processingSave = false;
|
||||
private bool processingDelete = false;
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
isVisible = true;
|
||||
|
||||
if (mrbAction is null) {
|
||||
mrbAction = new() {
|
||||
Action = "",
|
||||
Customer = "",
|
||||
Quantity = 0,
|
||||
PartNumber = "",
|
||||
LotNumber = "",
|
||||
MRBNumber = 0
|
||||
};
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private bool FormIsValid() {
|
||||
bool actionIsValid = mrbAction.Action.Equals("Block") || mrbAction.Action.Equals("Convert") ||
|
||||
mrbAction.Action.Equals("Other") || mrbAction.Action.Equals("Recall") || mrbAction.Action.Equals("Scrap") ||
|
||||
mrbAction.Action.Equals("Unblock") || mrbAction.Action.Equals("Waiver");
|
||||
return actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Customer) &&
|
||||
!string.IsNullOrWhiteSpace(mrbAction.PartNumber) &&
|
||||
!string.IsNullOrWhiteSpace(mrbAction.LotNumber);
|
||||
}
|
||||
|
||||
private async void SaveMRBAction() {
|
||||
processingSave = true;
|
||||
try {
|
||||
if (!FormIsValid()) throw new Exception("You must complete the form before saving!");
|
||||
|
||||
if (mrbAction.MRBNumber > 0) {
|
||||
if (mrbAction.ActionID <= 0) {
|
||||
await mrbService.CreateMRBAction(mrbAction);
|
||||
snackbar.Add("MRB action created", Severity.Success);
|
||||
} else {
|
||||
await mrbService.UpdateMRBAction(mrbAction);
|
||||
snackbar.Add("MRB action updated", Severity.Success);
|
||||
}
|
||||
} else {
|
||||
snackbar.Add("MRB action saved", Severity.Success);
|
||||
}
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(mrbAction));
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
processingSave = false;
|
||||
}
|
||||
|
||||
private async void DeleteMRBAction() {
|
||||
processingDelete = true;
|
||||
try {
|
||||
if (mrbAction is null) throw new Exception("MRB action cannot be null!");
|
||||
if (mrbAction.ActionID <= 0)
|
||||
throw new Exception("You cannot delete an action before creating it!");
|
||||
if (mrbAction.MRBNumber <= 0)
|
||||
throw new Exception("Invalid MRB number!");
|
||||
|
||||
await mrbService.DeleteMRBAction(mrbAction);
|
||||
snackbar.Add("MRB action successfully deleted", Severity.Success);
|
||||
|
||||
MudDialog.Close(DialogResult.Ok<MRBAction>(null));
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
processingDelete = false;
|
||||
}
|
||||
|
||||
private void Cancel() {
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
@attribute [AllowAnonymous]
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized() {
|
||||
Navigation.NavigateTo("login");
|
||||
}
|
||||
}
|
77
MesaFabApproval.Client/Pages/Dashboard.razor
Normal file
77
MesaFabApproval.Client/Pages/Dashboard.razor
Normal file
@ -0,0 +1,77 @@
|
||||
@page "/"
|
||||
@page "/Dashboard"
|
||||
@inject MesaFabApprovalAuthStateProvider stateProvider
|
||||
@inject IApprovalService approvalService
|
||||
@inject IMemoryCache cache
|
||||
@inject NavigationManager navigationManager
|
||||
@inject ISnackbar snackbar
|
||||
|
||||
<PageTitle>Dashboard</PageTitle>
|
||||
|
||||
<MudPaper Class="p-2 m-2" MinWidth="100%">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center">Dashboard</MudText>
|
||||
</MudPaper>
|
||||
|
||||
@if (stateProvider.CurrentUser is not null && approvalList is not null) {
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-column justify-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">My Active Approvals</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
|
||||
<MudTable Items="@approvalList"
|
||||
Class="m-2"
|
||||
Striped
|
||||
SortLabel="Sort by">
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<Approval,object>(x=>x.IssueID)">
|
||||
Issue ID
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<Approval,object>(x=>x.SubRoleCategoryItem)">
|
||||
Role
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<Approval,object>(x=>x.AssignedDate)">
|
||||
Assigned Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<Approval,object>(x=>x.Step)">
|
||||
Step
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Issue ID">
|
||||
<MudLink OnClick="@(() => GoTo($"/mrb/{context.IssueID}"))">@context.IssueID</MudLink>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Role">@context.SubRoleCategoryItem</MudTd>
|
||||
<MudTd DataLabel="Assigned Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.AssignedDate)</MudTd>
|
||||
<MudTd DataLabel="Step">@context.Step</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
@code {
|
||||
private IEnumerable<Approval> approvalList = new List<Approval>();
|
||||
|
||||
protected async override Task OnParametersSetAsync() {
|
||||
try {
|
||||
if (stateProvider.CurrentUser is not null)
|
||||
approvalList = (await approvalService.GetApprovalsForUserId(stateProvider.CurrentUser.UserID, true)).ToList();
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add($"Unable to fetch your outstanding approvals, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void GoTo(string page) {
|
||||
cache.Set("redirectUrl", page);
|
||||
navigationManager.NavigateTo(page);
|
||||
}
|
||||
}
|
84
MesaFabApproval.Client/Pages/Login.razor
Normal file
84
MesaFabApproval.Client/Pages/Login.razor
Normal file
@ -0,0 +1,84 @@
|
||||
@page "/login"
|
||||
@page "/login/{redirectUrl}"
|
||||
@page "/login/{redirectUrl}/{redirectUrlSub}"
|
||||
@attribute [AllowAnonymous]
|
||||
@inject MesaFabApprovalAuthStateProvider authStateProvider
|
||||
@inject NavigationManager navManager
|
||||
@inject ISnackbar snackbar
|
||||
|
||||
<MudPaper Class="p-2 m-2">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center">Login</MudText>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="p-2 m-2">
|
||||
<MudForm @bind-IsValid="@success" @bind-Errors="@errors">
|
||||
<MudTextField T="string"
|
||||
Label="Windows Username"
|
||||
Required="true"
|
||||
RequiredError="Username is required!"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value=username
|
||||
Class="m-1"
|
||||
Immediate="true"
|
||||
AutoFocus
|
||||
OnKeyDown=SubmitIfEnter />
|
||||
<MudTextField T="string"
|
||||
Label="Windows Password"
|
||||
Required="true"
|
||||
RequiredError="Password is required!"
|
||||
Variant="Variant.Outlined"
|
||||
@bind-Value=password
|
||||
InputType="InputType.Password"
|
||||
Class="m-1"
|
||||
Immediate="true"
|
||||
OnKeyDown=SubmitIfEnter />
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
Disabled="@(!success)"
|
||||
Class="m-1"
|
||||
OnClick=SubmitLogin >
|
||||
@if (processing) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
} else {
|
||||
<MudText>Log In</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? redirectUrl { get; set; }
|
||||
[Parameter]
|
||||
public string? redirectUrlSub { get; set; }
|
||||
private bool success;
|
||||
private bool processing = false;
|
||||
private string[] errors = { };
|
||||
private string? username;
|
||||
private string? password;
|
||||
|
||||
private async Task SubmitLogin() {
|
||||
processing = true;
|
||||
if (string.IsNullOrWhiteSpace(username)) snackbar.Add("Username is required!", Severity.Error);
|
||||
else if (string.IsNullOrWhiteSpace(password)) snackbar.Add("Password is required!", Severity.Error);
|
||||
else {
|
||||
await authStateProvider.LoginAsync(username, password);
|
||||
if (!string.IsNullOrWhiteSpace(redirectUrl) && !string.IsNullOrWhiteSpace(redirectUrlSub)) {
|
||||
navManager.NavigateTo($"{redirectUrl}/{redirectUrlSub}");
|
||||
} else if (!string.IsNullOrWhiteSpace(redirectUrl)) {
|
||||
navManager.NavigateTo(redirectUrl);
|
||||
} else {
|
||||
navManager.NavigateTo("dashboard");
|
||||
}
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
|
||||
private async Task SubmitIfEnter(KeyboardEventArgs e) {
|
||||
if (e.Key == "Enter" && success) {
|
||||
SubmitLogin();
|
||||
}
|
||||
}
|
||||
}
|
113
MesaFabApproval.Client/Pages/MRBAll.razor
Normal file
113
MesaFabApproval.Client/Pages/MRBAll.razor
Normal file
@ -0,0 +1,113 @@
|
||||
@page "/mrb/all"
|
||||
@using System.Globalization
|
||||
@inject IMRBService mrbService
|
||||
@inject ISnackbar snackbar
|
||||
@inject IMemoryCache cache
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<PageTitle>MRB</PageTitle>
|
||||
|
||||
<MudPaper Class="p-2 m-2">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center">MRB List</MudText>
|
||||
</MudPaper>
|
||||
|
||||
@if (allMrbs is not null && allMrbs.Count() > 0) {
|
||||
<MudTable Items="@allMrbs"
|
||||
Class="m-2"
|
||||
Striped="true"
|
||||
Filter="new Func<MRB,bool>(FilterFuncForTable)"
|
||||
SortLabel="Sort By"
|
||||
Hover="true">
|
||||
<ToolBarContent>
|
||||
<MudSpacer />
|
||||
<MudTextField @bind-Value="searchString"
|
||||
Placeholder="Search"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"
|
||||
Class="mt-0" />
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<MRB,object>(x=>x.MRBNumber)">
|
||||
MRB#
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.Title)">
|
||||
Title
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.OriginatorName)">
|
||||
Originator
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.SubmittedDate)">
|
||||
Submitted Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.CloseDate)">
|
||||
Closed Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="MRB#">
|
||||
<MudLink OnClick="@(() => GoTo($"/mrb/{context.MRBNumber}"))">@context.MRBNumber</MudLink>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Title">@context.Title</MudTd>
|
||||
<MudTd DataLabel="Originator">@context.OriginatorName</MudTd>
|
||||
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.SubmittedDate)</MudTd>
|
||||
<MudTd DataLabel="Closed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CloseDate)</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
|
||||
<MudOverlay @bind-Visible=inProcess DarkBackground="true" AutoClose="false">
|
||||
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
|
||||
</MudOverlay>
|
||||
|
||||
@code {
|
||||
private bool inProcess = false;
|
||||
private string searchString = "";
|
||||
private IEnumerable<MRB> allMrbs = new List<MRB>();
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
inProcess = true;
|
||||
try {
|
||||
if (mrbService is null) {
|
||||
throw new Exception("MRB service not injected!");
|
||||
} else {
|
||||
allMrbs = await mrbService.GetAllMRBs();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
inProcess = false;
|
||||
}
|
||||
|
||||
private bool FilterFuncForTable(MRB mrb) => FilterFunc(mrb, searchString);
|
||||
|
||||
private bool FilterFunc(MRB mrb, string searchString) {
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
return true;
|
||||
if (mrb.Title.ToLower().Contains(searchString.ToLower()))
|
||||
return true;
|
||||
if (mrb.OriginatorName.ToLower().Contains(searchString.ToLower()))
|
||||
return true;
|
||||
if (mrb.MRBNumber.ToString().Contains(searchString))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void GoTo(string page) {
|
||||
cache.Set("redirectUrl", page);
|
||||
navigationManager.NavigateTo(page);
|
||||
}
|
||||
}
|
996
MesaFabApproval.Client/Pages/MRBSingle.razor
Normal file
996
MesaFabApproval.Client/Pages/MRBSingle.razor
Normal file
@ -0,0 +1,996 @@
|
||||
@page "/mrb/{mrbNumber}"
|
||||
@page "/mrb/new"
|
||||
@using System.Text
|
||||
@inject IMRBService mrbService
|
||||
@inject ISnackbar snackbar
|
||||
@inject IDialogService dialogService
|
||||
@inject NavigationManager navigationManager
|
||||
@inject MesaFabApprovalAuthStateProvider authStateProvider
|
||||
@inject IUserService userService
|
||||
@inject IMemoryCache cache
|
||||
@inject IConfiguration config
|
||||
@inject IApprovalService approvalService
|
||||
|
||||
@if (mrbNumber is not null) {
|
||||
<PageTitle>MRB @mrbNumber</PageTitle>
|
||||
|
||||
<MudPaper Class="p-2 m-2 d-flex flex-row justify-content-between">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Dark"
|
||||
OnClick="@ReturnToAllMrbs"
|
||||
Size="Size.Large" />
|
||||
<MudText Typo="Typo.h3" Align="Align.Center">MRB @mrbNumber</MudText>
|
||||
<MudPaper Height="100%" Width="0.1%" Square="true" />
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
@if (mrb is not null) {
|
||||
<MudTimeline Class="mt-2 pt-2" TimelineOrientation="TimelineOrientation.Horizontal"
|
||||
TimelinePosition="TimelinePosition.Bottom">
|
||||
@for (int i = 0; i < MRB.Stages.Length; i++) {
|
||||
Color color;
|
||||
if (mrb.StageNo > i || mrb.StageNo == (MRB.Stages.Length - 1)) {
|
||||
color = Color.Success;
|
||||
} else if (mrb.StageNo == i) {
|
||||
color = Color.Info;
|
||||
} else {
|
||||
color = Color.Dark;
|
||||
}
|
||||
|
||||
string stageName = MRB.Stages[i];
|
||||
|
||||
<MudTimelineItem Color="@color" Variant="Variant.Filled">
|
||||
<MudText Align="Align.Center" Color="@color">@stageName</MudText>
|
||||
</MudTimelineItem>
|
||||
}
|
||||
</MudTimeline>
|
||||
|
||||
bool mrbIsSubmitted = mrb.SubmittedDate > DateTime.MinValue;
|
||||
bool mrbReadyToSubmit = mrbIsReadyToSubmit();
|
||||
bool userIsApprover = currentUserIsApprover();
|
||||
|
||||
if (!mrbIsSubmitted || (!mrbIsSubmitted && mrbReadyToSubmit) || (mrbIsSubmitted && userIsApprover))
|
||||
{
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-center"
|
||||
Elevation="10">
|
||||
@if (!mrbIsSubmitted)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick=SaveMRB>
|
||||
@if (saveInProcess)
|
||||
{
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText>Save</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
}
|
||||
@if (!mrbIsSubmitted && mrbReadyToSubmit)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick=SubmitMRBForApproval>
|
||||
@if (submitInProcess)
|
||||
{
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Processing</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText>Submit for Approval</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
}
|
||||
@if (mrbIsSubmitted && userIsApprover)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick=ApproveMRB>
|
||||
Approve
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick=DenyMRB>
|
||||
Deny
|
||||
</MudButton>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
|
||||
Elevation="10">
|
||||
<MudTextField @bind-Value=mrb.MRBNumber
|
||||
Text="@mrb.MRBNumber.ToString()"
|
||||
T="int"
|
||||
Disabled="true"
|
||||
Label="MRB#"
|
||||
Variant="Variant.Outlined" />
|
||||
<MudTextField @bind-Value=mrb.Title
|
||||
Text="@mrb.Title"
|
||||
Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
T="string"
|
||||
AutoGrow
|
||||
Variant="Variant.Outlined"
|
||||
Label="Title" />
|
||||
|
||||
<MudTextField Disabled="true"
|
||||
T="string"
|
||||
Value="@DateTimeUtilities.GetDateAsStringMinDefault(mrb.SubmittedDate)"
|
||||
Label="Submit Date"
|
||||
Variant="Variant.Outlined" />
|
||||
<MudTextField Disabled="true"
|
||||
Label="Approval Date"
|
||||
Variant="Variant.Outlined"
|
||||
Value="@DateTimeUtilities.GetDateAsStringMaxDefault(mrb.ApprovalDate)"
|
||||
T="string" />
|
||||
<MudTextField Disabled="true"
|
||||
Label="Closed Date"
|
||||
Variant="Variant.Outlined"
|
||||
Value="@DateTimeUtilities.GetDateAsStringMaxDefault(mrb.CloseDate)"
|
||||
T="string" />
|
||||
@if (mrb is not null && mrb.CancelDate < DateTime.MaxValue) {
|
||||
<MudTextField Disabled="true"
|
||||
Label="Canceled Date"
|
||||
Variant="Variant.Outlined"
|
||||
Value="@DateTimeUtilities.GetDateAsStringMaxDefault(mrb.CancelDate)"
|
||||
T="string" />
|
||||
}
|
||||
|
||||
<MudTextField Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.IssueDescription
|
||||
Text="@mrb.IssueDescription"
|
||||
AutoGrow
|
||||
Variant="Variant.Outlined"
|
||||
Label="Description" />
|
||||
<MudSelect T="string"
|
||||
Label="Originator"
|
||||
Variant="Variant.Outlined"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Disabled=@(mrb.SubmittedDate > DateTime.MinValue)
|
||||
@bind-Value=mrb.OriginatorName
|
||||
Text="@mrb.OriginatorName">
|
||||
@foreach (User user in allActiveUsers) {
|
||||
<MudSelectItem T="string" Value="@($"{user.FirstName} {user.LastName}")" />
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="string"
|
||||
Label="Department"
|
||||
Variant="Variant.Outlined"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Disabled=@(mrb.SubmittedDate > DateTime.MinValue)
|
||||
@bind-Value=mrb.Department
|
||||
Text="@mrb.Department">
|
||||
<MudSelectItem Value="@("Production")" />
|
||||
<MudSelectItem Value="@("Engineering")" />
|
||||
<MudSelectItem Value="@("Materials")" />
|
||||
<MudSelectItem Value="@("Facilities")" />
|
||||
<MudSelectItem Value="@("Maintenance")" />
|
||||
<MudSelectItem Value="@("Quality")" />
|
||||
</MudSelect>
|
||||
<MudSelect T="string"
|
||||
Label="Process"
|
||||
Variant="Variant.Outlined"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
Disabled=@(mrb.SubmittedDate > DateTime.MinValue)
|
||||
@bind-Value=mrb.Process
|
||||
Text="@mrb.Process">
|
||||
<MudSelectItem Value="@("Receiving")" />
|
||||
<MudSelectItem Value="@("Kitting")" />
|
||||
<MudSelectItem Value="@("Cleans")" />
|
||||
<MudSelectItem Value="@("Reactor")" />
|
||||
<MudSelectItem Value="@("Metrology")" />
|
||||
<MudSelectItem Value="@("Final QA")" />
|
||||
<MudSelectItem Value="@("Packaging")" />
|
||||
<MudSelectItem Value="@("Shipping")" />
|
||||
<MudSelectItem Value="@("N/A")" />
|
||||
</MudSelect>
|
||||
<MudTextField Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.NumberOfLotsAffected
|
||||
Text="@mrb.NumberOfLotsAffected.ToString()"
|
||||
Variant="Variant.Outlined"
|
||||
Label="Total Quantity" />
|
||||
<MudTextField Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.Val
|
||||
Text="@mrb.Val"
|
||||
Variant="Variant.Outlined"
|
||||
Label="Value" />
|
||||
<MudTextField Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.RMANo
|
||||
Text="@mrb.RMANo.ToString()"
|
||||
Variant="Variant.Outlined"
|
||||
Label="RMA#" />
|
||||
<MudPaper Outlined="true" Class="p-2">
|
||||
<MudCheckBox Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.CustomerImpacted
|
||||
Color="Color.Tertiary"
|
||||
Label="Customer Impacted?"
|
||||
LabelPosition="LabelPosition.Start" />
|
||||
</MudPaper>
|
||||
<MudTextField Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.PCRBNo
|
||||
Text="@mrb.PCRBNo.ToString()"
|
||||
Variant="Variant.Outlined"
|
||||
Label="PCRB#" />
|
||||
<MudPaper Outlined="true" Class="p-2">
|
||||
<MudCheckBox Disabled="@(mrb.SubmittedDate > DateTime.MinValue)"
|
||||
@bind-Value=mrb.SpecsImpacted
|
||||
Color="Color.Tertiary"
|
||||
Label="Specs Impacted?"
|
||||
LabelPosition="LabelPosition.Start" />
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
@if (mrb is not null && mrb.MRBNumber > 0) {
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-column justify-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">Supporting Documents</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
|
||||
@if (!(mrb.SubmittedDate > DateTime.MinValue)) {
|
||||
<MudFileUpload T="IBrowserFile" OnFilesChanged="AddAttachments" Class="centered-upload">
|
||||
<ButtonTemplate>
|
||||
<MudButton HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
style="margin: auto;"
|
||||
StartIcon="@Icons.Material.Filled.AttachFile"
|
||||
for="@context.Id">
|
||||
Upload Document
|
||||
</MudButton>
|
||||
</ButtonTemplate>
|
||||
</MudFileUpload>
|
||||
}
|
||||
|
||||
@if (mrbAttachments is not null) {
|
||||
<MudTable Items="@mrbAttachments"
|
||||
Class="m-2"
|
||||
Striped="true"
|
||||
Filter="new Func<MRBAttachment, bool>(FilterFuncForMRBAttachmentTable)"
|
||||
SortLabel="Sort By"
|
||||
Hover="true">
|
||||
<ToolBarContent>
|
||||
<MudSpacer />
|
||||
<MudTextField @bind-Value="attachmentSearchString"
|
||||
Placeholder="Search"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"
|
||||
Class="mt-0" />
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAttachment, object>(x=>x.FileName)">
|
||||
Name
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<MRBAttachment,object>(x=>x.UploadDate)">
|
||||
Upload Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">
|
||||
<a href="@(@$"{config["FabApprovalApiBaseUrl"]}/mrb/attachmentFile?path={context.Path}&fileName={context.FileName}")"
|
||||
download="@(context.FileName)"
|
||||
target="_top">
|
||||
@context.FileName
|
||||
</a>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Upload Date">@context.UploadDate.ToString("yyyy-MM-dd HH:mm")</MudTd>
|
||||
@if (mrb is not null && !(mrb.SubmittedDate > DateTime.MinValue)) {
|
||||
<MudTd>
|
||||
<MudButton Color="Color.Secondary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="@((e) => DeleteAttachment(context))">
|
||||
@if (deleteActionInProcess) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Deleting</MudText>
|
||||
} else {
|
||||
<MudText>Delete</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-column justify-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">Actions</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
|
||||
@if (!(mrb.SubmittedDate > DateTime.MinValue)) {
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
Class="object-center flex-grow-0"
|
||||
Style="max-width: 200px; margin: auto;"
|
||||
OnClick="@((e) => CreateNewAction())">
|
||||
Create New Action
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (mrbActions is not null) {
|
||||
<MudTable Items="@mrbActions"
|
||||
Class="m-2"
|
||||
Striped="true"
|
||||
Filter="new Func<MRBAction, bool>(FilterFuncForMRBActionTable)"
|
||||
SortLabel="Sort By"
|
||||
Hover="true">
|
||||
<ToolBarContent>
|
||||
<MudSpacer />
|
||||
<MudTextField @bind-Value="actionSearchString"
|
||||
Placeholder="Search"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium"
|
||||
Class="mt-0" />
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Ascending" SortBy="new Func<MRBAction,object>(x=>x.Action)">
|
||||
Action
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction,object>(x=>x.Customer)">
|
||||
Customer
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction,object>(x=>x.Quantity)">
|
||||
Qty
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction,object>(x=>x.PartNumber)">
|
||||
Part Number
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction,object>(x=>x.LotNumber)">
|
||||
Batch Number / Lot Number
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction, object>(x=>x.AssignedDate)">
|
||||
Assigned Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel SortBy="new Func<MRBAction, object>(x=>x.CompletedDate)">
|
||||
Completed Date
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Ascending" SortBy="new Func<MRBAction,object>(x=>x.CompletedByUser?.GetFullName() ??
|
||||
x.CompletedByUserID.ToString())">
|
||||
Completed By
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Action">@context.Action</MudTd>
|
||||
<MudTd DataLabel="Customer">@context.Customer</MudTd>
|
||||
<MudTd DataLabel="Qty">@context.Quantity</MudTd>
|
||||
<MudTd DataLabel="Part Number">@context.PartNumber</MudTd>
|
||||
<MudTd DataLabel="Batch Number / Lot Number">@context.LotNumber</MudTd>
|
||||
<MudTd DataLabel="Assigned Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.AssignedDate)</MudTd>
|
||||
<MudTd DataLabel="Completed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CompletedDate)</MudTd>
|
||||
<MudTd DataLabel="Completed By">@context.CompletedByUser?.GetFullName()</MudTd>
|
||||
@if (mrb is not null && !(mrb.SubmittedDate > DateTime.MinValue)) {
|
||||
<MudTd>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick="@((e) => EditAction(context))">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Color="Color.Secondary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="@((e) => DeleteAction(context))">
|
||||
@if (deleteActionInProcess) {
|
||||
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
|
||||
<MudText>Deleting</MudText>
|
||||
} else {
|
||||
<MudText>Delete</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
}
|
||||
@if (currentUser is not null && taskApprovals.Where(t => t.UserID == currentUser.UserID && t.TaskID == context.ActionID).ToList().Count() > 0 &&
|
||||
context.CompletedDate >= DateTime.MaxValue) {
|
||||
<MudTd>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Tertiary"
|
||||
OnClick="@((e) => CompleteAction(context))">
|
||||
Mark Complete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Outlined="true"
|
||||
Class="p-2 m-2 d-flex flex-column justify-center">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center">Approvals</MudText>
|
||||
|
||||
@if (nonTaskApprovals is not null) {
|
||||
<MudTable Items="@nonTaskApprovals"
|
||||
Class="m-2"
|
||||
Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Role</MudTh>
|
||||
<MudTh>Approver Name</MudTh>
|
||||
<MudTh>Status</MudTh>
|
||||
<MudTh>Disposition Date</MudTh>
|
||||
<MudTh>Comments</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Role">@context.SubRoleCategoryItem</MudTd>
|
||||
<MudTd DataLabel="Approver Name">@context.User?.GetFullName()</MudTd>
|
||||
<MudTd DataLabel="Status">@context.StatusMessage</MudTd>
|
||||
<MudTd DataLabel="Disposition Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CompletedDate)</MudTd>
|
||||
<MudTd DataLabel="Comments">@context.Comments</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudOverlay Visible=processing DarkBackground="true" AutoClose="false">
|
||||
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
|
||||
</MudOverlay>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? mrbNumber { get; set; }
|
||||
private int mrbNumberInt = 0;
|
||||
private MRB? mrb { get; set; }
|
||||
private User? currentUser = null;
|
||||
private int currentUserId = 0;
|
||||
private IEnumerable<User> allActiveUsers = new List<User>();
|
||||
private IEnumerable<MRBAction> mrbActions = new List<MRBAction>();
|
||||
private IEnumerable<MRBAttachment> mrbAttachments = new List<MRBAttachment>();
|
||||
private IEnumerable<Approval> mrbApprovals = new List<Approval>();
|
||||
private IEnumerable<Approval> nonTaskApprovals = new List<Approval>();
|
||||
private IEnumerable<Approval> taskApprovals = new List<Approval>();
|
||||
private bool processing = false;
|
||||
private bool saveInProcess = false;
|
||||
private bool submitInProcess = false;
|
||||
private bool approvalInProcess = false;
|
||||
private bool taskApprovalInProcess = false;
|
||||
private bool denialInProcess = false;
|
||||
private bool deleteActionInProcess = false;
|
||||
private bool deleteAttachmentInProcess = false;
|
||||
private string actionSearchString = "";
|
||||
private string attachmentSearchString = "";
|
||||
private string currentUrl = "";
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
processing = true;
|
||||
try {
|
||||
if (allActiveUsers is null) allActiveUsers = await userService.GetAllActiveUsers();
|
||||
if (authStateProvider is not null) currentUser = authStateProvider.CurrentUser;
|
||||
if (navigationManager is not null) currentUrl = navigationManager.Uri;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mrbNumber) && Int32.TryParse(mrbNumber, out mrbNumberInt)) {
|
||||
mrb = await mrbService.GetMRBById(mrbNumberInt);
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, false);
|
||||
mrbAttachments = await mrbService.GetAllAttachmentsForMRB(mrbNumberInt, false);
|
||||
|
||||
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrbNumberInt, false);
|
||||
|
||||
nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList();
|
||||
taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList();
|
||||
} else {
|
||||
mrbNumberInt = 0;
|
||||
mrbNumber = "";
|
||||
mrb = new() { Status = "Draft", StageNo = 0 };
|
||||
mrbActions = new List<MRBAction>();
|
||||
mrbAttachments = new List<MRBAttachment>();
|
||||
mrbApprovals = new List<Approval>();
|
||||
nonTaskApprovals = new List<Approval>();
|
||||
taskApprovals = new List<Approval>();
|
||||
if (authStateProvider is not null && authStateProvider.CurrentUser is not null) {
|
||||
mrb.OriginatorID = authStateProvider.CurrentUser.UserID;
|
||||
mrb.OriginatorName = $"{authStateProvider.CurrentUser.FirstName} {authStateProvider.CurrentUser.LastName}";
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
|
||||
private void ReturnToAllMrbs() {
|
||||
cache.Set("redirectUrl", $"mrb/all");
|
||||
navigationManager.NavigateTo("mrb/all");
|
||||
}
|
||||
|
||||
private async void SaveMRB() {
|
||||
try {
|
||||
saveInProcess = true;
|
||||
MRB initialMrb = new MRB() {
|
||||
MRBNumber = mrb.MRBNumber,
|
||||
Status = mrb.Status,
|
||||
StageNo = 0
|
||||
};
|
||||
if (mrb is not null) {
|
||||
User? originator = allActiveUsers.Where(u => $"{u.FirstName} {u.LastName}".Equals(mrb.OriginatorName)).FirstOrDefault();
|
||||
if (originator is not null) mrb.OriginatorID = originator.UserID;
|
||||
|
||||
if (mrb.MRBNumber <= 0) {
|
||||
await mrbService.CreateNewMRB(mrb);
|
||||
mrb = await mrbService.GetMRBByTitle(mrb.Title, true);
|
||||
cache.Set("redirectUrl", $"mrb/{mrb.MRBNumber}");
|
||||
} else {
|
||||
await mrbService.UpdateMRB(mrb);
|
||||
}
|
||||
|
||||
mrbNumber = mrb.MRBNumber.ToString();
|
||||
mrbNumberInt = mrb.MRBNumber;
|
||||
|
||||
foreach (MRBAction action in mrbActions) {
|
||||
if (action is not null) {
|
||||
action.MRBNumber = mrbNumberInt;
|
||||
if (action.ActionID > 0) {
|
||||
await mrbService.UpdateMRBAction(action);
|
||||
} else {
|
||||
await mrbService.CreateMRBAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true);
|
||||
}
|
||||
saveInProcess = false;
|
||||
StateHasChanged();
|
||||
snackbar.Add($"MRB {mrb.MRBNumber} successfully saved", Severity.Success);
|
||||
|
||||
if (initialMrb.MRBNumber <= 0)
|
||||
navigationManager.NavigateTo($"mrb/{mrb.MRBNumber}");
|
||||
} catch (Exception ex) {
|
||||
saveInProcess = false;
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
saveInProcess = false;
|
||||
}
|
||||
|
||||
private async void SubmitMRBForApproval() {
|
||||
submitInProcess = true;
|
||||
try {
|
||||
if (mrb is null) throw new Exception("MRB cannot be null");
|
||||
|
||||
User? originator = allActiveUsers.Where(u => $"{u.FirstName} {u.LastName}".Equals(mrb.OriginatorName)).FirstOrDefault();
|
||||
if (originator is not null) mrb.OriginatorID = originator.UserID;
|
||||
|
||||
if (mrb.StageNo == 0) {
|
||||
mrb.StageNo++;
|
||||
mrb.SubmittedDate = DateTime.Now;
|
||||
await mrbService.UpdateMRB(mrb);
|
||||
}
|
||||
|
||||
foreach (MRBAction action in mrbActions) {
|
||||
if (action is not null) {
|
||||
action.MRBNumber = mrb.MRBNumber;
|
||||
if (action.ActionID > 0) {
|
||||
await mrbService.UpdateMRBAction(action);
|
||||
} else {
|
||||
await mrbService.CreateMRBAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true);
|
||||
|
||||
await mrbService.SubmitForApproval(mrb);
|
||||
|
||||
await mrbService.NotifyNewApprovals(mrb);
|
||||
|
||||
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true);
|
||||
|
||||
nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList();
|
||||
taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList();
|
||||
|
||||
submitInProcess = false;
|
||||
|
||||
snackbar.Add("MRB submitted for approval", Severity.Success);
|
||||
} catch (Exception ex) {
|
||||
submitInProcess = false;
|
||||
snackbar.Add($"Unable to submit MRB for approval, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async void ApproveMRB() {
|
||||
approvalInProcess = true;
|
||||
try {
|
||||
if (mrbApprovals is null || mrbApprovals.Count() <= 0)
|
||||
throw new Exception("there are no approvals to approval");
|
||||
if (authStateProvider.CurrentUser is null) {
|
||||
navigationManager.NavigateTo("login");
|
||||
return;
|
||||
}
|
||||
if (mrb is null) throw new Exception("MRB is null");
|
||||
|
||||
string? comments = "";
|
||||
|
||||
DialogParameters<Comments> parameters = new DialogParameters<Comments> { { x => x.comments, comments } };
|
||||
var dialog = dialogService.Show<Comments>($"Approval Comments", parameters);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
comments = result.Data.ToString();
|
||||
|
||||
if (result.Canceled) throw new Exception("you must provide your approval comments");
|
||||
|
||||
IEnumerable<Approval> approvals = mrbApprovals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID &&
|
||||
!(a.CompletedDate < DateTime.MaxValue) &&
|
||||
a.Step == mrb.StageNo);
|
||||
|
||||
foreach (Approval approval in approvals) {
|
||||
approval.CompletedDate = DateTime.Now;
|
||||
approval.Comments = comments is null ? "" : comments;
|
||||
approval.ItemStatus = 1;
|
||||
await approvalService.Approve(approval);
|
||||
}
|
||||
|
||||
IEnumerable<Approval> remainingApprovalsInKind = approvals.Where(a => a.Step == mrb.StageNo &&
|
||||
a.UserID != authStateProvider.CurrentUser.UserID &&
|
||||
!(a.CompletedDate < DateTime.MaxValue));
|
||||
|
||||
if (remainingApprovalsInKind is null || remainingApprovalsInKind.Count() <= 0) {
|
||||
mrb.StageNo++;
|
||||
if (mrb.StageNo == 3) mrb.ApprovalDate = DateTime.Now;
|
||||
await mrbService.UpdateMRB(mrb);
|
||||
|
||||
if (mrb.StageNo < 3)
|
||||
SubmitMRBForApproval();
|
||||
|
||||
if (mrb.StageNo == 3) {
|
||||
GenerateActionTasks();
|
||||
|
||||
string body = $"Your MRB ({mrb.MRBNumber}) has been approved.";
|
||||
|
||||
MRBNotification notification = new() {
|
||||
MRB = mrb,
|
||||
Message = body
|
||||
};
|
||||
|
||||
await mrbService.NotifyOriginator(notification);
|
||||
}
|
||||
}
|
||||
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrb.MRBNumber, true);
|
||||
|
||||
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true);
|
||||
taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList();
|
||||
nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList();
|
||||
|
||||
approvalInProcess = false;
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
snackbar.Add("Successfully approved", Severity.Success);
|
||||
} catch (Exception ex) {
|
||||
approvalInProcess = false;
|
||||
|
||||
snackbar.Add($"Unable to approve, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DenyMRB() {
|
||||
denialInProcess = true;
|
||||
try {
|
||||
if (mrbApprovals is null || mrbApprovals.Count() <= 0)
|
||||
throw new Exception("there are no approvals to deny");
|
||||
if (authStateProvider.CurrentUser is null) {
|
||||
navigationManager.NavigateTo("login");
|
||||
return;
|
||||
}
|
||||
if (mrb is null) throw new Exception("MRB is null");
|
||||
|
||||
string? comments = "";
|
||||
|
||||
DialogParameters<Comments> parameters = new DialogParameters<Comments> { { x => x.comments, comments } };
|
||||
var dialog = dialogService.Show<Comments>($"Denial Comments", parameters);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
comments = result.Data.ToString();
|
||||
|
||||
if (result.Canceled) throw new Exception("you must provide your denial comments");
|
||||
|
||||
IEnumerable<Approval> approvals = mrbApprovals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID &&
|
||||
!(a.CompletedDate < DateTime.MaxValue) &&
|
||||
a.Step == mrb.StageNo);
|
||||
|
||||
foreach (Approval approval in approvals) {
|
||||
approval.CompletedDate = DateTime.Now;
|
||||
approval.Comments = comments is null ? "" : comments;
|
||||
approval.ItemStatus = -1;
|
||||
await approvalService.Deny(approval);
|
||||
}
|
||||
|
||||
if (mrb.StageNo < 2) {
|
||||
mrb.StageNo = 0;
|
||||
mrb.SubmittedDate = DateTime.MinValue;
|
||||
} else {
|
||||
IEnumerable<Approval> remainingApprovalsInKind = approvals.Where(a => a.Step == mrb.StageNo &&
|
||||
a.UserID != authStateProvider.CurrentUser.UserID &&
|
||||
!(a.CompletedDate < DateTime.MaxValue));
|
||||
|
||||
if (remainingApprovalsInKind is null || remainingApprovalsInKind.Count() <= 0) {
|
||||
mrb.CloseDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
await mrbService.UpdateMRB(mrb);
|
||||
|
||||
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true);
|
||||
nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList();
|
||||
|
||||
string body = $"Your MRB ({mrb.MRBNumber}) has been denied.";
|
||||
|
||||
MRBNotification notification = new() {
|
||||
MRB = mrb,
|
||||
Message = body
|
||||
};
|
||||
|
||||
await mrbService.NotifyOriginator(notification);
|
||||
|
||||
denialInProcess = false;
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
snackbar.Add("Successfully approved", Severity.Success);
|
||||
} catch (Exception ex) {
|
||||
denialInProcess = false;
|
||||
|
||||
snackbar.Add($"Unable to approve, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void GenerateActionTasks() {
|
||||
try {
|
||||
if (mrb is null) throw new Exception("MRB cannot be null");
|
||||
|
||||
foreach (MRBAction action in mrbActions) {
|
||||
action.AssignedDate = DateTime.Now;
|
||||
await mrbService.UpdateMRBAction(action);
|
||||
|
||||
await mrbService.GenerateActionTasks(mrb, action);
|
||||
}
|
||||
|
||||
await mrbService.NotifyNewApprovals(mrb);
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add($"Unable to generate action tasks, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CompleteAction(MRBAction action) {
|
||||
try {
|
||||
if (action is null) throw new Exception("MRB action cannot be null");
|
||||
if (authStateProvider.CurrentUser is null)
|
||||
throw new Exception("you must be logged in to complete this action");
|
||||
if (mrb is null) throw new Exception("MRB cannot be null");
|
||||
|
||||
action.CompletedDate = DateTime.Now;
|
||||
action.CompletedByUserID = authStateProvider.CurrentUser.UserID;
|
||||
action.CompletedByUser = authStateProvider.CurrentUser;
|
||||
|
||||
await mrbService.UpdateMRBAction(action);
|
||||
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(action.MRBNumber, true);
|
||||
|
||||
string role = "";
|
||||
foreach (Approval approval in taskApprovals) {
|
||||
bool approved = false;
|
||||
if (approval.UserID == action.CompletedByUserID && approval.ItemStatus == 0 && approval.TaskID == action.ActionID) {
|
||||
approved = true;
|
||||
role = approval.SubRoleCategoryItem;
|
||||
await approvalService.Approve(approval);
|
||||
}
|
||||
|
||||
if (!approved && approval.SubRoleCategoryItem.Equals(role))
|
||||
await approvalService.Approve(approval);
|
||||
}
|
||||
|
||||
mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true);
|
||||
taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList();
|
||||
|
||||
int outStandingTaskCount = taskApprovals.Where(a => a.CompletedDate >= DateTime.MaxValue).Count();
|
||||
|
||||
if (outStandingTaskCount == 0) {
|
||||
mrb.StageNo++;
|
||||
mrb.CloseDate = DateTime.Now;
|
||||
await mrbService.UpdateMRB(mrb);
|
||||
|
||||
string body = $"Your MRB ({mrb.MRBNumber}) is complete.";
|
||||
|
||||
MRBNotification notification = new() {
|
||||
MRB = mrb,
|
||||
Message = body
|
||||
};
|
||||
|
||||
await mrbService.NotifyOriginator(notification);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add($"Unable to mark action complete, because {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool mrbIsReadyToSubmit() {
|
||||
return mrb is not null && !(mrb.SubmittedDate > DateTime.MinValue) && mrb.MRBNumber > 0 && mrb.OriginatorID > 0 && !mrb.OriginatorName.Equals("") &&
|
||||
!mrb.Title.Equals("") && !mrb.IssueDescription.Equals("") && !mrb.Department.Equals("") && !mrb.Process.Equals("");
|
||||
}
|
||||
|
||||
private bool currentUserIsApprover() {
|
||||
if (mrbApprovals is null || authStateProvider is null) return false;
|
||||
if (authStateProvider.CurrentUser is null) return false;
|
||||
|
||||
IEnumerable<Approval> approvalsForCurrentUser = mrbApprovals.Where(a => mrb is not null &&
|
||||
mrb.StageNo < 3 &&
|
||||
a.UserID == authStateProvider.CurrentUser.UserID &&
|
||||
a.ItemStatus == 0);
|
||||
|
||||
if (approvalsForCurrentUser is not null && approvalsForCurrentUser.Count() > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async void CreateNewAction() {
|
||||
try {
|
||||
MRBAction mrbAction = new() {
|
||||
Action = "",
|
||||
Customer = "",
|
||||
LotNumber = "",
|
||||
PartNumber = "",
|
||||
MRBNumber = mrbNumberInt,
|
||||
Quantity = 0
|
||||
};
|
||||
|
||||
DialogParameters<MRBActionForm> parameters = new DialogParameters<MRBActionForm> { { x => x.mrbAction, mrbAction } };
|
||||
var dialog = dialogService.Show<MRBActionForm>($"New MRB Action", parameters);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled) {
|
||||
if (mrbNumberInt > 0) {
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true);
|
||||
} else {
|
||||
List<MRBAction> actionList = mrbActions.ToList();
|
||||
actionList.Add(mrbAction);
|
||||
mrbActions = actionList;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditAction(MRBAction mrbAction) {
|
||||
try {
|
||||
if (mrbAction is null)
|
||||
throw new ArgumentNullException("Action cannot be null");
|
||||
|
||||
var parameters = new DialogParameters<MRBActionForm> { { x => x.mrbAction, mrbAction } };
|
||||
var dialog = dialogService.Show<MRBActionForm>($"MRB Action {mrbAction.ActionID}", parameters);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled) {
|
||||
if (mrbNumberInt > 0) {
|
||||
mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true);
|
||||
} else {
|
||||
List<MRBAction> actionList = mrbActions.ToList();
|
||||
actionList.Add(mrbAction);
|
||||
mrbActions = actionList;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteAction(MRBAction mrbAction) {
|
||||
deleteActionInProcess = true;
|
||||
try {
|
||||
if (mrbAction is null)
|
||||
throw new ArgumentNullException("Action cannot be null");
|
||||
|
||||
await mrbService.DeleteMRBAction(mrbAction);
|
||||
|
||||
List<MRBAction> mrbActionList = mrbActions.ToList();
|
||||
mrbActionList.RemoveAll(x => x.ActionID == mrbAction.ActionID);
|
||||
mrbActions = mrbActionList;
|
||||
|
||||
snackbar.Add("Action successfully deleted", Severity.Success);
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
deleteActionInProcess = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private bool FilterFuncForMRBActionTable(MRBAction action) => MRBActionFilterFunc(action, actionSearchString);
|
||||
|
||||
private bool MRBActionFilterFunc(MRBAction action, string searchString) {
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
return true;
|
||||
|
||||
string search = searchString.ToLower();
|
||||
if (action.Customer.ToLower().Contains(search))
|
||||
return true;
|
||||
if (action.Action.ToLower().Contains(search))
|
||||
return true;
|
||||
if (action.PartNumber.ToLower().Contains(search))
|
||||
return true;
|
||||
if (action.LotNumber.ToLower().Contains(search))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task AddAttachments(InputFileChangeEventArgs args) {
|
||||
List<IBrowserFile> attachments = new() { args.File };
|
||||
|
||||
if (authStateProvider.CurrentUser is not null) {
|
||||
await mrbService.UploadAttachments(attachments, mrbNumberInt);
|
||||
|
||||
mrbAttachments = await mrbService.GetAllAttachmentsForMRB(mrbNumberInt, true);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteAttachment(MRBAttachment mrbAttachment) {
|
||||
deleteAttachmentInProcess = true;
|
||||
try {
|
||||
if (mrbAttachment is null)
|
||||
throw new ArgumentNullException("Attachment cannot be null");
|
||||
|
||||
await mrbService.DeleteAttachment(mrbAttachment);
|
||||
|
||||
List<MRBAttachment> mrbAttachmentList = mrbAttachments.ToList();
|
||||
mrbAttachmentList.RemoveAll(x => x.AttachmentID == mrbAttachment.AttachmentID);
|
||||
mrbAttachments = mrbAttachmentList;
|
||||
|
||||
snackbar.Add("Attachment successfully deleted", Severity.Success);
|
||||
} catch (Exception ex) {
|
||||
snackbar.Add(ex.Message, Severity.Error);
|
||||
}
|
||||
deleteAttachmentInProcess = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private bool FilterFuncForMRBAttachmentTable(MRBAttachment attachment) => MRBAttachmentFilterFunc(attachment, attachmentSearchString);
|
||||
|
||||
private bool MRBAttachmentFilterFunc(MRBAttachment attachment, string searchString) {
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
return true;
|
||||
|
||||
string search = searchString.ToLower();
|
||||
if (attachment.FileName.ToLower().Contains(search))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user