Compare commits
No commits in common. "misya" and "main" have entirely different histories.
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {}
|
|
||||||
}
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -361,6 +361,3 @@ MigrationBackup/
|
|||||||
|
|
||||||
# Fody - auto-generated XML schema
|
# Fody - auto-generated XML schema
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
|
||||||
# Ignore local publish test builds
|
|
||||||
publish-test/
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
@page
|
|
||||||
@model AccessDeniedModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Access denied";
|
|
||||||
@inject UserManager<UserModel> _userManager
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
var userComDept = user.departmentId;
|
|
||||||
var userRole = await _userManager.GetRolesAsync(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<header id="deniedHeader">
|
|
||||||
<template v-if="ldapUserInfo.role.length == 0"><p class="text-danger">You do not have access to this resource because you have no role. Please contact the system administrator.</p></template>
|
|
||||||
<template v-else><p class="text-danger">You do not have access to this resource.</p></template>
|
|
||||||
</header>
|
|
||||||
@section Scripts {
|
|
||||||
<script>
|
|
||||||
if (typeof jQuery === 'undefined') {
|
|
||||||
console.error('jQuery is not loaded.');
|
|
||||||
}
|
|
||||||
$(function () {
|
|
||||||
app.mount('#deniedHeader');
|
|
||||||
});
|
|
||||||
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ldapUserInfo: {
|
|
||||||
role: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getUserInfo();
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getUserInfo() {
|
|
||||||
try {
|
|
||||||
// Show the loading modal
|
|
||||||
$('#loadingModal').modal('show');
|
|
||||||
|
|
||||||
// Perform the fetch request
|
|
||||||
const response = await fetch('/IdentityAPI/GetUserInformation', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the response is OK
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
throw new Error(errorData.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.userInfo) {
|
|
||||||
console.log(data.userInfo)
|
|
||||||
this.ldapUserInfo = data.userInfo
|
|
||||||
} else {
|
|
||||||
console.error('Get user failed:', data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error getting user information:', error);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
$('#loadingModal').on('shown.bs.modal', resolve);
|
|
||||||
});
|
|
||||||
$('#loadingModal').modal('hide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class AccessDeniedModel : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ConfirmEmailModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Confirm email";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<partial name="_StatusMessage" model="Model.StatusMessage" />
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class ConfirmEmailModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
|
|
||||||
public ConfirmEmailModel(UserManager<UserModel> userManager)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
public async Task<IActionResult> OnGetAsync(string userId, string code)
|
|
||||||
{
|
|
||||||
if (userId == null || code == null)
|
|
||||||
{
|
|
||||||
return RedirectToPage("/Index");
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{userId}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
|
|
||||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
|
||||||
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ConfirmEmailChangeModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Confirm email change";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<partial name="_StatusMessage" model="Model.StatusMessage" />
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class ConfirmEmailChangeModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
|
|
||||||
public ConfirmEmailChangeModel(UserManager<UserModel> userManager, SignInManager<UserModel> signInManager)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
|
|
||||||
{
|
|
||||||
if (userId == null || email == null || code == null)
|
|
||||||
{
|
|
||||||
return RedirectToPage("/Index");
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{userId}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
|
|
||||||
var result = await _userManager.ChangeEmailAsync(user, email, code);
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
StatusMessage = "Error changing email.";
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
// In our UI email and user name are one and the same, so when we update the email
|
|
||||||
// we need to update the user name.
|
|
||||||
var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
|
|
||||||
if (!setUserNameResult.Succeeded)
|
|
||||||
{
|
|
||||||
StatusMessage = "Error changing user name.";
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
StatusMessage = "Thank you for confirming your email change.";
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ExternalLoginModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Register";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<h2 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h2>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<p id="external-login-description" class="text-info">
|
|
||||||
You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>.
|
|
||||||
Please enter an email address for this site below and click the Register button to finish
|
|
||||||
logging in.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" autocomplete="email" placeholder="Please enter your email."/>
|
|
||||||
<label asp-for="Input.Email" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,224 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class ExternalLoginModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly IUserStore<UserModel> _userStore;
|
|
||||||
private readonly IUserEmailStore<UserModel> _emailStore;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
private readonly ILogger<ExternalLoginModel> _logger;
|
|
||||||
|
|
||||||
public ExternalLoginModel(
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
IUserStore<UserModel> userStore,
|
|
||||||
ILogger<ExternalLoginModel> logger,
|
|
||||||
IEmailSender emailSender)
|
|
||||||
{
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
_userStore = userStore;
|
|
||||||
_emailStore = GetEmailStore();
|
|
||||||
_logger = logger;
|
|
||||||
_emailSender = emailSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ProviderDisplayName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ReturnUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult OnGet() => RedirectToPage("./Login");
|
|
||||||
|
|
||||||
public IActionResult OnPost(string provider, string returnUrl = null)
|
|
||||||
{
|
|
||||||
// Request a redirect to the external login provider.
|
|
||||||
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
|
|
||||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
|
|
||||||
return new ChallengeResult(provider, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
|
|
||||||
{
|
|
||||||
returnUrl = returnUrl ?? Url.Content("~/");
|
|
||||||
if (remoteError != null)
|
|
||||||
{
|
|
||||||
ErrorMessage = $"Error from external provider: {remoteError}";
|
|
||||||
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
|
|
||||||
}
|
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
|
||||||
if (info == null)
|
|
||||||
{
|
|
||||||
ErrorMessage = "Error loading external login information.";
|
|
||||||
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign in the user with this external login provider if the user already has a login.
|
|
||||||
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("{CompanyName} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
if (result.IsLockedOut)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./Lockout");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the user does not have an account, then ask the user to create an account.
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
ProviderDisplayName = info.ProviderDisplayName;
|
|
||||||
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
|
|
||||||
{
|
|
||||||
Input = new InputModel
|
|
||||||
{
|
|
||||||
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
returnUrl = returnUrl ?? Url.Content("~/");
|
|
||||||
// Get the information about the user from the external login provider
|
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync();
|
|
||||||
if (info == null)
|
|
||||||
{
|
|
||||||
ErrorMessage = "Error loading external login information during confirmation.";
|
|
||||||
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
var user = CreateUser();
|
|
||||||
|
|
||||||
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
|
|
||||||
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
|
|
||||||
|
|
||||||
var result = await _userManager.CreateAsync(user);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
result = await _userManager.AddLoginAsync(user, info);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User created an account using {CompanyName} provider.", info.LoginProvider);
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmail",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", userId = userId, code = code },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
|
|
||||||
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
|
|
||||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
// If account confirmation is required, we need to show the link if we don't have a real email sender
|
|
||||||
if (_userManager.Options.SignIn.RequireConfirmedAccount)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var error in result.Errors)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProviderDisplayName = info.ProviderDisplayName;
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserModel CreateUser()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Activator.CreateInstance<UserModel>();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Can't create an instance of '{nameof(UserModel)}'. " +
|
|
||||||
$"Ensure that '{nameof(UserModel)}' is not an abstract class and has a parameterless constructor, or alternatively " +
|
|
||||||
$"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IUserEmailStore<UserModel> GetEmailStore()
|
|
||||||
{
|
|
||||||
if (!_userManager.SupportsUserEmail)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("The default UI requires a user store with email support.");
|
|
||||||
}
|
|
||||||
return (IUserEmailStore<UserModel>)_userStore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ForgotPasswordModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Forgot your password?";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<h2>Enter your email.</h2>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label asp-for="Input.Email" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Reset Password</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class ForgotPasswordModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
|
|
||||||
public ForgotPasswordModel(UserManager<UserModel> userManager, IEmailSender emailSender)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_emailSender = emailSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
var user = await _userManager.FindByEmailAsync(Input.Email);
|
|
||||||
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
|
|
||||||
{
|
|
||||||
// Don't reveal that the user does not exist or is not confirmed
|
|
||||||
return RedirectToPage("./ForgotPasswordConfirmation");
|
|
||||||
}
|
|
||||||
|
|
||||||
// For more information on how to enable account confirmation and password reset please
|
|
||||||
// visit https://go.microsoft.com/fwlink/?LinkID=532713
|
|
||||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ResetPassword",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", code },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
|
|
||||||
await _emailSender.SendEmailAsync(
|
|
||||||
Input.Email,
|
|
||||||
"Reset Password",
|
|
||||||
$"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
return RedirectToPage("./ForgotPasswordConfirmation");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ForgotPasswordConfirmation
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Forgot password confirmation";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<p>
|
|
||||||
Please check your email to reset your password.
|
|
||||||
</p>
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class ForgotPasswordConfirmation : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@page
|
|
||||||
@model LockoutModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Locked out";
|
|
||||||
}
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<h1 class="text-danger">@ViewData["Title"]</h1>
|
|
||||||
<p class="text-danger">This account has been locked out, please try again later.</p>
|
|
||||||
</header>
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class LockoutModel : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
@page
|
|
||||||
@model LoginModel
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Log in";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="row" id="systemLogin">
|
|
||||||
<div class="row">
|
|
||||||
<h2><label class="col-md-2">Login Type</label></h2>
|
|
||||||
<div class="btn-group col-md-4" role="group" aria-label="Login type">
|
|
||||||
<input type="radio" class="btn-check" name="loginType" id="local-login" value="Local" v-model="loginType">
|
|
||||||
<label class="btn btn-outline-primary" for="local-login">Local</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="loginType" id="ad-login" value="AD" v-model="loginType" checked>
|
|
||||||
<label class="btn btn-outline-primary" for="ad-login">AD</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4" v-if="loginType == 'Local'">
|
|
||||||
<form id="account" method="post">
|
|
||||||
<h2>Use a local account to log in.</h2>
|
|
||||||
<hr />
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label asp-for="Input.Email" class="form-label">Email</label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
|
|
||||||
<label asp-for="Input.Password" class="form-label">Password</label>
|
|
||||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4" v-if="loginType == 'AD'">
|
|
||||||
<form v-on:submit.prevent="ldapLogin" id="login" method="post">
|
|
||||||
<h2>Use a AD account to log in.</h2>
|
|
||||||
<hr />
|
|
||||||
<div class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input v-model="ldapLoginInfo.username" id="ldapUsername" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label id="ldapEmailLabel" class="form-label">Windows Login</label>
|
|
||||||
<span id="ldapEmailError" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input v-model="ldapLoginInfo.password" id="ldapPassword" class="form-control" type="password" autocomplete="current-password" aria-required="true" placeholder="password" />
|
|
||||||
<label id="ldapPasswordLabel" class="form-label">Password</label>
|
|
||||||
<span id="ldapPasswordError" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="ldap-login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 col-md-offset-2">
|
|
||||||
<section>
|
|
||||||
<h3>Use another service to log in.</h3>
|
|
||||||
<hr />
|
|
||||||
@{
|
|
||||||
if ((Model.ExternalLogins?.Count ?? 0) == 0)
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
|
|
||||||
about setting up this ASP.NET application to support logging in via external services</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
@foreach (var provider in Model.ExternalLogins!)
|
|
||||||
{
|
|
||||||
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
app.mount('#systemLogin');
|
|
||||||
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loginType: 'AD',
|
|
||||||
ldapLoginInfo: {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async ldapLogin() {
|
|
||||||
try {
|
|
||||||
// Show the loading modal
|
|
||||||
$('#loadingModal').modal('show');
|
|
||||||
|
|
||||||
// Perform the fetch request
|
|
||||||
const response = await fetch('/IdentityAPI/LdapLogin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.ldapLoginInfo),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the response is OK
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
throw new Error(errorData.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Redirect if a URL is provided
|
|
||||||
if (data.redirectUrl) {
|
|
||||||
window.location.href = data.redirectUrl;
|
|
||||||
} else {
|
|
||||||
console.error('Login failed:', data);
|
|
||||||
alert('Login failed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error during LDAP login:', error);
|
|
||||||
alert(error.message);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
$('#loadingModal').on('shown.bs.modal', resolve);
|
|
||||||
});
|
|
||||||
$('#loadingModal').modal('hide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchControllerMethodList() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/AdminAPI/GetListClassAndMethodInformation', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Assign data if it exists
|
|
||||||
if (data) {
|
|
||||||
this.controllerMethodData = data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class LoginModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<LoginModel> _logger;
|
|
||||||
|
|
||||||
public LoginModel(SignInManager<UserModel> signInManager, ILogger<LoginModel> logger)
|
|
||||||
{
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ReturnUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Display(Name = "Remember me?")]
|
|
||||||
public bool RememberMe { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnGetAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
returnUrl ??= Url.Content("~/");
|
|
||||||
|
|
||||||
// Clear the existing external cookie to ensure a clean login process
|
|
||||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
|
||||||
|
|
||||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
|
||||||
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
returnUrl ??= Url.Content("~/");
|
|
||||||
|
|
||||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
|
||||||
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
// This doesn't count login failures towards account lockout
|
|
||||||
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
|
|
||||||
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User logged in.");
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
if (result.RequiresTwoFactor)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
|
|
||||||
}
|
|
||||||
if (result.IsLockedOut)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("User account locked out.");
|
|
||||||
return RedirectToPage("./Lockout");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
@page
|
|
||||||
@model LoginWith2faModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Two-factor authentication";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<hr />
|
|
||||||
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form method="post" asp-route-returnUrl="@Model.ReturnUrl">
|
|
||||||
<input asp-for="RememberMe" type="hidden" />
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" />
|
|
||||||
<label asp-for="Input.TwoFactorCode" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox mb-3">
|
|
||||||
<label asp-for="Input.RememberMachine" class="form-label">
|
|
||||||
<input asp-for="Input.RememberMachine" />
|
|
||||||
@Html.DisplayNameFor(m => m.Input.RememberMachine)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
Don't have access to your authenticator device? You can
|
|
||||||
<a id="recovery-code-login" asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class LoginWith2faModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<LoginWith2faModel> _logger;
|
|
||||||
|
|
||||||
public LoginWith2faModel(
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<LoginWith2faModel> logger)
|
|
||||||
{
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool RememberMe { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ReturnUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Text)]
|
|
||||||
[Display(Name = "Authenticator code")]
|
|
||||||
public string TwoFactorCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Display(Name = "Remember this machine")]
|
|
||||||
public bool RememberMachine { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null)
|
|
||||||
{
|
|
||||||
// Ensure the user has gone through the username & password screen first
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
RememberMe = rememberMe;
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null)
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
returnUrl = returnUrl ?? Url.Content("~/");
|
|
||||||
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
|
|
||||||
|
|
||||||
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
else if (result.IsLockedOut)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
|
|
||||||
return RedirectToPage("./Lockout");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
|
|
||||||
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
@page
|
|
||||||
@model LoginWithRecoveryCodeModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Recovery code verification";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
You have requested to log in with a recovery code. This login will not be remembered until you provide
|
|
||||||
an authenticator app code at log in or disable 2FA and log in again.
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" placeholder="RecoveryCode" />
|
|
||||||
<label asp-for="Input.RecoveryCode" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.RecoveryCode" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class LoginWithRecoveryCodeModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<LoginWithRecoveryCodeModel> _logger;
|
|
||||||
|
|
||||||
public LoginWithRecoveryCodeModel(
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<LoginWithRecoveryCodeModel> logger)
|
|
||||||
{
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ReturnUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
[Required]
|
|
||||||
[DataType(DataType.Text)]
|
|
||||||
[Display(Name = "Recovery Code")]
|
|
||||||
public string RecoveryCode { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
// Ensure the user has gone through the username & password screen first
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
|
|
||||||
|
|
||||||
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
|
|
||||||
return LocalRedirect(returnUrl ?? Url.Content("~/"));
|
|
||||||
}
|
|
||||||
if (result.IsLockedOut)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("User account locked out.");
|
|
||||||
return RedirectToPage("./Lockout");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
|
|
||||||
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
@page
|
|
||||||
@model LogoutModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Log out";
|
|
||||||
}
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
@{
|
|
||||||
if (User.Identity?.IsAuthenticated ?? false)
|
|
||||||
{
|
|
||||||
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
|
|
||||||
<button type="submit" class="nav-link btn btn-link text-dark">Click here to Logout</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<p>You have successfully logged out of the application.</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</header>
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class LogoutModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<LogoutModel> _logger;
|
|
||||||
|
|
||||||
public LogoutModel(SignInManager<UserModel> signInManager, ILogger<LogoutModel> logger)
|
|
||||||
{
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
|
||||||
{
|
|
||||||
await _signInManager.SignOutAsync();
|
|
||||||
_logger.LogInformation("User logged out.");
|
|
||||||
if (returnUrl != null)
|
|
||||||
{
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This needs to be a redirect so that the browser performs a new
|
|
||||||
// request and the identity for the user gets updated.
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ChangePasswordModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Change password";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form id="change-password-form" method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.OldPassword" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password." />
|
|
||||||
<label asp-for="Input.OldPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.OldPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password." />
|
|
||||||
<label asp-for="Input.NewPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
|
|
||||||
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Update password</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class ChangePasswordModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<ChangePasswordModel> _logger;
|
|
||||||
|
|
||||||
public ChangePasswordModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
ILogger<ChangePasswordModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Current password")]
|
|
||||||
public string OldPassword { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "New password")]
|
|
||||||
public string NewPassword { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Confirm new password")]
|
|
||||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
|
||||||
public string ConfirmPassword { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
|
||||||
if (!hasPassword)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./SetPassword");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
|
|
||||||
if (!changePasswordResult.Succeeded)
|
|
||||||
{
|
|
||||||
foreach (var error in changePasswordResult.Errors)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
|
||||||
}
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
_logger.LogInformation("User changed their password successfully.");
|
|
||||||
StatusMessage = "Your password has been changed.";
|
|
||||||
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
@page
|
|
||||||
@model DeletePersonalDataModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Delete Personal Data";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.PersonalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>
|
|
||||||
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<form id="delete-user" method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
@if (Model.RequirePassword)
|
|
||||||
{
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your password." />
|
|
||||||
<label asp-for="Input.Password" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<button class="w-100 btn btn-lg btn-danger" type="submit">Delete data and close my account</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class DeletePersonalDataModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<DeletePersonalDataModel> _logger;
|
|
||||||
|
|
||||||
public DeletePersonalDataModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
ILogger<DeletePersonalDataModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
public string Password { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool RequirePassword { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGet()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RequirePassword = await _userManager.HasPasswordAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RequirePassword = await _userManager.HasPasswordAsync(user);
|
|
||||||
if (RequirePassword)
|
|
||||||
{
|
|
||||||
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, "Incorrect password.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userManager.DeleteAsync(user);
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected error occurred deleting user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.SignOutAsync();
|
|
||||||
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
|
|
||||||
|
|
||||||
return Redirect("~/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
@page
|
|
||||||
@model Disable2faModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Disable two-factor authentication (2FA)";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>
|
|
||||||
<strong>This action only disables 2FA.</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
|
|
||||||
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<form method="post">
|
|
||||||
<button class="btn btn-danger" type="submit">Disable 2FA</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class Disable2faModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<Disable2faModel> _logger;
|
|
||||||
|
|
||||||
public Disable2faModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<Disable2faModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGet()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await _userManager.GetTwoFactorEnabledAsync(user))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
|
|
||||||
if (!disable2faResult.Succeeded)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
|
|
||||||
StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
|
|
||||||
return RedirectToPage("./TwoFactorAuthentication");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
@page
|
|
||||||
@model DownloadPersonalDataModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Download Your Data";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.PersonalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class DownloadPersonalDataModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<DownloadPersonalDataModel> _logger;
|
|
||||||
|
|
||||||
public DownloadPersonalDataModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<DownloadPersonalDataModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult OnGet()
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
|
|
||||||
|
|
||||||
// Only include personal data for download
|
|
||||||
var personalData = new Dictionary<string, string>();
|
|
||||||
var personalDataProps = typeof(UserModel).GetProperties().Where(
|
|
||||||
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
|
|
||||||
foreach (var p in personalDataProps)
|
|
||||||
{
|
|
||||||
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
|
|
||||||
}
|
|
||||||
|
|
||||||
var logins = await _userManager.GetLoginsAsync(user);
|
|
||||||
foreach (var l in logins)
|
|
||||||
{
|
|
||||||
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user));
|
|
||||||
|
|
||||||
Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json");
|
|
||||||
return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
@page
|
|
||||||
@model EmailModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Manage Email";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.Email;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form id="email-form" method="post">
|
|
||||||
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
|
|
||||||
@if (Model.IsEmailConfirmed)
|
|
||||||
{
|
|
||||||
<div class="form-floating mb-3 input-group">
|
|
||||||
<input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
|
|
||||||
<div class="input-group-append">
|
|
||||||
<span class="h-100 input-group-text text-success font-weight-bold">✓</span>
|
|
||||||
</div>
|
|
||||||
<label asp-for="Email" class="form-label"></label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
|
|
||||||
<label asp-for="Email" class="form-label"></label>
|
|
||||||
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" placeholder="Please enter new email." />
|
|
||||||
<label asp-for="Input.NewEmail" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.NewEmail" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class EmailModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
|
|
||||||
public EmailModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
IEmailSender emailSender)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_emailSender = emailSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEmailConfirmed { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
[Display(Name = "New email")]
|
|
||||||
public string NewEmail { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadAsync(UserModel user)
|
|
||||||
{
|
|
||||||
var email = await _userManager.GetEmailAsync(user);
|
|
||||||
Email = email;
|
|
||||||
|
|
||||||
Input = new InputModel
|
|
||||||
{
|
|
||||||
NewEmail = email,
|
|
||||||
};
|
|
||||||
|
|
||||||
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await LoadAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostChangeEmailAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
await LoadAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var email = await _userManager.GetEmailAsync(user);
|
|
||||||
if (Input.NewEmail != email)
|
|
||||||
{
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmailChange",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
await _emailSender.SendEmailAsync(
|
|
||||||
Input.NewEmail,
|
|
||||||
"Confirm your email",
|
|
||||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
StatusMessage = "Confirmation link to change email sent. Please check your email.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage = "Your email is unchanged.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
await LoadAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var email = await _userManager.GetEmailAsync(user);
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmail",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", userId = userId, code = code },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
await _emailSender.SendEmailAsync(
|
|
||||||
email,
|
|
||||||
"Confirm your email",
|
|
||||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
StatusMessage = "Verification email sent. Please check your email.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
@page
|
|
||||||
@model EnableAuthenticatorModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Configure authenticator app";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<div>
|
|
||||||
<p>To use an authenticator app go through the following steps:</p>
|
|
||||||
<ol class="list">
|
|
||||||
<li>
|
|
||||||
<p>
|
|
||||||
Download a two-factor authenticator app like Microsoft Authenticator for
|
|
||||||
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
|
|
||||||
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
|
|
||||||
Google Authenticator for
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en">Android</a> and
|
|
||||||
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
|
|
||||||
<div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
|
|
||||||
<div id="qrCode"></div>
|
|
||||||
<div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>
|
|
||||||
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
|
|
||||||
with a unique code. Enter the code in the confirmation box below.
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form id="send-code" method="post">
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Code" class="form-control" autocomplete="off" placeholder="Please enter the code."/>
|
|
||||||
<label asp-for="Input.Code" class="control-label form-label">Verification Code</label>
|
|
||||||
<span asp-validation-for="Input.Code" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button>
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class EnableAuthenticatorModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<EnableAuthenticatorModel> _logger;
|
|
||||||
private readonly UrlEncoder _urlEncoder;
|
|
||||||
|
|
||||||
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
|
||||||
|
|
||||||
public EnableAuthenticatorModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<EnableAuthenticatorModel> logger,
|
|
||||||
UrlEncoder urlEncoder)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
_urlEncoder = urlEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string SharedKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string AuthenticatorUri { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string[] RecoveryCodes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Text)]
|
|
||||||
[Display(Name = "Verification Code")]
|
|
||||||
public string Code { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await LoadSharedKeyAndQrCodeUriAsync(user);
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
await LoadSharedKeyAndQrCodeUriAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip spaces and hyphens
|
|
||||||
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
|
|
||||||
|
|
||||||
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
|
|
||||||
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
|
|
||||||
|
|
||||||
if (!is2faTokenValid)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
|
|
||||||
await LoadSharedKeyAndQrCodeUriAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userManager.SetTwoFactorEnabledAsync(user, true);
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
|
|
||||||
|
|
||||||
StatusMessage = "Your authenticator app has been verified.";
|
|
||||||
|
|
||||||
if (await _userManager.CountRecoveryCodesAsync(user) == 0)
|
|
||||||
{
|
|
||||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
|
||||||
RecoveryCodes = recoveryCodes.ToArray();
|
|
||||||
return RedirectToPage("./ShowRecoveryCodes");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return RedirectToPage("./TwoFactorAuthentication");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadSharedKeyAndQrCodeUriAsync(UserModel user)
|
|
||||||
{
|
|
||||||
// Load the authenticator key & QR code URI to display on the form
|
|
||||||
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
|
||||||
if (string.IsNullOrEmpty(unformattedKey))
|
|
||||||
{
|
|
||||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
|
||||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedKey = FormatKey(unformattedKey);
|
|
||||||
|
|
||||||
var email = await _userManager.GetEmailAsync(user);
|
|
||||||
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatKey(string unformattedKey)
|
|
||||||
{
|
|
||||||
var result = new StringBuilder();
|
|
||||||
int currentPosition = 0;
|
|
||||||
while (currentPosition + 4 < unformattedKey.Length)
|
|
||||||
{
|
|
||||||
result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
|
|
||||||
currentPosition += 4;
|
|
||||||
}
|
|
||||||
if (currentPosition < unformattedKey.Length)
|
|
||||||
{
|
|
||||||
result.Append(unformattedKey.AsSpan(currentPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString().ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
|
||||||
{
|
|
||||||
return string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
AuthenticatorUriFormat,
|
|
||||||
_urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
|
|
||||||
_urlEncoder.Encode(email),
|
|
||||||
unformattedKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ExternalLoginsModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Manage your external logins";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
@if (Model.CurrentLogins?.Count > 0)
|
|
||||||
{
|
|
||||||
<h3>Registered Logins</h3>
|
|
||||||
<table class="table">
|
|
||||||
<tbody>
|
|
||||||
@foreach (var login in Model.CurrentLogins)
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td>
|
|
||||||
<td>
|
|
||||||
@if (Model.ShowRemoveButton)
|
|
||||||
{
|
|
||||||
<form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post">
|
|
||||||
<div>
|
|
||||||
<input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
|
|
||||||
<input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
|
|
||||||
<button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@:
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
@if (Model.OtherLogins?.Count > 0)
|
|
||||||
{
|
|
||||||
<h4>Add another service to log in.</h4>
|
|
||||||
<hr />
|
|
||||||
<form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal">
|
|
||||||
<div id="socialLoginList">
|
|
||||||
<p>
|
|
||||||
@foreach (var provider in Model.OtherLogins)
|
|
||||||
{
|
|
||||||
<button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class ExternalLoginsModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly IUserStore<UserModel> _userStore;
|
|
||||||
|
|
||||||
public ExternalLoginsModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
IUserStore<UserModel> userStore)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_userStore = userStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public IList<AuthenticationScheme> OtherLogins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowRemoveButton { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentLogins = await _userManager.GetLoginsAsync(user);
|
|
||||||
OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
|
||||||
.Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
string passwordHash = null;
|
|
||||||
if (_userStore is IUserPasswordStore<UserModel> userPasswordStore)
|
|
||||||
{
|
|
||||||
passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey)
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
StatusMessage = "The external login was not removed.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
StatusMessage = "The external login was removed.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
|
|
||||||
{
|
|
||||||
// Clear the existing external cookie to ensure a clean login process
|
|
||||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
|
||||||
|
|
||||||
// Request a redirect to the external login provider to link a login for the current user
|
|
||||||
var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
|
|
||||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
|
|
||||||
return new ChallengeResult(provider, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetLinkLoginCallbackAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var info = await _signInManager.GetExternalLoginInfoAsync(userId);
|
|
||||||
if (info == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected error occurred loading external login info.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userManager.AddLoginAsync(user, info);
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
StatusMessage = "The external login was not added. External logins can only be associated with one account.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the existing external cookie to ensure a clean login process
|
|
||||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
|
||||||
|
|
||||||
StatusMessage = "The external login was added.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
@page
|
|
||||||
@model GenerateRecoveryCodesModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>
|
|
||||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
|
||||||
<strong>Put these codes in a safe place.</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
|
|
||||||
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<form method="post">
|
|
||||||
<button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class GenerateRecoveryCodesModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<GenerateRecoveryCodesModel> _logger;
|
|
||||||
|
|
||||||
public GenerateRecoveryCodesModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<GenerateRecoveryCodesModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string[] RecoveryCodes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
|
|
||||||
if (!isTwoFactorEnabled)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
if (!isTwoFactorEnabled)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
|
||||||
RecoveryCodes = recoveryCodes.ToArray();
|
|
||||||
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
|
|
||||||
StatusMessage = "You have generated new recovery codes.";
|
|
||||||
return RedirectToPage("./ShowRecoveryCodes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
@page
|
|
||||||
@model IndexModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Profile";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.Index;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form id="profile-form" method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Username" class="form-control" placeholder="Please choose your username." disabled />
|
|
||||||
<label asp-for="Username" class="form-label"></label>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number."/>
|
|
||||||
<label asp-for="Input.PhoneNumber" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class IndexModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
|
|
||||||
public IndexModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Phone]
|
|
||||||
[Display(Name = "Phone number")]
|
|
||||||
public string PhoneNumber { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadAsync(UserModel user)
|
|
||||||
{
|
|
||||||
var userName = await _userManager.GetUserNameAsync(user);
|
|
||||||
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
|
|
||||||
|
|
||||||
Username = userName;
|
|
||||||
|
|
||||||
Input = new InputModel
|
|
||||||
{
|
|
||||||
PhoneNumber = phoneNumber
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await LoadAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
await LoadAsync(user);
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
|
|
||||||
if (Input.PhoneNumber != phoneNumber)
|
|
||||||
{
|
|
||||||
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
|
|
||||||
if (!setPhoneResult.Succeeded)
|
|
||||||
{
|
|
||||||
StatusMessage = "Unexpected error when trying to set phone number.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
StatusMessage = "Your profile has been updated";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static class ManageNavPages
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string Index => "Index";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string Email => "Email";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string ChangePassword => "ChangePassword";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string DownloadPersonalData => "DownloadPersonalData";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string DeletePersonalData => "DeletePersonalData";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string ExternalLogins => "ExternalLogins";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string PersonalData => "PersonalData";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public static string PageNavClass(ViewContext viewContext, string page)
|
|
||||||
{
|
|
||||||
var activePage = viewContext.ViewData["ActivePage"] as string
|
|
||||||
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
|
|
||||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
@page
|
|
||||||
@model PersonalDataModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Personal Data";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.PersonalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
|
|
||||||
<p>
|
|
||||||
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
|
|
||||||
</p>
|
|
||||||
<form id="download-data" asp-page="DownloadPersonalData" method="post">
|
|
||||||
<button class="btn btn-primary" type="submit">Download</button>
|
|
||||||
</form>
|
|
||||||
<p>
|
|
||||||
<a id="delete" asp-page="DeletePersonalData" class="btn btn-danger">Delete</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class PersonalDataModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly ILogger<PersonalDataModel> _logger;
|
|
||||||
|
|
||||||
public PersonalDataModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
ILogger<PersonalDataModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGet()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ResetAuthenticatorModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Reset authenticator key";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>
|
|
||||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
|
||||||
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This process disables 2FA until you verify your authenticator app.
|
|
||||||
If you do not complete your authenticator app configuration you may lose access to your account.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<form id="reset-authenticator-form" method="post">
|
|
||||||
<button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class ResetAuthenticatorModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<ResetAuthenticatorModel> _logger;
|
|
||||||
|
|
||||||
public ResetAuthenticatorModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
ILogger<ResetAuthenticatorModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGet()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _userManager.SetTwoFactorEnabledAsync(user, false);
|
|
||||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
|
|
||||||
|
|
||||||
return RedirectToPage("./EnableAuthenticator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
@page
|
|
||||||
@model SetPasswordModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Set password";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>Set your password</h3>
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<p class="text-info">
|
|
||||||
You do not have a local username/password for this site. Add a local
|
|
||||||
account so you can log in without an external login.
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<form id="set-password-form" method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password."/>
|
|
||||||
<label asp-for="Input.NewPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password."/>
|
|
||||||
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class SetPasswordModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
|
|
||||||
public SetPasswordModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
SignInManager<UserModel> signInManager)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "New password")]
|
|
||||||
public string NewPassword { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Confirm new password")]
|
|
||||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
|
||||||
public string ConfirmPassword { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
|
||||||
|
|
||||||
if (hasPassword)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./ChangePassword");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
|
|
||||||
if (!addPasswordResult.Succeeded)
|
|
||||||
{
|
|
||||||
foreach (var error in addPasswordResult.Errors)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
|
||||||
}
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.RefreshSignInAsync(user);
|
|
||||||
StatusMessage = "Your password has been set.";
|
|
||||||
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ShowRecoveryCodesModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Recovery codes";
|
|
||||||
ViewData["ActivePage"] = "TwoFactorAuthentication";
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<p>
|
|
||||||
<strong>Put these codes in a safe place.</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you lose your device and don't have the recovery codes you will lose access to your account.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
|
|
||||||
{
|
|
||||||
<code class="recovery-code">@Model.RecoveryCodes[row]</code><text> </text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class ShowRecoveryCodesModel : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string[] RecoveryCodes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public IActionResult OnGet()
|
|
||||||
{
|
|
||||||
if (RecoveryCodes == null || RecoveryCodes.Length == 0)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./TwoFactorAuthentication");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
@page
|
|
||||||
@using Microsoft.AspNetCore.Http.Features
|
|
||||||
@model TwoFactorAuthenticationModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Two-factor authentication (2FA)";
|
|
||||||
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="StatusMessage" />
|
|
||||||
<h3>@ViewData["Title"]</h3>
|
|
||||||
@{
|
|
||||||
var consentFeature = HttpContext.Features.Get<ITrackingConsentFeature>();
|
|
||||||
@if (consentFeature?.CanTrack ?? true)
|
|
||||||
{
|
|
||||||
@if (Model.Is2faEnabled)
|
|
||||||
{
|
|
||||||
if (Model.RecoveryCodesLeft == 0)
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<strong>You have no recovery codes left.</strong>
|
|
||||||
<p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (Model.RecoveryCodesLeft == 1)
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<strong>You have 1 recovery code left.</strong>
|
|
||||||
<p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (Model.RecoveryCodesLeft <= 3)
|
|
||||||
{
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
|
|
||||||
<p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Model.IsMachineRemembered)
|
|
||||||
{
|
|
||||||
<form method="post" style="display: inline-block">
|
|
||||||
<button type="submit" class="btn btn-primary">Forget this browser</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
<a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a>
|
|
||||||
<a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a>
|
|
||||||
}
|
|
||||||
|
|
||||||
<h4>Authenticator app</h4>
|
|
||||||
@if (!Model.HasAuthenticator)
|
|
||||||
{
|
|
||||||
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Set up authenticator app</a>
|
|
||||||
<a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<strong>Privacy and cookie policy have not been accepted.</strong>
|
|
||||||
<p>You must accept the policy before you can enable two factor authentication.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
{
|
|
||||||
public class TwoFactorAuthenticationModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly ILogger<TwoFactorAuthenticationModel> _logger;
|
|
||||||
|
|
||||||
public TwoFactorAuthenticationModel(
|
|
||||||
UserManager<UserModel> userManager, SignInManager<UserModel> signInManager, ILogger<TwoFactorAuthenticationModel> logger)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasAuthenticator { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public int RecoveryCodesLeft { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public bool Is2faEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsMachineRemembered { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[TempData]
|
|
||||||
public string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
|
|
||||||
Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
|
|
||||||
IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);
|
|
||||||
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
var user = await _userManager.GetUserAsync(User);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _signInManager.ForgetTwoFactorClientAsync();
|
|
||||||
StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.";
|
|
||||||
return RedirectToPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
@{
|
|
||||||
if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout != null)
|
|
||||||
{
|
|
||||||
Layout = parentLayout.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>Manage your account</h1>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h2>Change your account settings</h2>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<partial name="_ManageNav" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-9">
|
|
||||||
@RenderBody()
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@RenderSection("Scripts", required: false)
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
@inject SignInManager<UserModel> SignInManager
|
|
||||||
@{
|
|
||||||
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
|
|
||||||
}
|
|
||||||
<ul class="nav nav-pills flex-column">
|
|
||||||
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
|
|
||||||
@if (hasExternalLogins)
|
|
||||||
{
|
|
||||||
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
|
|
||||||
}
|
|
||||||
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
|
|
||||||
</ul>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@model string
|
|
||||||
|
|
||||||
@if (!String.IsNullOrEmpty(Model))
|
|
||||||
{
|
|
||||||
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
|
|
||||||
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
@Model
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@using PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
@page
|
|
||||||
@model RegisterModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Register";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
|
|
||||||
<h2>Create a new account.</h2>
|
|
||||||
<hr />
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label asp-for="Input.Email">Email</label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
|
|
||||||
<label asp-for="Input.Password">Password</label>
|
|
||||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
|
|
||||||
<label asp-for="Input.ConfirmPassword">Confirm Password</label>
|
|
||||||
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 col-md-offset-2">
|
|
||||||
<section>
|
|
||||||
<h3>Use another service to register.</h3>
|
|
||||||
<hr />
|
|
||||||
@{
|
|
||||||
if ((Model.ExternalLogins?.Count ?? 0) == 0)
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
|
|
||||||
about setting up this ASP.NET application to support logging in via external services</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
@foreach (var provider in Model.ExternalLogins!)
|
|
||||||
{
|
|
||||||
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
[Authorize]
|
|
||||||
public class RegisterModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly SignInManager<UserModel> _signInManager;
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly IUserStore<UserModel> _userStore;
|
|
||||||
private readonly IUserEmailStore<UserModel> _emailStore;
|
|
||||||
private readonly ILogger<RegisterModel> _logger;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
|
|
||||||
public RegisterModel(
|
|
||||||
UserManager<UserModel> userManager,
|
|
||||||
IUserStore<UserModel> userStore,
|
|
||||||
SignInManager<UserModel> signInManager,
|
|
||||||
ILogger<RegisterModel> logger,
|
|
||||||
IEmailSender emailSender)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_userStore = userStore;
|
|
||||||
_emailStore = GetEmailStore();
|
|
||||||
_signInManager = signInManager;
|
|
||||||
_logger = logger;
|
|
||||||
_emailSender = emailSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string ReturnUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
[Display(Name = "Email")]
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Password")]
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Confirm password")]
|
|
||||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
|
||||||
public string ConfirmPassword { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task OnGetAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
ReturnUrl = returnUrl;
|
|
||||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
|
||||||
{
|
|
||||||
returnUrl ??= Url.Content("~/");
|
|
||||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
var user = CreateUser();
|
|
||||||
|
|
||||||
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
|
|
||||||
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
|
|
||||||
var result = await _userManager.CreateAsync(user, Input.Password);
|
|
||||||
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("User created a new account with password.");
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmail",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
|
|
||||||
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
|
|
||||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
if (_userManager.Options.SignIn.RequireConfirmedAccount)
|
|
||||||
{
|
|
||||||
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
|
||||||
return LocalRedirect(returnUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var error in result.Errors)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got this far, something failed, redisplay form
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserModel CreateUser()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Activator.CreateInstance<UserModel>();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Can't create an instance of '{nameof(UserModel)}'. " +
|
|
||||||
$"Ensure that '{nameof(UserModel)}' is not an abstract class and has a parameterless constructor, or alternatively " +
|
|
||||||
$"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IUserEmailStore<UserModel> GetEmailStore()
|
|
||||||
{
|
|
||||||
if (!_userManager.SupportsUserEmail)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("The default UI requires a user store with email support.");
|
|
||||||
}
|
|
||||||
return (IUserEmailStore<UserModel>)_userStore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
@page
|
|
||||||
@model RegisterConfirmationModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Register confirmation";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
@{
|
|
||||||
if (@Model.DisplayConfirmAccountLink)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
|
|
||||||
Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
Please check your email to confirm your account.
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class RegisterConfirmationModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly IEmailSender _sender;
|
|
||||||
|
|
||||||
public RegisterConfirmationModel(UserManager<UserModel> userManager, IEmailSender sender)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_sender = sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool DisplayConfirmAccountLink { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string EmailConfirmationUrl { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
|
|
||||||
{
|
|
||||||
if (email == null)
|
|
||||||
{
|
|
||||||
return RedirectToPage("/Index");
|
|
||||||
}
|
|
||||||
returnUrl = returnUrl ?? Url.Content("~/");
|
|
||||||
|
|
||||||
var user = await _userManager.FindByEmailAsync(email);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return NotFound($"Unable to load user with email '{email}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Email = email;
|
|
||||||
// Once you add a real email sender, you should remove this code that lets you confirm the account
|
|
||||||
DisplayConfirmAccountLink = true;
|
|
||||||
if (DisplayConfirmAccountLink)
|
|
||||||
{
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
EmailConfirmationUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmail",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ResendEmailConfirmationModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Resend email confirmation";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<h2>Enter your email.</h2>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form method="post">
|
|
||||||
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label asp-for="Input.Email" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Resend</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class ResendEmailConfirmationModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
private readonly IEmailSender _emailSender;
|
|
||||||
|
|
||||||
public ResendEmailConfirmationModel(UserManager<UserModel> userManager, IEmailSender emailSender)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
_emailSender = emailSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.FindByEmailAsync(Input.Email);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var userId = await _userManager.GetUserIdAsync(user);
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
|
||||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
||||||
var callbackUrl = Url.Page(
|
|
||||||
"/Account/ConfirmEmail",
|
|
||||||
pageHandler: null,
|
|
||||||
values: new { userId = userId, code = code },
|
|
||||||
protocol: Request.Scheme);
|
|
||||||
await _emailSender.SendEmailAsync(
|
|
||||||
Input.Email,
|
|
||||||
"Confirm your email",
|
|
||||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
|
||||||
|
|
||||||
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ResetPasswordModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Reset password";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<h2>Reset your password.</h2>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<form method="post">
|
|
||||||
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
|
|
||||||
<input asp-for="Input.Code" type="hidden" />
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
|
||||||
<label asp-for="Input.Email" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Email" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your password." />
|
|
||||||
<label asp-for="Input.Password" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your password." />
|
|
||||||
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
|
|
||||||
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Reset</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
public class ResetPasswordModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly UserManager<UserModel> _userManager;
|
|
||||||
|
|
||||||
public ResetPasswordModel(UserManager<UserModel> userManager)
|
|
||||||
{
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[BindProperty]
|
|
||||||
public InputModel Input { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public class InputModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[DataType(DataType.Password)]
|
|
||||||
[Display(Name = "Confirm password")]
|
|
||||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
|
||||||
public string ConfirmPassword { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[Required]
|
|
||||||
public string Code { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult OnGet(string code = null)
|
|
||||||
{
|
|
||||||
if (code == null)
|
|
||||||
{
|
|
||||||
return BadRequest("A code must be supplied for password reset.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Input = new InputModel
|
|
||||||
{
|
|
||||||
Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
|
|
||||||
};
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userManager.FindByEmailAsync(Input.Email);
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
// Don't reveal that the user does not exist
|
|
||||||
return RedirectToPage("./ResetPasswordConfirmation");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
|
|
||||||
if (result.Succeeded)
|
|
||||||
{
|
|
||||||
return RedirectToPage("./ResetPasswordConfirmation");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var error in result.Errors)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, error.Description);
|
|
||||||
}
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ResetPasswordConfirmationModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Reset password confirmation";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<p>
|
|
||||||
Your password has been reset. Please <a asp-page="./Login">click here to log in</a>.
|
|
||||||
</p>
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class ResetPasswordConfirmationModel : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@model string
|
|
||||||
|
|
||||||
@if (!String.IsNullOrEmpty(Model))
|
|
||||||
{
|
|
||||||
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
|
|
||||||
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
@Model
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
@using PSTW_CentralSystem.Areas.Identity.Pages.Account
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
@page
|
|
||||||
@model ErrorModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Error";
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
|
||||||
|
|
||||||
@if (Model.ShowRequestId)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>Development Mode</h3>
|
|
||||||
<p>
|
|
||||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
|
|
||||||
</p>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Identity.Pages
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
[AllowAnonymous]
|
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
|
||||||
public class ErrorModel : PageModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public string RequestId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
|
|
||||||
/// directly from your code. This API may change or be removed in future releases.
|
|
||||||
/// </summary>
|
|
||||||
public void OnGet()
|
|
||||||
{
|
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
|
||||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
@using Microsoft.AspNetCore.Identity
|
|
||||||
@using PSTW_CentralSystem.Areas.Identity
|
|
||||||
@using PSTW_CentralSystem.Areas.Identity.Pages
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@using PSTW_CentralSystem.Models
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
@{
|
|
||||||
Layout = "/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Controllers.Admin
|
|
||||||
{
|
|
||||||
[Area("Inventory")]
|
|
||||||
[Authorize(Policy = "RoleModulePolicy")]
|
|
||||||
public class InventoryMasterController : Controller
|
|
||||||
{
|
|
||||||
public IActionResult AdminDashboard()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult ItemRegistration()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult ItemMovement()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult ItemRequestMaster()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult ProductRegistration()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult SupplierRegistration()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
public IActionResult ManifacturerRegistration()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult StationRegistration()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
public IActionResult QrMaster()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using PSTW_CentralSystem.Areas.Inventory.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Controllers
|
|
||||||
{
|
|
||||||
[Area("Inventory")]
|
|
||||||
|
|
||||||
[Authorize(Policy = "RoleModulePolicy")]
|
|
||||||
public class ItemMovementController : Controller
|
|
||||||
{
|
|
||||||
// GET: Inventory
|
|
||||||
public ActionResult Index()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
public ActionResult ItemMovementUser()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
public ActionResult QrUser()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult ItemRequest()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult UserDashboard()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpPost("/i/{id}")]
|
|
||||||
public IActionResult ItemRecognization(string id, [FromBody] ItemModel item)
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Controllers
|
|
||||||
{
|
|
||||||
[Area("Inventory")]
|
|
||||||
//[Authorize(Policy = "RoleModulePolicy")]
|
|
||||||
public class MainController : Controller
|
|
||||||
{
|
|
||||||
// GET: Inventory
|
|
||||||
public ActionResult Index()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class InventoryMasterModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int StoreId { get; set; }
|
|
||||||
public int UserId { get; set; }
|
|
||||||
[ForeignKey("UserId")]
|
|
||||||
public virtual UserModel? User { get; set; }
|
|
||||||
[ForeignKey("StoreId")]
|
|
||||||
public virtual StoreModel? Store { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class ItemModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int ItemID { get; set; }
|
|
||||||
public string UniqueID { get; set; } = string.Empty;
|
|
||||||
public required int CompanyId { get; set; }
|
|
||||||
public required int DepartmentId { get; set; }
|
|
||||||
public required int ProductId { get; set; }
|
|
||||||
public required string? SerialNumber { get; set; }
|
|
||||||
public required string? TeamType { get; set; }
|
|
||||||
public required int Quantity { get; set; }
|
|
||||||
public required string Supplier { get; set; }
|
|
||||||
public required DateTime PurchaseDate { get; set; }
|
|
||||||
public required string PONo { get; set; }
|
|
||||||
public required string Currency { get; set; }
|
|
||||||
public required float DefaultPrice { get; set; }
|
|
||||||
public required float CurrencyRate { get; set; }
|
|
||||||
public required float ConvertPrice { get; set; }
|
|
||||||
public string? DONo { get; set; }
|
|
||||||
public DateTime? DODate { get; set; }
|
|
||||||
public required int Warranty { get; set; }
|
|
||||||
public required DateTime EndWDate { get; set; }
|
|
||||||
public string? InvoiceNo { get; set; }
|
|
||||||
public DateTime? InvoiceDate { get; set; }
|
|
||||||
[Comment("1 = In stock; 2 = Item Moving; 3 = Item Out; 4 = Item Broken; 5 = Item Lost; 6 = Item Stolen; 7 = Item Damaged; 8 = Item Discarded; 9 = Item Destroyed; 10 = Item Finished;")]
|
|
||||||
public int ItemStatus { get; set; } = 1;
|
|
||||||
public int? MovementId { get; set; }
|
|
||||||
public string PartNumber { get; set; } = string.Empty;
|
|
||||||
public int CreatedByUserId { get; set; }
|
|
||||||
[ForeignKey("CreatedByUserId")]
|
|
||||||
public virtual UserModel? CreatedBy { get; set; }
|
|
||||||
[ForeignKey("CompanyId")]
|
|
||||||
public virtual CompanyModel? Company { get; set; }
|
|
||||||
[ForeignKey("DepartmentId")]
|
|
||||||
public virtual DepartmentModel? Department { get; set; }
|
|
||||||
[ForeignKey("ProductId")]
|
|
||||||
public virtual ProductModel? Product { get; set; }
|
|
||||||
[ForeignKey("MovementId")]
|
|
||||||
public virtual ItemMovementModel? Movement { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
using PSTW_CentralSystem.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class ItemMovementModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int? ItemId { get; set; }
|
|
||||||
public int? ToStation { get; set; }
|
|
||||||
public int? ToStore { get; set; }
|
|
||||||
public int? ToUser { get; set; }
|
|
||||||
[Comment("Repair, Calibration, Faulty, Ready To Deploy, On Delivery")]
|
|
||||||
public string? ToOther { get; set; }
|
|
||||||
public DateTime? sendDate { get; set; }
|
|
||||||
[Comment("Register, StockIn, Stock Out")]
|
|
||||||
public string? Action { get; set; }
|
|
||||||
public int? Quantity { get; set; }
|
|
||||||
public string? Remark { get; set; }
|
|
||||||
public string? ConsignmentNote { get; set; }
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
public int? LastUser { get; set; }
|
|
||||||
public int? LastStore{ get; set; }
|
|
||||||
public int? LastStation{ get; set; }
|
|
||||||
[Comment("Repair, Calibration, Faulty, Ready To Deploy, On Delivery")]
|
|
||||||
public string? LatestStatus { get; set; }
|
|
||||||
public DateTime? receiveDate { get; set; }
|
|
||||||
public bool MovementComplete { get; set; } = false;
|
|
||||||
//public virtual ItemModel? Item { get; set; }
|
|
||||||
//[ForeignKey("ToStore")]
|
|
||||||
[ForeignKey("ItemId")]
|
|
||||||
public virtual ItemModel? Item { get; set; }
|
|
||||||
[ForeignKey("ToStore")]
|
|
||||||
public virtual StoreModel? NextStore { get; set; }
|
|
||||||
[ForeignKey("ToStation")]
|
|
||||||
public virtual StationModel? NextStation { get; set; }
|
|
||||||
[ForeignKey("ToUser")]
|
|
||||||
public virtual UserModel? NextUser { get; set; }
|
|
||||||
[ForeignKey("LastStore")]
|
|
||||||
public virtual StoreModel? FromStore { get; set; }
|
|
||||||
[ForeignKey("LastStation")]
|
|
||||||
public virtual StationModel? FromStation { get; set; }
|
|
||||||
[ForeignKey("LastUser")]
|
|
||||||
public virtual UserModel? FromUser { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class ManufacturerModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int ManufacturerId { get; set; }
|
|
||||||
public required string ManufacturerName { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using PSTW_CentralSystem.Models;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class ProductModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int ProductId { get; set; }
|
|
||||||
public required string ProductName { get; set; }
|
|
||||||
public required string ProductShortName { get; set; }
|
|
||||||
public required int ManufacturerId { get; set; }
|
|
||||||
public required string Category { get; set; }
|
|
||||||
public required string ModelNo { get; set; }
|
|
||||||
public int? QuantityProduct { get; set; }
|
|
||||||
public required string ImageProduct { get; set; }
|
|
||||||
[ForeignKey("ManufacturerId")]
|
|
||||||
public virtual ManufacturerModel? Manufacturer { get; set; }
|
|
||||||
public virtual ICollection<ItemModel>? Items { get; set; } // Navigation property>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
using PSTW_CentralSystem.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
[Table("request")]
|
|
||||||
public class RequestModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
|
||||||
public int requestID { get; set; }
|
|
||||||
public int ProductId { get; set; }
|
|
||||||
[ForeignKey("ProductId")]
|
|
||||||
public virtual ProductModel? Product { get; set; }
|
|
||||||
public int? StationId { get; set; }
|
|
||||||
[ForeignKey("StationId")]
|
|
||||||
public virtual StationModel? Station { get; set; }
|
|
||||||
public int UserId { get; set; }
|
|
||||||
[ForeignKey("UserId")]
|
|
||||||
public virtual UserModel? User { get; set; }
|
|
||||||
public string? ProductCategory { get; set; }
|
|
||||||
public string? remarkUser { get; set; }
|
|
||||||
public string? remarkMasterInv { get; set; }
|
|
||||||
public string? status { get; set; }
|
|
||||||
public DateTime requestDate { get; set; }
|
|
||||||
public DateTime? approvalDate { get; set; }
|
|
||||||
public int? RequestQuantity { get; set; }
|
|
||||||
public string? Document { get; set; }
|
|
||||||
public int? fromStoreItem { get; set; }
|
|
||||||
[ForeignKey("fromStoreItem")]
|
|
||||||
public virtual StoreModel? Store { get; set; }
|
|
||||||
public int? assignStoreItem { get; set; }
|
|
||||||
[ForeignKey("assignStoreItem")]
|
|
||||||
public virtual StoreModel? Stores { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
using PSTW_CentralSystem.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class StationModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int StationId { get; set; }
|
|
||||||
public int StationPicID { get; set; }
|
|
||||||
public string? StationName { get; set; }
|
|
||||||
public int DepartmentId { get; set; }
|
|
||||||
[ForeignKey("DepartmentId")]
|
|
||||||
public virtual DepartmentModel? Department { get; set; }
|
|
||||||
[ForeignKey("StationPicID")]
|
|
||||||
public virtual UserModel? StationPic { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
using PSTW_CentralSystem.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class StoreModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int CompanyId { get; set; }
|
|
||||||
public string StoreName { get; set; } = string.Empty;
|
|
||||||
[ForeignKey("CompanyId")]
|
|
||||||
public virtual CompanyModel? Company { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.Inventory.Models
|
|
||||||
{
|
|
||||||
public class SupplierModel
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int SupplierId { get; set; }
|
|
||||||
public required string SupplierCompName { get; set; }
|
|
||||||
public required string SupplierAddress { get; set; }
|
|
||||||
[AllowNull]
|
|
||||||
public string? SupplierPIC { get; set; }
|
|
||||||
[AllowNull]
|
|
||||||
public string? SupplierEmail { get; set; }
|
|
||||||
[AllowNull]
|
|
||||||
public string? SupplierPhoneNo { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Dashboard";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
<div class="container" id="invAdmin">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-center">
|
|
||||||
<p><h1 class="display-4">Inventory Admin Dashboard</h1></p>
|
|
||||||
<p v-show="currentUserCompanyDept.departmentName"><h2 class="display-6">Store: {{ currentUserCompanyDept.departmentName }}</h2></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Inventory Report</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div v-if="reportData">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-3">
|
|
||||||
<h4>Statistic</h4>
|
|
||||||
<p>Total Number of Item Registered: {{ reportData.itemCountRegistered }}</p>
|
|
||||||
<p>Total Number of Item Still in Stock: {{ reportData.itemCountStillInStock }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<h4>Item Registered </h4>
|
|
||||||
<p>This Month: {{ reportData.itemCountRegisteredThisMonth }}</p>
|
|
||||||
<p>Last Month: {{ reportData.itemCountRegisteredLastMonth }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<h4>Item Stock Out </h4>
|
|
||||||
<p>This Month: {{ reportData.itemCountStockOutThisMonth }}</p>
|
|
||||||
<p>Last Month: {{ reportData.itemCountStockOutLastMonth }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#invAdmin');
|
|
||||||
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentUser: null,
|
|
||||||
currentUserCompanyDept: {
|
|
||||||
departmentName: null,
|
|
||||||
departmentId: null
|
|
||||||
},
|
|
||||||
reportData: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchUser();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.currentUser = data?.userInfo || null;
|
|
||||||
const companyDeptData = await this.currentUser.department;
|
|
||||||
const userRole = this.currentUser.role;
|
|
||||||
if(userRole == "SuperAdmin" || userRole == "SystemAdmin"){
|
|
||||||
this.currentUserCompanyDept = {departmentId : 0, departmentName : "All"}
|
|
||||||
this.fetchInventoryReport(0);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.currentUserCompanyDept = companyDeptData;
|
|
||||||
this.fetchInventoryReport(companyDeptData.departmentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchInventoryReport(deptId){
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/GetInventoryReport/` + deptId, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.reportData = data;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,207 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Manufactures";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
|
|
||||||
<div id="app">
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<button id="addManufacturerBtn" class="btn btn-success col-md-3 m-1"><i class="fa fa-plus"></i> Add Manufacturer</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div v-if="loading">
|
|
||||||
<div class="spinner-border text-info" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="manufacturerTable" style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal fade" id="addManufacturerModal" tabindex="-1" role="dialog" aria-labelledby="addManufacturerModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="addManufacturerModalLabel">Add Manufacturer</h5>
|
|
||||||
<button type="button" class="closeModal" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form v-on:submit.prevent="addManufacturer">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="manufacturerName">Manufacturer Name:</label>
|
|
||||||
<input type="text" class="form-control" id="manufacturerName" v-model="newManufacturer.manufacturerName" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary closeModal" data-dismiss="modal">Close</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
manufacturer: null,
|
|
||||||
manufacturerDatatable: null,
|
|
||||||
newManufacturer: {
|
|
||||||
manufacturerName: null,
|
|
||||||
},
|
|
||||||
loading: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// Fetch companies, depts, and products from the API
|
|
||||||
this.fetchManufactures();
|
|
||||||
this.initiateTable();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchManufactures() {
|
|
||||||
fetch('/InvMainAPI/ManufacturerList', {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data != null && data.length > 0)
|
|
||||||
{
|
|
||||||
this.manufacturer = data;
|
|
||||||
}
|
|
||||||
if (!this.manufacturerDatatable) {
|
|
||||||
this.initiateTable();
|
|
||||||
} else {
|
|
||||||
this.fillTable(data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
async initiateTable() {
|
|
||||||
this.manufacturerDatatable = $('#manufacturerTable').DataTable({
|
|
||||||
"data": this.manufacturer,
|
|
||||||
"columns": [
|
|
||||||
{ "title": "Manufacturer Name",
|
|
||||||
"data": "manufacturerName",
|
|
||||||
},
|
|
||||||
{ "title": "Delete",
|
|
||||||
"data": "manufacturerName",
|
|
||||||
"render": function (data, type, full, meta) {
|
|
||||||
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${full.manufacturerId}">Delete</button>`;
|
|
||||||
return deleteButton;
|
|
||||||
},
|
|
||||||
"width": '10%',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
self = this;
|
|
||||||
// Attach click event listener to the delete buttons
|
|
||||||
$('#manufacturerTable tbody').on('click', '.delete-btn', function () {
|
|
||||||
const manufacturerId = $(this).data('id'); // Get the manufacturer ID from the button
|
|
||||||
self.deleteManufacturer(manufacturerId); // Call the Vue method
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
fillTable(data){
|
|
||||||
if (!this.manufacturerDatatable) {
|
|
||||||
console.error("DataTable not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.manufacturerDatatable.clear();
|
|
||||||
this.manufacturerDatatable.rows.add(data);
|
|
||||||
this.manufacturerDatatable.draw();
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
addManufacturer() {
|
|
||||||
this.loading = true;
|
|
||||||
const existingManufacturer = this.manufacturer != null ? this.manufacturer.find(m => m.manufacturerName.toLowerCase() === this.newManufacturer.manufacturerName.toLowerCase()) : null;
|
|
||||||
if (existingManufacturer) {
|
|
||||||
alert('Manufacturer already exists');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetch('/InvMainAPI/AddManufacturer', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(this.newManufacturer)
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data != null && data.length > 0)
|
|
||||||
{
|
|
||||||
this.manufacturer = data;
|
|
||||||
}
|
|
||||||
this.fillTable(data);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
$('#preloader').modal('hide');
|
|
||||||
this.newManufacturer.manufacturerName = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async deleteManufacturer(manufacturerId) {
|
|
||||||
if (!confirm("Are you sure you want to delete this manufacturer?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/DeleteManufacturer/${manufacturerId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
alert(result.message);
|
|
||||||
// Remove the row from DataTables
|
|
||||||
this.manufacturerDatatable
|
|
||||||
.row($(`.delete-btn[data-id="${manufacturerId}"]`).closest('tr'))
|
|
||||||
.remove()
|
|
||||||
.draw();
|
|
||||||
} else {
|
|
||||||
alert(result.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Error deleting manufacturer:", error);
|
|
||||||
alert("An error occurred while deleting the manufacturer.");
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
app.mount('#app');
|
|
||||||
|
|
||||||
// Attach a click event listener to elements with the class 'btn-success'.
|
|
||||||
$('#addManufacturerBtn').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('#addManufacturerModal').modal('show');
|
|
||||||
});
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('#addManufacturerModal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,376 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Product Form";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
string userId = ViewBag.UserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
|
|
||||||
<div class="row">
|
|
||||||
<div id="registerProduct" class="card m-1">
|
|
||||||
<div class="row" v-if="addSection == true">
|
|
||||||
<form v-on:submit.prevent="addProduct" data-aos="fade-right" >
|
|
||||||
<div class="container register" data-aos="fade-right">
|
|
||||||
<div class="row" data-aos="fade-right">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="tab-content" id="myTabContent">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="register-heading">REGISTRATION PRODUCT</h3>
|
|
||||||
</div>
|
|
||||||
<div class="row register-form card-body">
|
|
||||||
<div class="col-md-6">
|
|
||||||
|
|
||||||
@* Product Name *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="productName" class="col-sm-3">Product Name:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="productName" name="productName" class="form-control" required v-model="productName">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Product Short Name *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="productName" class="col-sm-3">Product Short Name:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="productShortName" name="productShortName" class="form-control" maxlength="13" v-model="productShortName" required>
|
|
||||||
<p><em><small class="text-danger">* Product short name limited to 13 characters</small></em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Manufacturer *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Manufacturer:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="">
|
|
||||||
<select class="btn btn-primary form-select" v-model="manufacturer" required>
|
|
||||||
<option class="btn-light" value="" selected disabled>Select Manufacturer</option>
|
|
||||||
<option class="btn-light" v-for="(item, index) in manufacturers" :key="item.manufacturerId" :value="item.manufacturerId">{{ item.manufacturerName ?? 'Select Manufacturer' }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Category *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Category:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="">
|
|
||||||
<select class="btn btn-primary form-select" v-model="category" required>
|
|
||||||
<option class="btn-light" value="" selected disabled>Select Category</option>
|
|
||||||
<option class="btn-light" v-for="(item, index) in categories" :key="item" :value="item">{{ item ?? 'Select Category' }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
|
|
||||||
@* Model No Coding *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="modelNo" class="col-sm-3">Model No:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="modelNo" name="modelNo" class="form-control" required v-model="modelNo">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Image Product Coding *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="imageProduct" class="col-sm-3">Image:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="file" id="imageProduct" name="imageProduct" class="form-control" v-on:change="previewImage" accept="image/*" required>
|
|
||||||
<br>
|
|
||||||
<img v-if="imageSrc" :src="imageSrc" alt="Image Preview" class="img-thumbnail" style="width: 200px; margin-top: 10px;" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-9 offset-sm-3">
|
|
||||||
<button type="button" v-on:click="resetForm" class="btn btn-secondary mx-1">Reset</button>
|
|
||||||
<input type="submit" class="btn btn-primary mx-1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="row">
|
|
||||||
<button class="btn btn-danger col-md-3 m-1" v-on:click="addSection = false"><i class="fa fa-minus"></i> Hide Add Product Section</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<button id="addProductBtn" class="btn btn-success col-md-3 m-1" v-show="addSection == false" v-on:click="addSection = true"><i class="fa fa-plus"></i> Show Add Product Section</button>
|
|
||||||
</div>
|
|
||||||
<div class="row table-responsive">
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="productDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#registerProduct');
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
addSection: false,
|
|
||||||
productName: null,
|
|
||||||
manufacturer: '',
|
|
||||||
manufacturers: null,
|
|
||||||
category: '',
|
|
||||||
categories: ["Asset", "Part", "Disposable"],
|
|
||||||
modelNo: null,
|
|
||||||
imageProduct: null,
|
|
||||||
manufactures: [],
|
|
||||||
showOtherManufacturer: false,
|
|
||||||
imageSrc: '',
|
|
||||||
products: null,
|
|
||||||
productDatatable: null,
|
|
||||||
productShortName: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// Fetch companies, depts, and products from the API
|
|
||||||
this.fetchManufactures();
|
|
||||||
this.fetchProducts();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initiateTable() {
|
|
||||||
this.productDatatable = $('#productDatatable').DataTable({
|
|
||||||
"data": this.products,
|
|
||||||
"columns": [
|
|
||||||
{ "title": "Product Name",
|
|
||||||
"data": "productName",
|
|
||||||
},
|
|
||||||
{ "title": "Product Short Name",
|
|
||||||
"data": "productShortName",
|
|
||||||
},
|
|
||||||
{ "title": "Model Number",
|
|
||||||
"data": "modelNo",
|
|
||||||
},
|
|
||||||
{ "title": "Manufacturer",
|
|
||||||
"data": "manufacturer.manufacturerName",
|
|
||||||
},
|
|
||||||
{ "title": "Product Category",
|
|
||||||
"data": "category",
|
|
||||||
},
|
|
||||||
{ "title": "Product Stock",
|
|
||||||
"data": "quantityProduct",
|
|
||||||
},
|
|
||||||
{ "title": "Image",
|
|
||||||
"data": "imageProduct",
|
|
||||||
"render": function (data, type, full, meta) {
|
|
||||||
var image = `<a href="${data}" target="_blank" data-lightbox="image-1">
|
|
||||||
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
|
||||||
</a>`;
|
|
||||||
return image;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Delete",
|
|
||||||
"data": "productId",
|
|
||||||
"render": function (data) {
|
|
||||||
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
|
|
||||||
return deleteButton;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
],
|
|
||||||
responsive:true,
|
|
||||||
})
|
|
||||||
self = this;
|
|
||||||
// Attach click event listener to the delete buttons
|
|
||||||
$('#productDatatable tbody').on('click', '.delete-btn', function () {
|
|
||||||
const productId = $(this).data('id'); // Get the manufacturer ID from the button
|
|
||||||
self.deleteProduct(productId); // Call the Vue method
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
async fetchManufactures() {
|
|
||||||
fetch('/InvMainAPI/ManufacturerList', {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data != null && data.length > 0)
|
|
||||||
{
|
|
||||||
this.manufacturers = data;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async fetchProducts() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/ProductList',{
|
|
||||||
method: 'POST'
|
|
||||||
}); // Call the API
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch products');
|
|
||||||
}
|
|
||||||
this.products = await response.json(); // Store the fetched products
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.initiateTable()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching products:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async addProduct() {
|
|
||||||
// const existingProduct = this.products.find(p => p.modelNo === this.modelNo);
|
|
||||||
// if (existingProduct) {
|
|
||||||
// alert(`Product Error: The model number ${this.modelNo} already exists.`, 'error');
|
|
||||||
// return; // Exit early if the modelNo exists
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Create the payload
|
|
||||||
const formData = {
|
|
||||||
productName: this.productName,
|
|
||||||
productShortName: this.productShortName,
|
|
||||||
manufacturerId: this.manufacturer,
|
|
||||||
category: this.category,
|
|
||||||
modelNo: this.modelNo,
|
|
||||||
imageProduct: this.imageProduct
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// List of required fields
|
|
||||||
const requiredFields = ['productName', 'manufacturer', 'category', 'modelNo', 'imageProduct'];
|
|
||||||
|
|
||||||
// Loop through required fields and check if any are null or empty
|
|
||||||
for (let field of requiredFields) {
|
|
||||||
if (this[field] === null || this[field] === '') {
|
|
||||||
alert('Product Error', `Please fill in required fields: ${field}.`, 'warning');
|
|
||||||
return; // Exit early if validation fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed to send the data as raw JSON string
|
|
||||||
const response = await fetch('/InvMainAPI/AddProduct', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData) // Convert the formData to a JSON string
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.products = await response.json();
|
|
||||||
alert('Success!', 'Product form has been successfully submitted.', 'success');
|
|
||||||
this.fillTable(this.products);
|
|
||||||
this.resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Product Error', `An error occurred: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetForm() {
|
|
||||||
this.productName = null;
|
|
||||||
this.manufacturer = '';
|
|
||||||
this.category = '';
|
|
||||||
this.modelNo = null;
|
|
||||||
this.imageProduct = null;
|
|
||||||
this.imageSrc = '';
|
|
||||||
const fileInput = document.getElementById('imageProduct');
|
|
||||||
if (fileInput) {
|
|
||||||
fileInput.value = ''; // Clear the file input value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Update Select View
|
|
||||||
updateManufacturer(manufacturer) {
|
|
||||||
this.manufacturer = manufacturer;
|
|
||||||
this.showOtherManufacturer = false;
|
|
||||||
},
|
|
||||||
updateCategory(category) {
|
|
||||||
this.category = category;
|
|
||||||
},
|
|
||||||
|
|
||||||
// When User Presses Button Other
|
|
||||||
toggleOtherInput(type) {
|
|
||||||
if (type === 'manufacturer') {
|
|
||||||
this.showOtherManufacturer = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// User Inserting an Image
|
|
||||||
previewImage(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
this.imageSrc = e.target.result; // Show the image preview
|
|
||||||
this.imageProduct = e.target.result.split(',')[1]; // Get Base64 string (remove metadata)
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
} else {
|
|
||||||
this.imageSrc = '';
|
|
||||||
this.imageProduct = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async deleteProduct(productId) {
|
|
||||||
if (!confirm("Are you sure you want to delete this product?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/DeleteProduct/${productId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
alert(result.message);
|
|
||||||
// Remove the row from DataTables
|
|
||||||
this.productDatatable
|
|
||||||
.row($(`.delete-btn[data-id="${productId}"]`).closest('tr'))
|
|
||||||
.remove()
|
|
||||||
.draw();
|
|
||||||
} else {
|
|
||||||
alert(result.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Error deleting product:", error);
|
|
||||||
alert("An error occurred while deleting the product.");
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fillTable(data){
|
|
||||||
if (!this.productDatatable) {
|
|
||||||
console.error("DataTable not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.productDatatable.clear();
|
|
||||||
this.productDatatable.rows.add(data);
|
|
||||||
this.productDatatable.draw();
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,298 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Station";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
|
|
||||||
<div id="registerStation">
|
|
||||||
<form v-on:submit.prevent="addStation" data-aos="fade-right" id="registerStationForm" v-if="registerStationForm">
|
|
||||||
<div class="container register" data-aos="fade-right">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-9 register-rights" data-aos="fade-right">
|
|
||||||
<div class="tab-content" id="myTabContent" data-aos="fade-right">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab" data-aos="fade-right">
|
|
||||||
<h3 class="register-heading">REGISTRATION STATION</h3>
|
|
||||||
<div class="row register-form">
|
|
||||||
<div class="col-md-61">
|
|
||||||
|
|
||||||
@* Station Name *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="stationName" class="col-sm-3">Station Name:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="stationName" name="stationName" class="form-control" required v-model="stationName">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* User ID *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Station User PIC</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div class="dropdown">
|
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedUserName" :disabled="currentUser != null" required v-on:change="updateDepartment()">
|
|
||||||
<option class="btn-light" value="" disabled selected>Select User</option>
|
|
||||||
<option v-for="(technicianUser, index) in users" :key="index" :value="technicianUser.fullname">
|
|
||||||
{{ technicianUser.fullname }}
|
|
||||||
</option>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Department ID *@
|
|
||||||
<!-- Department Dropdown -->
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Department: </label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<!-- Use a span to keep the same space occupied -->
|
|
||||||
<span v-if="selectedDepartment">{{ selectedDepartment }}</span>
|
|
||||||
<span v-else>No department assigned</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-9 offset-sm-3">
|
|
||||||
<button type="button" v-on:click="resetForm" class="btn btn-secondary m-1">Reset</button>
|
|
||||||
<button type="submit" class="btn btn-primary m-1">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<button id="addStationBtn" :class="['btn', 'col-md-3', 'col-lg-3', 'm-1', 'col-12', registerStationForm ? 'btn-danger' : 'btn-success']" v-on:click="registerStationForm = !registerStationForm"><i :class="['fa', registerStationForm ? 'fa-minus' : 'fa-plus']"></i> {{registerStationForm ? 'Hide Add Station' : 'Show Add Station'}}</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="stationDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#registerStation');
|
|
||||||
|
|
||||||
// Attach a click event listener to elements with the class 'btn-success'.
|
|
||||||
$('#addStationBtn').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addStationModal'.
|
|
||||||
$('#registerStationModal').modal('show');
|
|
||||||
});
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addStationModal'.
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
stationName: null,
|
|
||||||
stationUserPIC: null,
|
|
||||||
departmentId : null,
|
|
||||||
selectedUserName : null,
|
|
||||||
selectedDepartment: null,
|
|
||||||
stations: null,
|
|
||||||
currentUser: null,
|
|
||||||
users : null,
|
|
||||||
registerStationForm: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchUsers();
|
|
||||||
this.fetchStations();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchUsers() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/IdentityAPI/GetTechnicianUserInformation/`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.users = data.technicianUsers;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchStations() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/StationList', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.stationDatatable) {
|
|
||||||
this.stationDatatable.clear().destroy();
|
|
||||||
}
|
|
||||||
const stations = await response.json();
|
|
||||||
this.stations = stations;
|
|
||||||
|
|
||||||
if ($.fn.dataTable.isDataTable('#stationDatatable')) {
|
|
||||||
$('#stationDatatable').DataTable().clear().destroy();
|
|
||||||
}
|
|
||||||
this.initiateTable();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching stations:', error);
|
|
||||||
this.errorMessage = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async addStation() {
|
|
||||||
$('#loadingModal').modal('show');
|
|
||||||
// Create the payload
|
|
||||||
const formData = {
|
|
||||||
StationName: this.stationName,
|
|
||||||
StationPicID: this.stationUserPIC,
|
|
||||||
DepartmentId: this.departmentId,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// List of required fields
|
|
||||||
const requiredFields = ['stationName', 'stationUserPIC', 'departmentId'];
|
|
||||||
|
|
||||||
// Loop through required fields and check if any are null or empty
|
|
||||||
for (let field of requiredFields) {
|
|
||||||
if (this[field] === null || this[field] === '') {
|
|
||||||
alert('Station Error', `Please fill in required fields: ${field}.`, 'warning');
|
|
||||||
return; // Exit early if validation fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed to send the data as raw JSON string
|
|
||||||
const response = await fetch('/InvMainAPI/AddStation', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData) // Convert the formData to a JSON string
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.fetchStations();
|
|
||||||
$('#loadingModal').modal('hide');
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.fetchStations();
|
|
||||||
this.resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Station Error', `An error occurred: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
$('#loadingModal').on('shown.bs.modal', resolve);
|
|
||||||
});
|
|
||||||
$('#loadingModal').modal('hide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetForm() {
|
|
||||||
this.selectedUserName = null;
|
|
||||||
this.selectedDepartment = null;
|
|
||||||
this.stationName = null;
|
|
||||||
this.stationUserPIC = null;
|
|
||||||
this.departmentId = '';
|
|
||||||
},
|
|
||||||
initiateTable() {
|
|
||||||
self = this;
|
|
||||||
this.stationDatatable = $('#stationDatatable').DataTable({
|
|
||||||
"data": this.stations,
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"title": "Station Name",
|
|
||||||
"data": "stationName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Station User PIC",
|
|
||||||
"data": "stationPicID",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Department ID",
|
|
||||||
"data": "departmentId",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Delete",
|
|
||||||
"data": "stationId",
|
|
||||||
"render": function (data) {
|
|
||||||
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
|
|
||||||
return deleteButton;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Attach click event listener to the delete buttons
|
|
||||||
$('#stationDatatable tbody').on('click', '.delete-btn', function () {
|
|
||||||
const stationId = $(this).data('id');
|
|
||||||
self.deleteStation(stationId);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
updateDepartment() {
|
|
||||||
// Find the selected user by their full name
|
|
||||||
const selectedUser = this.users.find(user => user.fullname === this.selectedUserName);
|
|
||||||
if (selectedUser) {
|
|
||||||
this.stationUserPIC = selectedUser.id;
|
|
||||||
this.departmentId = selectedUser.department.departmentId;
|
|
||||||
this.selectedDepartment = selectedUser.department.departmentName; // Set department name
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async deleteStation(stationId) {
|
|
||||||
if (!confirm("Are you sure you want to delete this station?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/DeleteStation/${stationId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ stationId })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.fetchStations();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting stations:', error);
|
|
||||||
this.errorMessage = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "User Form";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
|
|
||||||
<div id="registerSupplier">
|
|
||||||
<form v-on:submit.prevent="addSupplier" data-aos="fade-right" id="registerSupplierForm" v-if="registerSupplierForm">
|
|
||||||
<div class="container register" data-aos="fade-right">
|
|
||||||
<div class="row">
|
|
||||||
@*Right Side*@
|
|
||||||
<div class="col-md-9 register-rights" data-aos="fade-right">
|
|
||||||
<div class="tab-content" id="myTabContent" data-aos="fade-right">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab" data-aos="fade-right">
|
|
||||||
<h3 class="register-heading">REGISTRATION SUPPLIER</h3>
|
|
||||||
<div class="row register-form">
|
|
||||||
<div class="col-md-61">
|
|
||||||
|
|
||||||
@* Supplier Name *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="supplierCompName" class="col-sm-3">Supplier Company Name:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="supplierCompName" name="supplierCompName" class="form-control" required v-model="supplierCompName">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Supplier Gender *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Supplier Address:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="dropdown">
|
|
||||||
<textarea type="text" id="supplierAddress" name="supplierAddress" class="form-control" required v-model="supplierAddress"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Supplier PIC *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Supplier PIC:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="email" id="supplierPIC" name="supplierPIC" class="form-control" v-model="supplierPIC">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Supplier Email *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Supplier Email:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="email" id="supplierEmail" name="supplierEmail" class="form-control" v-model="supplierEmail">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Supplier Number Phone *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-3">Supplier Phone Number:</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="tel" id="supplierPhoneNo" name="supplierPhoneNo" class="form-control" v-model="supplierPhoneNo">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-9 offset-sm-3">
|
|
||||||
<button type="button" v-on:click="resetForm" class="btn btn-secondary m-1">Reset</button>
|
|
||||||
<button type="submit" class="btn btn-primary m-1">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<button id="addSupplierBtn" :class="['btn', 'col-md-3', 'col-lg-3', 'm-1', 'col-12', registerSupplierForm ? 'btn-danger' : 'btn-success']" v-on:click="registerSupplierForm = !registerSupplierForm"><i :class="['fa', registerSupplierForm ? 'fa-minus' : 'fa-plus']"></i> {{registerSupplierForm ? 'Hide Add Supplier' : 'Show Add Supplier'}}</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="supplierDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#registerSupplier');
|
|
||||||
|
|
||||||
// Attach a click event listener to elements with the class 'btn-success'.
|
|
||||||
$('#addSupplierBtn').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('#registerSupplierModal').modal('show');
|
|
||||||
});
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
supplierCompName : null,
|
|
||||||
supplierEmail : null,
|
|
||||||
supplierAddress : null,
|
|
||||||
supplierPhoneNo : null,
|
|
||||||
supplierPIC : null,
|
|
||||||
suppliers: null,
|
|
||||||
supplierDatatable: null,
|
|
||||||
gender: ["Male", "Female", "Helicopter"],
|
|
||||||
registerSupplierForm: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
this.fetchSuppliers();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchSuppliers() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/SupplierList', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.supplierDatatable){
|
|
||||||
this.supplierDatatable.clear().destroy();
|
|
||||||
}
|
|
||||||
const suppliers = await response.json();
|
|
||||||
this.suppliers = suppliers;
|
|
||||||
|
|
||||||
if (this.itemDatatable) {
|
|
||||||
this.itemDatatable.clear().destroy();
|
|
||||||
}
|
|
||||||
this.initiateTable();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching suppliers:', error);
|
|
||||||
this.errorMessage = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async addSupplier() {
|
|
||||||
$('#loadingModal').modal('show');
|
|
||||||
// Create the payload
|
|
||||||
const formData = {
|
|
||||||
supplierCompName: this.supplierCompName,
|
|
||||||
supplierAddress: this.supplierAddress,
|
|
||||||
supplierPIC: this.supplierPIC,
|
|
||||||
supplierEmail: this.supplierEmail,
|
|
||||||
supplierPhoneNo: this.supplierPhoneNo,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
// List of required fields
|
|
||||||
const requiredFields = ['supplierCompName', 'supplierAddress'];
|
|
||||||
|
|
||||||
// Loop through required fields and check if any are null or empty
|
|
||||||
for (let field of requiredFields) {
|
|
||||||
if (this[field] === null || this[field] === '') {
|
|
||||||
alert('Product Error', `Please fill in required fields: ${field}.`, 'warning');
|
|
||||||
return; // Exit early if validation fails
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proceed to send the data as raw JSON string
|
|
||||||
const response = await fetch('/InvMainAPI/AddSupplier', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData) // Convert the formData to a JSON string
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
alert('Success!', 'Supplier form has been successfully submitted.', 'success');
|
|
||||||
this.fetchSuppliers();
|
|
||||||
this.resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Product Error', `An error occurred: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
$('#loadingModal').on('shown.bs.modal', resolve);
|
|
||||||
});
|
|
||||||
$('#loadingModal').modal('hide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetForm() {
|
|
||||||
this.supplierCompName = null;
|
|
||||||
this.supplierAddress = null;
|
|
||||||
this.supplierEmail = null;
|
|
||||||
this.supplierPhoneNo = null;
|
|
||||||
this.supplierPIC = null;
|
|
||||||
},
|
|
||||||
initiateTable() {
|
|
||||||
self = this;
|
|
||||||
this.supplierDatatable = $('#supplierDatatable').DataTable({
|
|
||||||
"data": this.suppliers,
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"title": "Supplier Company Name",
|
|
||||||
"data": "supplierCompName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Supplier Address",
|
|
||||||
"data": "supplierAddress",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Company PIC",
|
|
||||||
"data": "supplierPIC",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Supplier Email",
|
|
||||||
"data": "supplierEmail",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Supplier Phone No",
|
|
||||||
"data": "supplierPhoneNo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Delete",
|
|
||||||
"data": "supplierId",
|
|
||||||
"render": function (data) {
|
|
||||||
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
|
|
||||||
return deleteButton;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Attach click event listener to the delete buttons
|
|
||||||
$('#supplierDatatable tbody').on('click', '.delete-btn', function () {
|
|
||||||
const supplierId = $(this).data('id');
|
|
||||||
self.deleteSupplier(supplierId);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
async deleteSupplier(supplierId) {
|
|
||||||
if (!confirm("Are you sure you want to delete this supplier?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/DeleteSupplier/${supplierId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ supplierId })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error('Error response:', errorData);
|
|
||||||
this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fetchSuppliers();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error deleting supplier:', error);
|
|
||||||
this.errorMessage = 'Error: ' + error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,923 +0,0 @@
|
|||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Item Movement";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
<style>
|
|
||||||
.text-true {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-false {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-primary {
|
|
||||||
color: blue; /* Warna asal untuk 'Receive' */
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-warning {
|
|
||||||
color: orange; /* Warna oren untuk 'Return' */
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-success {
|
|
||||||
color: greenyellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ms-auto {
|
|
||||||
margin-left: auto !important; /* Push Complete/Incomplete to right */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
|
|
||||||
|
|
||||||
<div id="ItemMovement" class="row">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:150px;">Sort by:</h2>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting">
|
|
||||||
<option value="all">All</option>
|
|
||||||
<option value="item">Item</option>
|
|
||||||
<option value="station">Station</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-3" v-if="sortBy === 'item'">
|
|
||||||
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Item:</h4>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<input type="text" class="form-control" v-model="searchQuery" placeholder="Search by item code...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-3" v-if="sortBy === 'station'">
|
|
||||||
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<input type="text" class="form-control" v-model="searchStation" placeholder="Search by station name...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="sortBy === 'all'">
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Pending Item Movement</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementNotCompleteDatatable"
|
|
||||||
style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Complete Item Movement</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable"
|
|
||||||
style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
|
|
||||||
<div v-if="sortBy === 'item'">
|
|
||||||
<div v-for="(group, itemId) in filteredItems" :key="itemId" class="row card">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h2>Item : {{ group.uniqueID }}</h2>
|
|
||||||
<button class="btn btn-light" v-on:click="toggleCategory(itemId)">
|
|
||||||
<i :class="categoryVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Details
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hide all details unless button is clicked -->
|
|
||||||
<div v-show="categoryVisible[itemId]" class="card-body">
|
|
||||||
<div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row">
|
|
||||||
|
|
||||||
<div v-if="index === 0" class="row">
|
|
||||||
<strong>Latest Movement</strong>
|
|
||||||
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
|
||||||
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
|
||||||
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
|
||||||
|
|
||||||
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- Send Date -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receive Date -->
|
|
||||||
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.action }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- More Details Button -->
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
|
|
||||||
More Details
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Completion Status -->
|
|
||||||
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
|
||||||
class="text-nowrap ms-3">
|
|
||||||
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render Start Icon -->
|
|
||||||
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<p><strong>Start</strong></p>
|
|
||||||
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
|
|
||||||
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
|
|
||||||
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<p></p>
|
|
||||||
<i class="fas fa-arrow-right fa-2x"></i>
|
|
||||||
<p>{{ movement.latestStatus || movement.toOther }}</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
|
|
||||||
Remark
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
|
|
||||||
Consignment Note
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render End Icon -->
|
|
||||||
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-user fa-2x"></i>
|
|
||||||
<p><strong>End</strong></p>
|
|
||||||
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
|
|
||||||
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
|
|
||||||
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)">
|
|
||||||
<i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-show="historyVisible[itemId]" class="history-row">
|
|
||||||
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
|
|
||||||
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
|
||||||
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
|
||||||
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
|
||||||
|
|
||||||
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- Send Date -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receive Date -->
|
|
||||||
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.action }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- More Details Button -->
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
|
|
||||||
More Details
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Completion Status -->
|
|
||||||
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
|
||||||
class="text-nowrap ms-3">
|
|
||||||
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render Start Icon -->
|
|
||||||
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<p><strong>Start</strong></p>
|
|
||||||
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
|
|
||||||
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
|
|
||||||
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<p></p>
|
|
||||||
<i class="fas fa-arrow-right fa-2x"></i>
|
|
||||||
<p>{{ movement.latestStatus || movement.toOther }}</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
|
|
||||||
Remark
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
|
|
||||||
Consignment Note
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render End Icon -->
|
|
||||||
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-user fa-2x"></i>
|
|
||||||
<p><strong>End</strong></p>
|
|
||||||
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
|
|
||||||
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
|
|
||||||
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
|
|
||||||
<div v-if="sortBy === 'station'">
|
|
||||||
<div v-for="(items, station) in filteredStation" :key="stationName"
|
|
||||||
:class="{'bg-light-gray': station === 'Unassign Station', 'bg-white': station !== 'Unassign Station'}" class="station-category card mt-3">
|
|
||||||
<!-- Station Header -->
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h3>{{ station }}</h3>
|
|
||||||
<button class="btn btn-light" v-on:click="toggleCategory(station)">
|
|
||||||
<i :class="categoryVisible[station] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Items
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Show Items Under Each Station -->
|
|
||||||
<div v-show="categoryVisible[station]" class="card-body">
|
|
||||||
<div v-for="(group, itemId) in items" :key="itemId" class="row card">
|
|
||||||
<!-- Item Header -->
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h2>Item : {{ group.uniqueID }}</h2>
|
|
||||||
<button class="btn btn-light" v-on:click="toggleCategory(itemId)">
|
|
||||||
<i :class="categoryVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Details
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Show Movements for Each Item -->
|
|
||||||
<div v-show="categoryVisible[itemId]" class="card-body">
|
|
||||||
<div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row">
|
|
||||||
<div v-if="index === 0" class="row">
|
|
||||||
<strong>Latest Movement</strong>
|
|
||||||
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
|
||||||
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
|
||||||
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
|
||||||
|
|
||||||
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- Send Date -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receive Date -->
|
|
||||||
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.action }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- More Details Button -->
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
|
|
||||||
More Details
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Completion Status -->
|
|
||||||
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
|
||||||
class="text-nowrap ms-3">
|
|
||||||
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<p><strong>Start</strong></p>
|
|
||||||
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
|
|
||||||
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
|
|
||||||
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<i class="fas fa-arrow-right fa-2x"></i>
|
|
||||||
<p>{{ movement.latestStatus || movement.toOther }}</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
|
|
||||||
Remark
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
|
|
||||||
Consignment Note
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-user fa-2x"></i>
|
|
||||||
<p><strong>End</strong></p>
|
|
||||||
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
|
|
||||||
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
|
|
||||||
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Single View History Button -->
|
|
||||||
<button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)">
|
|
||||||
<i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-show="historyVisible[itemId]" class="history-row">
|
|
||||||
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
|
|
||||||
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
|
||||||
<!-- Movement Type -->
|
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
|
||||||
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
|
||||||
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
|
||||||
|
|
||||||
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
|
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- Send Date -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receive Date -->
|
|
||||||
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
|
|
||||||
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
|
|
||||||
<span class="fixed-value text-truncate">{{ movement.action }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
|
|
||||||
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
|
|
||||||
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- More Details Button -->
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
|
|
||||||
More Details
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Completion Status -->
|
|
||||||
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
|
||||||
class="text-nowrap ms-3">
|
|
||||||
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Details Section (Hidden by Default) -->
|
|
||||||
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render Start Icon -->
|
|
||||||
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<p><strong>Start</strong></p>
|
|
||||||
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
|
|
||||||
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
|
|
||||||
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<p></p>
|
|
||||||
<i class="fas fa-arrow-right fa-2x"></i>
|
|
||||||
<p>{{ movement.latestStatus || movement.toOther }}</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
|
|
||||||
Remark
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
|
|
||||||
Consignment Note
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-center">
|
|
||||||
<!-- Conditionally render End Icon -->
|
|
||||||
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
|
|
||||||
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
|
|
||||||
<i v-else class="fas fa-user fa-2x"></i>
|
|
||||||
<p><strong>End</strong></p>
|
|
||||||
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
|
|
||||||
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
|
|
||||||
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--------------------------------------------REMARK & CONSIGNMENT NOTE CATEGORY---------------------------------------------------------------------->
|
|
||||||
<div class="modal fade" id="remarkModal" tabindex="-1" aria-labelledby="remarkModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="remarkModalLabel">Remark</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" id="remarkContent">
|
|
||||||
<!-- Remark Content Here -->
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal fade" id="consignmentModal" tabindex="-1" aria-labelledby="consignmentModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-xl">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="consignmentModalLabel">Consignment Note</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-center">
|
|
||||||
<img v-if="/\.(jpeg|jpg|png|gif)$/i.test(consignmentNoteUrl)" :src="consignmentNoteUrl" class="img-fluid" alt="Consignment Note Image">
|
|
||||||
<iframe v-else-if="/\.pdf$/i.test(consignmentNoteUrl)" :src="consignmentNoteUrl" style="width:100%; height: 80vh;"></iframe>
|
|
||||||
<a v-else class="btn btn-primary">There's no Folder or Picture</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#ItemMovement');
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
itemMovements: [],
|
|
||||||
itemMovementCompleteDatatable: null,
|
|
||||||
stationDatatable: null,
|
|
||||||
itemMovementNotCompleteDatatable: null,
|
|
||||||
searchQuery: "",
|
|
||||||
searchStation: "",
|
|
||||||
sortBy: "all",
|
|
||||||
historyVisible: {},
|
|
||||||
detailsVisible: {},
|
|
||||||
categoryVisible: {},
|
|
||||||
consignmentNoteUrl: "",
|
|
||||||
stationName: "",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
processedGroupedItems() {
|
|
||||||
let grouped = this.itemMovements.reduce((acc, movement) => {
|
|
||||||
if (!acc[movement.itemId]) {
|
|
||||||
acc[movement.itemId] = {
|
|
||||||
uniqueID: movement.uniqueID,
|
|
||||||
movements: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
acc[movement.itemId].movements.push(movement);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let filteredGrouped = {};
|
|
||||||
|
|
||||||
for (let itemId in grouped) {
|
|
||||||
let movements = grouped[itemId].movements
|
|
||||||
.sort((a, b) => b.id - a.id); // Newest to oldest
|
|
||||||
|
|
||||||
let stopIndex = movements.findIndex(m =>
|
|
||||||
m.toOther === 'Return' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
let nextIndex = movements.findIndex(m =>
|
|
||||||
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stopIndex !== -1) {
|
|
||||||
movements = movements.slice(0, stopIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextIndex !== -1) {
|
|
||||||
movements = movements.slice(0, nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (movements.length > 0) {
|
|
||||||
filteredGrouped[itemId] = {
|
|
||||||
uniqueID: grouped[itemId].uniqueID,
|
|
||||||
movements: movements,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredGrouped;
|
|
||||||
},
|
|
||||||
|
|
||||||
groupedByStation() {
|
|
||||||
let groupedByItem = this.itemMovements.reduce((acc, movement) => {
|
|
||||||
if (!acc[movement.uniqueID]) {
|
|
||||||
acc[movement.uniqueID] = {
|
|
||||||
uniqueID: movement.uniqueID,
|
|
||||||
movements: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
acc[movement.uniqueID].movements.push(movement);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let groupedByStation = {};
|
|
||||||
|
|
||||||
Object.keys(groupedByItem).forEach(itemId => {
|
|
||||||
let movements = groupedByItem[itemId].movements
|
|
||||||
.sort((a, b) => b.id - a.id); // Newest → Oldest
|
|
||||||
|
|
||||||
// Find first occurrence of 'Return' complete
|
|
||||||
let stopIndex = movements.findIndex(m =>
|
|
||||||
m.toOther === 'Return' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
let nextIndex = movements.findIndex(m =>
|
|
||||||
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stopIndex !== -1) {
|
|
||||||
movements = movements.slice(0, stopIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextIndex !== -1) {
|
|
||||||
movements = movements.slice(0, nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (movements.length > 0) {
|
|
||||||
let latestMovement = movements[0];
|
|
||||||
let station = latestMovement.lastStationName || latestMovement.toStationName || "Self Assigned";
|
|
||||||
|
|
||||||
if (!groupedByStation[station]) {
|
|
||||||
groupedByStation[station] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
groupedByStation[station][itemId] = { uniqueID: itemId, movements };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4️⃣ **Sort stations & move 'Unassign Station' to last**
|
|
||||||
let sortedKeys = Object.keys(groupedByStation).sort((a, b) => {
|
|
||||||
if (a === "Unassign Station") return 1;
|
|
||||||
if (b === "Unassign Station") return -1;
|
|
||||||
return a.localeCompare(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
let sortedGrouped = {};
|
|
||||||
sortedKeys.forEach(key => {
|
|
||||||
sortedGrouped[key] = groupedByStation[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortedGrouped;
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredItems() {
|
|
||||||
if (!this.searchQuery.trim()) {
|
|
||||||
return this.processedGroupedItems;
|
|
||||||
}
|
|
||||||
const searchLower = this.searchQuery.toLowerCase();
|
|
||||||
let grouped = this.processedGroupedItems;
|
|
||||||
let filtered = {};
|
|
||||||
|
|
||||||
Object.keys(grouped).forEach(item => {
|
|
||||||
if (item.toLowerCase().includes(searchLower)) {
|
|
||||||
if (grouped[item] > 0) {
|
|
||||||
filtered[item] = grouped[item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return filtered;
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredStation() {
|
|
||||||
if (!this.searchStation) {
|
|
||||||
return this.groupedByStation;
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchQuery = this.searchStation.toLowerCase();
|
|
||||||
let grouped = this.groupedByStation;
|
|
||||||
let filtered = {};
|
|
||||||
|
|
||||||
Object.keys(grouped).forEach(station => {
|
|
||||||
if (station.toLowerCase().includes(searchQuery)) {
|
|
||||||
filtered[station] = grouped[station];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchItemMovement();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
remark(remark) {
|
|
||||||
document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
|
|
||||||
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
|
|
||||||
modal.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
consignmentNote(consignmentNote) {
|
|
||||||
if (!consignmentNote) {
|
|
||||||
this.consignmentNoteUrl = "No consignment note available.";
|
|
||||||
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pastikan URL betul
|
|
||||||
this.consignmentNoteUrl = consignmentNote;
|
|
||||||
|
|
||||||
// Tunggu Vue update sebelum buka modal
|
|
||||||
this.$nextTick(() => {
|
|
||||||
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchItemMovement() {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/InvMainAPI/ItemMovementUser", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to fetch item movement");
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
this.itemMovements = data.map((movement) => ({
|
|
||||||
...movement,
|
|
||||||
showDetails: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.renderTables();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching item:", error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSorting() {
|
|
||||||
this.renderTables();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTables() {
|
|
||||||
if (this.sortBy === "all") {
|
|
||||||
this.initAllTables();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
initAllTables() {
|
|
||||||
if (this.itemMovementNotCompleteDatatable) {
|
|
||||||
this.itemMovementNotCompleteDatatable.destroy();
|
|
||||||
}
|
|
||||||
if (this.itemMovementCompleteDatatable) {
|
|
||||||
this.itemMovementCompleteDatatable.destroy();
|
|
||||||
}
|
|
||||||
if (this.stationDatatable) {
|
|
||||||
this.stationDatatable.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get latest movement per uniqueID after filtering
|
|
||||||
function getLatestMovements(data) {
|
|
||||||
let latestMovements = {};
|
|
||||||
data.forEach(movement => {
|
|
||||||
let id = movement.uniqueID;
|
|
||||||
if (!latestMovements[id] || latestMovements[id].id < movement.id) {
|
|
||||||
latestMovements[id] = movement;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Object.values(latestMovements);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter movements based on conditions
|
|
||||||
function filterMovements(movements) {
|
|
||||||
let stopIndex = movements.findIndex(m =>
|
|
||||||
m.toOther === 'Return' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
let nextIndex = movements.findIndex(m =>
|
|
||||||
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stopIndex !== -1) {
|
|
||||||
movements = movements.slice(0, stopIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextIndex !== -1) {
|
|
||||||
movements = movements.slice(0, nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return movements;
|
|
||||||
}
|
|
||||||
|
|
||||||
let latestMovements = getLatestMovements(this.itemMovements);
|
|
||||||
|
|
||||||
let notCompleteData = [];
|
|
||||||
let completeData = [];
|
|
||||||
|
|
||||||
latestMovements.forEach(movement => {
|
|
||||||
let filteredMovements = filterMovements([movement]);
|
|
||||||
|
|
||||||
if (filteredMovements.length > 0) {
|
|
||||||
if (movement.movementComplete == 0) {
|
|
||||||
notCompleteData.push(movement);
|
|
||||||
} else if (movement.movementComplete == 1) {
|
|
||||||
completeData.push(movement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table 1: Not Complete Movements
|
|
||||||
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
|
|
||||||
data: notCompleteData,
|
|
||||||
columns: [
|
|
||||||
{ title: "Unique Id", data: "id" },
|
|
||||||
{ title: "Product Name", data: "productName" },
|
|
||||||
{ title: "Product Code", data: "uniqueID" },
|
|
||||||
{ title: "Action", data: "action" },
|
|
||||||
{ title: "Send Date", data: "sendDate" },
|
|
||||||
{ title: "Start Status", data: "toOther" },
|
|
||||||
{ title: "From User", data: "toUserName" },
|
|
||||||
{ title: "Last User", data: "lastUserName" },
|
|
||||||
{ title: "From Station", data: "toStationName" },
|
|
||||||
{ title: "From Store", data: "toStoreName" },
|
|
||||||
{ title: "Quantity", data: "quantity" },
|
|
||||||
{ title: "Note", data: "consignmentNote", render: renderFile },
|
|
||||||
{ title: "Remark", data: "remark" },
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table 2: Completed Movements
|
|
||||||
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
|
|
||||||
data: completeData,
|
|
||||||
columns: [
|
|
||||||
{ title: "Unique Id", data: "id" },
|
|
||||||
{ title: "Product Name", data: "productName" },
|
|
||||||
{ title: "Product Code", data: "uniqueID" },
|
|
||||||
{ title: "Send Date", data: "sendDate" },
|
|
||||||
{ title: "Receive Date", data: "receiveDate" },
|
|
||||||
{ title: "Action", data: "action" },
|
|
||||||
{ title: "Start Status", data: "toOther" },
|
|
||||||
{ title: "Latest Status", data: "latestStatus" },
|
|
||||||
{ title: "From User", data: "toUserName" },
|
|
||||||
{ title: "Last User", data: "lastUserName" },
|
|
||||||
{ title: "From Station", data: "toStationName" },
|
|
||||||
{ title: "Last Station", data: "lastStationName" },
|
|
||||||
{ title: "From Store", data: "toStoreName" },
|
|
||||||
{ title: "Last Store", data: "lastStoreName" },
|
|
||||||
{ title: "Qty", data: "quantity" },
|
|
||||||
{ title: "Note", data: "consignmentNote", render: renderFile },
|
|
||||||
{ title: "Remark", data: "remark" },
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to render file (image/PDF)
|
|
||||||
function renderFile(data, type, full, meta) {
|
|
||||||
if (!data) {
|
|
||||||
return "No Document";
|
|
||||||
}
|
|
||||||
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
|
|
||||||
var isPdf = /\.pdf$/i.test(data);
|
|
||||||
if (isImage) {
|
|
||||||
return `<a href="${data}" target="_blank" data-lightbox="image-1">
|
|
||||||
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
|
||||||
</a>`;
|
|
||||||
} else if (isPdf) {
|
|
||||||
return `<a href="${data}" target="_blank">
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
|
|
||||||
alt="PDF Document" class="img-thumbnail"
|
|
||||||
style="width: 50px; height: 50px;" />
|
|
||||||
<br>View PDF
|
|
||||||
</a>`;
|
|
||||||
} else {
|
|
||||||
return `<a href="${data}" target="_blank">Download File</a>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
toggleCategory(itemId) {
|
|
||||||
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
|
|
||||||
|
|
||||||
this.detailsVisible = {};
|
|
||||||
this.historyVisible = {};
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleHistory(itemId) {
|
|
||||||
// Jika item yang ditekan sudah terbuka, tutup
|
|
||||||
if (this.historyVisible[itemId]) {
|
|
||||||
this.historyVisible[itemId] = false;
|
|
||||||
} else {
|
|
||||||
// Tutup semua history lain dahulu
|
|
||||||
this.historyVisible = {};
|
|
||||||
|
|
||||||
// Buka hanya item yang ditekan
|
|
||||||
this.historyVisible[itemId] = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleDetails(movementId) {
|
|
||||||
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,681 +0,0 @@
|
|||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Product Request";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
|
|
||||||
<style>
|
|
||||||
.dropdown {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle-box input {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-btn {
|
|
||||||
border: none;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content option {
|
|
||||||
padding: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content option:hover {
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body img {
|
|
||||||
max-width: 100%; /* Ensure it fits the modal */
|
|
||||||
max-height: 150px; /* Adjust max height */
|
|
||||||
display: block;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<div id="requestProduct" class="row">
|
|
||||||
|
|
||||||
<!-- Document Preview Modal -->
|
|
||||||
<div id="documentModal" class="modal fade" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Document Preview</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-center">
|
|
||||||
<img id="documentPreview" src="" alt="Document" class="img-fluid" style="max-height: 80vh;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Pending Request</h2>
|
|
||||||
<button id="addRequestBtn" class="btn btn-success col-md-3 col-lg-3 m-1 col-12"><i class="fa fa-plus"></i> Add Request</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
@* <div v-if="loading">
|
|
||||||
<div class="spinner-border text-info" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div> *@
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="requestDatatable" style=" width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Complete Request</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
@* <div v-if="loading">
|
|
||||||
<div class="spinner-border text-info" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div> *@
|
|
||||||
<table class="table table-bordered table-hover table-striped no-wrap" id="settledrequestDatatable" style=" width:100%;border-style: solid; border-width: 1px"></table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal fade" id="requestModal" tabindex="-1" role="dialog" aria-labelledby="addRequestModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="addRequestModalLabel">Add Request</h5>
|
|
||||||
<button type="button" class="closeModal" data-dismiss="modal" aria-label="Close" v-on:click="showRequestModal=false">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<form v-on:submit.prevent="addRequest" data-aos="fade-right">
|
|
||||||
<div class=" register" data-aos="fade-right">
|
|
||||||
<div class="row" data-aos="fade-right">
|
|
||||||
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="tab-content" id="myTabContent">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
|
||||||
<h3 class="register-heading">PRODUCT REQUEST</h3>
|
|
||||||
<div class="row register-form">
|
|
||||||
<div class="col-md-13">
|
|
||||||
|
|
||||||
<!-- Product Name -->
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-2 col-form-label">Product</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div class="dropdown" v-click-outside="closeDropdown">
|
|
||||||
<!-- Button + Input dalam satu box -->
|
|
||||||
<div class="dropdown-toggle-box" v-on:click="dropdownOpen = !dropdownOpen">
|
|
||||||
<input type="text" class="form-control" v-model="searchQuery"
|
|
||||||
placeholder="Search product..." v-on:focus="dropdownOpen = true" v-on:click.stop />
|
|
||||||
<button type="button" class="btn btn-primary dropdown-btn" v-on:click.stop="dropdownOpen = !dropdownOpen">
|
|
||||||
▼
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dropdown list -->
|
|
||||||
<div v-if="dropdownOpen" class="dropdown-content" v-on:click.stop>
|
|
||||||
<div v-for="(item, index) in filteredProducts"
|
|
||||||
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectProduct(item)">
|
|
||||||
{{ item.productName + ' (' + item.modelNo + ')' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Category Dropdown -->
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-2 col-form-label">Category:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" id="category" name="category" v-model="showProduct.category" class="form-control" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Image Product Dropdown-->
|
|
||||||
<div class="form-group row align-items-center">
|
|
||||||
<label for="imageProduct" class="col-sm-2 col-form-label">Product Image: </label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<img v-if="showProduct.imageProduct" :src="showProduct.imageProduct" alt="Product Image" class="img-fluid" data-toggle="modal" data-target="#imageModal" />
|
|
||||||
<input type="hidden" id="imageProduct" name="imageProduct" v-model="showProduct">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="imageModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="imageModalLabel">Product Image</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<img :src="showProduct.imageProduct" alt="Product Image" class="img-fluid">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quantity Input -->
|
|
||||||
<div class="form-group row d-flex align-items-center">
|
|
||||||
<label class="col-sm-2 col-form-label">Quantity:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="number" class="form-control" v-model="quantity" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Who will assign to *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-2 col-form-label">Item Assignment : </label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div class="dropdown">
|
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-12" v-model="assign"required>
|
|
||||||
<option class="btn-light" value="" selected disabled>Select Assignment Type</option>
|
|
||||||
<option class="btn-light" v-for="(item, index) in assigns" :key="item" :value="item">{{ item ?? 'Select Assignment Type' }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Station Dropdown -->
|
|
||||||
<div class="form-group row" v-if="assign === 'Station'">
|
|
||||||
<label class="col-sm-2 col-form-label">Station:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div class="dropdown">
|
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="stationId" required>
|
|
||||||
<option class="btn-light" value="" disabled selected>Select Station</option>
|
|
||||||
<option v-if="stations.length === 0" class="btn-light" disabled>No Station Assigned to You</option>
|
|
||||||
<option class="btn-light" v-for="(item, index) in stations" :key="index" :value="item.stationId">
|
|
||||||
{{ item.stationName }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Remark Input (Wider) -->
|
|
||||||
<div class="form-group row d-flex align-items-center">
|
|
||||||
<label class="col-sm-2 col-form-label">Remark:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" class="form-control col-md-10" v-model="remark" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Document/Picture Input -->
|
|
||||||
<div class="form-group row d-flex align-items-center">
|
|
||||||
<label class="col-sm-2 col-form-label">Document/Picture:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="file" id="document" name="document" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
@* Submit and Reset Buttons *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-8 offset-sm-3">
|
|
||||||
<button type="button" v-on:click="resetForm" class="btn btn-secondary m-1">Reset</button>
|
|
||||||
<button type="submit" class="btn btn-primary m-1">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#requestProduct');
|
|
||||||
|
|
||||||
// Attach a click event listener to elements with the class 'btn-success'.
|
|
||||||
$('#addRequestBtn').on('click', function () {
|
|
||||||
$('#requestModal').modal('show');
|
|
||||||
});
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
requestID : null,
|
|
||||||
userId : null,
|
|
||||||
stationId : "",
|
|
||||||
productId : "",
|
|
||||||
remark: "",
|
|
||||||
document: null,
|
|
||||||
quantity: 0,
|
|
||||||
status: "",
|
|
||||||
requestDate : null,
|
|
||||||
approvalDate : null,
|
|
||||||
productCategory: "",
|
|
||||||
assign: "",
|
|
||||||
|
|
||||||
productName: null,
|
|
||||||
searchQuery: "",
|
|
||||||
dropdownOpen: false,
|
|
||||||
stations: [],
|
|
||||||
selectedProduct: "",
|
|
||||||
selectedStation: "",
|
|
||||||
selectedCategory: "",
|
|
||||||
assigns: ["Self-Assign", "Station"],
|
|
||||||
showRequestModal: false,
|
|
||||||
loading: false,
|
|
||||||
products: [],
|
|
||||||
request: [],
|
|
||||||
currentUser: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.fetchProducts();
|
|
||||||
await this.fetchUser();
|
|
||||||
await Promise.all([
|
|
||||||
this.fetchStation(),
|
|
||||||
this.fetchRequest(),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
showProduct() {
|
|
||||||
if (!this.productId) {
|
|
||||||
return []; // No company selected, return empty list
|
|
||||||
}
|
|
||||||
const product = this.products.find(c => c.productId === this.productId);
|
|
||||||
|
|
||||||
this.productCategory = product.category;
|
|
||||||
|
|
||||||
return product ? product : {};
|
|
||||||
},
|
|
||||||
filteredProducts() {
|
|
||||||
return this.products.filter(item =>
|
|
||||||
item.productName.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
|
||||||
item.modelNo.toLowerCase().includes(this.searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
closeDropdown() {
|
|
||||||
this.dropdownOpen = false; // Tutup dropdown
|
|
||||||
},
|
|
||||||
|
|
||||||
selectProduct(item) {
|
|
||||||
this.selectedProduct = item;
|
|
||||||
this.productId = item.productId;
|
|
||||||
this.searchQuery = item.productName + " (" + item.modelNo + ")";
|
|
||||||
this.dropdownOpen = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleFileUpload(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
this.document = e.target.result.split(',')[1]; // Get Base64 string (remove metadata)
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
} else {
|
|
||||||
this.document = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async addRequest() {
|
|
||||||
try {
|
|
||||||
const requiredFields = ['productId', 'quantity', 'assign'];
|
|
||||||
|
|
||||||
// Loop through required fields and check if any are null or empty
|
|
||||||
for (let field of requiredFields) {
|
|
||||||
if (!this[field]) {
|
|
||||||
alert('Request Error', `Please fill in required fields: ${field}.`, 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.assign === "Station"){
|
|
||||||
if (this.stationId == null) {
|
|
||||||
alert('Request Error', `Please fill in required fields : Station.`, 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.assign === "Self-Assign"){
|
|
||||||
this.stationId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userId = this.currentUser.id;
|
|
||||||
this.status = "Requested";
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
this.requestDate = new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString();
|
|
||||||
|
|
||||||
|
|
||||||
// Prepare data as JSON (No file upload)
|
|
||||||
const requestData = {
|
|
||||||
ProductId: this.productId,
|
|
||||||
StationId: this.stationId,
|
|
||||||
UserId: this.userId,
|
|
||||||
ProductCategory: this.productCategory,
|
|
||||||
RequestQuantity: this.quantity,
|
|
||||||
remarkUser: this.remark || '',
|
|
||||||
remarkMasterInv: '',
|
|
||||||
status: this.status,
|
|
||||||
requestDate: this.requestDate,
|
|
||||||
approvalDate: null,
|
|
||||||
Document: this.document
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
|
|
||||||
// Send the data to the API
|
|
||||||
const response = await fetch('/InvMainAPI/AddRequest', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
this.resetForm();
|
|
||||||
alert('Success!', 'Request form has been successfully submitted.', 'success');
|
|
||||||
const requestItem = await response.json();
|
|
||||||
this.request.push(requestItem);
|
|
||||||
this.fetchRequest();
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to submit form.');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Inventory PSTW Error', `An error occurred: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
initiateTable() {
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
function renderDocument(data, full) {
|
|
||||||
if (!data) return "No Document";
|
|
||||||
|
|
||||||
let isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
|
|
||||||
let isPdf = /\.pdf$/i.test(data);
|
|
||||||
|
|
||||||
if (isImage) {
|
|
||||||
return `<a href="${data}" target="_blank" data-lightbox="image-1">
|
|
||||||
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
|
||||||
</a>`;
|
|
||||||
} else if (isPdf) {
|
|
||||||
return `<a href="${data}" target="_blank">
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
|
|
||||||
alt="PDF Document" class="img-thumbnail"
|
|
||||||
style="width: 50px; height: 50px;" />
|
|
||||||
<br>View PDF
|
|
||||||
</a>`;
|
|
||||||
} else {
|
|
||||||
return `<a href="${data}" target="_blank">Download File</a>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDeleteButton(data) {
|
|
||||||
return `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pendingRequestDatatable = $('#requestDatatable').DataTable({
|
|
||||||
"data": this.request.filter(req => req.status === "Requested"),
|
|
||||||
"columns": [
|
|
||||||
{ "title": "Request ID", "data": "requestID", "createdCell": (td, cellData) => $(td).attr('id', `qr${cellData}`) },
|
|
||||||
{ "title": "Product Name", "data": "productName", "render": (data, type, full) => renderDocument(full.productPicture) },
|
|
||||||
{ "title": "Product Category", "data": "productCategory" },
|
|
||||||
{ "title": "Request Quantity", "data": "requestQuantity" },
|
|
||||||
{ "title": "Document / Picture", "data": "document", "render": (data, type, full) => renderDocument(data) },
|
|
||||||
{ "title": "Remark", "data": "remarkUser" },
|
|
||||||
{ "title": "Station Deploy", "data": "stationName", "render": (data) => data || "Self Assign" },
|
|
||||||
{ "title": "Request Date", "data": "requestDate" },
|
|
||||||
{ "title": "Status", "data": "status" },
|
|
||||||
{ "title": "Delete", "data": "requestID", "render": renderDeleteButton, "className": "align-middle" }
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settledRequestDatatable = $('#settledrequestDatatable').DataTable({
|
|
||||||
"data": this.request.filter(req => req.status !== "Requested"),
|
|
||||||
"columns": [
|
|
||||||
{ "title": "Request ID", "data": "requestID", "createdCell": (td, cellData) => $(td).attr('id', `qr${cellData}`) },
|
|
||||||
{ "title": "Status", "data": "status" },
|
|
||||||
{ "title": "Product Name", "data": "productName", "render": (data, type, full) => renderDocument(full.productPicture) },
|
|
||||||
{ "title": "Product Category", "data": "productCategory" },
|
|
||||||
{ "title": "Request Quantity", "data": "requestQuantity" },
|
|
||||||
{ "title": "Station Deploy", "data": "stationName", "render": (data) => data || "Self Assign" },
|
|
||||||
{ "title": "Document / Picture", "data": "document", "render": (data, type, full) => renderDocument(data) },
|
|
||||||
{ "title": "Remark", "data": "remarkUser" },
|
|
||||||
{ "title": "Remark (Master)", "data": "remarkMasterInv" },
|
|
||||||
{ "title": "Request Date", "data": "requestDate" },
|
|
||||||
{ "title": "Approval Date", "data": "approvalDate" }
|
|
||||||
],
|
|
||||||
responsive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#requestDatatable tbody').off('click', '.delete-btn');
|
|
||||||
|
|
||||||
$('#requestDatatable tbody').on('click', '.delete-btn', function () {
|
|
||||||
const requestID = $(this).data('id');
|
|
||||||
self.deleteRequestItem(requestID);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async fetchRequest() {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const response = await fetch(`/InvMainAPI/ItemRequestListEachUser/${this.userId}`,
|
|
||||||
{
|
|
||||||
method: 'GET', // Specify the HTTP method
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json', // Set content type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch request List');
|
|
||||||
}
|
|
||||||
this.request = await response.json();
|
|
||||||
|
|
||||||
if ($.fn.dataTable.isDataTable('#requestDatatable')) {
|
|
||||||
$('#requestDatatable').DataTable().clear().destroy();
|
|
||||||
}
|
|
||||||
if ($.fn.dataTable.isDataTable('#settledrequestDatatable')) {
|
|
||||||
$('#settledrequestDatatable').DataTable().clear().destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initiateTable();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error fetching request List:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchProducts() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/ProductList', {
|
|
||||||
method: 'POST', // Specify the HTTP method
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json', // Set content type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch products');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.products = await response.json();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error fetching products:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchStation() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/StationList', {
|
|
||||||
method: 'POST', // Specify the HTTP method
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch suppliers');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
this.stations = data.filter(station => station.stationPicID === this.userId);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching suppliers:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.currentUser = data?.userInfo || null;
|
|
||||||
this.userId = await this.currentUser.id;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetForm() {
|
|
||||||
this.searchQuery = "";
|
|
||||||
this.stationId = "";
|
|
||||||
this.productId = "";
|
|
||||||
this.remark = "";
|
|
||||||
this.document = null;
|
|
||||||
this.quantity = 0;
|
|
||||||
this.status = "";
|
|
||||||
this.requestDate = null;
|
|
||||||
this.approvalDate = null;
|
|
||||||
this.productCategory = "";
|
|
||||||
this.assign = "",
|
|
||||||
|
|
||||||
this.productName = null;
|
|
||||||
this.selectedProduct = "";
|
|
||||||
this.selectedStation = "";
|
|
||||||
this.selectedCategory = "";
|
|
||||||
this.showRequestModal = false;
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteRequestItem(requestID) {
|
|
||||||
if (!confirm("Are you sure you want to delete this request?")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/DeleteRequest/${requestID}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
alert(result.message);
|
|
||||||
|
|
||||||
if ($.fn.dataTable.isDataTable('#requestDatatable')) {
|
|
||||||
const table = $('#requestDatatable').DataTable();
|
|
||||||
table.row($(`.delete-btn[data-id="${requestID}"]`).closest('tr')).remove().draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.request = this.request.filter(req => req.requestID !== requestID);
|
|
||||||
} else {
|
|
||||||
alert(result.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Error deleting item:", error);
|
|
||||||
alert("An error occurred while deleting the item.");
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
clickOutside: {
|
|
||||||
beforeMount(el, binding) {
|
|
||||||
el.clickOutsideEvent = (event) => {
|
|
||||||
if (!(el.contains(event.target))) {
|
|
||||||
binding.value?.(); // Guna optional chaining untuk elak error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.body.addEventListener("click", el.clickOutsideEvent);
|
|
||||||
},
|
|
||||||
unmounted(el) {
|
|
||||||
document.body.removeEventListener("click", el.clickOutsideEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,828 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "QR Scanner";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
<style scoped>
|
|
||||||
.error {
|
|
||||||
font-weight: bold;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.barcode-format-checkbox {
|
|
||||||
margin-right: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
width: 200px; /* Adjust width as needed */
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
|
|
||||||
<div id="registerItem" class="row">
|
|
||||||
<div class="row card">
|
|
||||||
<div class="card-header">
|
|
||||||
<button v-if="displayStatus !== null" v-on:click="resetScanner" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-arrow-left"></i> Back to Scanner
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
<select v-if="displayStatus == null" class="form-select" v-model="selectedCameraId" v-on:change="updateCamera">
|
|
||||||
<option v-for="device in videoInputDevices" :key="device.deviceId" :value="device.deviceId">
|
|
||||||
{{ device.label || `Camera ${videoInputDevices.indexOf(device) + 1}` }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div id="registerItem" v-if="displayStatus == null" data-aos="fade-right">
|
|
||||||
<p style="text-align:center; padding:10px;">Scan QR Code Here:</p>
|
|
||||||
|
|
||||||
<qrcode-stream :constraints="selectedConstraints"
|
|
||||||
:formats="['qr_code']"
|
|
||||||
:track="trackFunctionSelected.value"
|
|
||||||
v-on:camera-on="onCameraReady"
|
|
||||||
v-on:detect="onDecode"
|
|
||||||
v-on:error="onError">
|
|
||||||
</qrcode-stream>
|
|
||||||
|
|
||||||
<p class="error">{{ error }}</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!--RECEIVE OR RETURN INTERFACE -->
|
|
||||||
<div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'arrived' && thisItem.currentStationId != null">
|
|
||||||
<h2>Item Receive Information :</h2>
|
|
||||||
<h3>Station Assign</h3>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'arrived' && thisItem.currentStationId == null">
|
|
||||||
<h2>Item Receive Information :</h2>
|
|
||||||
<h3>Self Assign</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'return' && thisItem.currentStationId != null">
|
|
||||||
<h2>Item Return Information :</h2>
|
|
||||||
<h3>Station</h3>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: center; margin: 20px 0;" v-if="displayStatus === 'return' && thisItem.currentStationId == null">
|
|
||||||
<h2>Item Return Information :</h2>
|
|
||||||
<h3>Self Assign</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="displayStatus === 'arrived' || displayStatus === 'return'" style="display: flex; justify-content: center; align-items: center;">
|
|
||||||
|
|
||||||
<div class="col-lg-7 col-11 border rounded p-3 shadow-sm">
|
|
||||||
<div class="row m-3 d-flex align-items-center justify-content-center">
|
|
||||||
<div class="col-lg-7 col-11 border rounded p-3 shadow-sm">
|
|
||||||
<div class="col-12 text-center">
|
|
||||||
<img :src="thisItem.imageProduct" alt="Product Image" class="img-fluid rounded" data-toggle="modal" data-target="#imageModal" style="max-height: 300px;" />
|
|
||||||
</div>
|
|
||||||
<div class="col-12 text-center mt-3">
|
|
||||||
<p class="h4 fw-bold text-primary">{{ thisItem.uniqueID }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Item Name -->
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<p class="h5 fw-bold">
|
|
||||||
<i class="fas fa-tag me-2 text-secondary"></i>Item Name:
|
|
||||||
<span class="text-muted">{{ thisItem.productName }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Part Number -->
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<p class="h5 fw-bold">
|
|
||||||
<i class="fas fa-barcode me-2 text-secondary"></i>Part Number:
|
|
||||||
<span class="text-muted">{{ thisItem.partNumber }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Serial Number -->
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<p class="h5 fw-bold">
|
|
||||||
<i class="fas fa-hashtag me-2 text-secondary"></i>Serial Number:
|
|
||||||
<span class="text-muted">{{ thisItem.serialNumber }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Station -->
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<p class="h5 fw-bold">
|
|
||||||
<i class="fas fa-user-tie me-2 text-secondary"></i>PIC:
|
|
||||||
<span class="text-muted">{{thisItem.currentUserFullName}}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Station -->
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<p class="h5 fw-bold">
|
|
||||||
<i class="fas fa-user-tie me-2 text-secondary"></i>Station:
|
|
||||||
<span class="text-muted">{{thisItem.currentStation || 'No Station Deploy (Self Assign)' }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--RECIEVE INTERFACE -->
|
|
||||||
<div class="col-12" v-if="displayStatus === 'arrived'">
|
|
||||||
<div class="card shadow-sm border-0">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title mb-4 text-primary">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>Receiver Information
|
|
||||||
</h5>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<!-- User -->
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
|
||||||
<span class="fw-bold">
|
|
||||||
<i class="fas fa-user me-2 text-secondary"></i>User:
|
|
||||||
</span>
|
|
||||||
<span class="text-muted text-end" style="max-width: 70%; word-wrap: break-word;">
|
|
||||||
{{ thisItem.currentUser }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--RETURN INTERFACE -->
|
|
||||||
<div class="col-12" v-if="displayStatus === 'return'">
|
|
||||||
<div class="card shadow-sm border-0">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title mb-4 text-primary">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>Sender Information
|
|
||||||
</h5>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<!-- User -->
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
|
||||||
<span class="fw-bold">
|
|
||||||
<i class="fas fa-user me-2 text-secondary"></i>User:
|
|
||||||
</span>
|
|
||||||
<span class="text-muted text-end" style="max-width: 70%; word-wrap: break-word;">
|
|
||||||
{{ thisItem.currentUser }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--RECIEVE INTERFACE -->
|
|
||||||
<form v-on:submit.prevent="updateItemMovement" v-if="displayStatus === 'arrived'" data-aos="fade-right">
|
|
||||||
<div class=" register" data-aos="fade-right">
|
|
||||||
<div class="row" data-aos="fade-right">
|
|
||||||
|
|
||||||
@*Right Side*@
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="tab-content" id="myTabContent">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
@* Submit and Reset Buttons *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-8 offset-sm-5">
|
|
||||||
<button type="button" v-on:click="receiveReturnMessage" class="btn btn-secondary m-1">Return Item</button>
|
|
||||||
<button type="submit" class="btn btn-primary m-1">Receive Item</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!--RETURN INTERFACE -->
|
|
||||||
<form v-on:submit.prevent="" v-if="displayStatus === 'return'" data-aos="fade-right">
|
|
||||||
<div class=" register" data-aos="fade-right">
|
|
||||||
<div class="row" data-aos="fade-right">
|
|
||||||
|
|
||||||
@*Right Side*@
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="tab-content" id="myTabContent">
|
|
||||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
|
||||||
<br><br>
|
|
||||||
@* Submit and Reset Buttons *@
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-8 offset-sm-5">
|
|
||||||
<button type="submit" v-on:click="ReturnMessage" class="btn btn-primary m-1">Return Item</button>
|
|
||||||
<button type="submit" v-on:click="StationMessage" class="btn btn-primary m-1">{{ thisItem?.currentStationId == null ? "Deploy Station" : "Change Station" }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!--STATION DEPLOY MESSAGE BOX INTERFACE-->
|
|
||||||
<div class="modal fade" id="stationMessage" tabindex="-1" role="dialog" aria-labelledby="stationModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="stationModalLabel">{{ thisItem?.currentStationId == null ? "Deploy Station" : "Change Station" }}</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" v-on:click="closeStationMessageModal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form v-on:submit.prevent="updateStationItemMovement">
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Deploy Station : </label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedStation">
|
|
||||||
<option class="btn-light" value="" disabled selected>Select Station</option>
|
|
||||||
<option v-if="stationList.length === 0" class="btn-light" disabled>No Station Assigned to You</option>
|
|
||||||
<option class="btn-light" v-for="(station, index) in stationList" :key="index" :value="station.stationId">{{ station.stationName}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Remark:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" class="form-control" v-model="remark" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Consignment Note:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Deploy Station</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--RETURN MESSAGE BOX INTERFACE-->
|
|
||||||
<div class="modal fade" id="returnMessage" tabindex="-1" role="dialog" aria-labelledby="returnModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="returnModalLabel">Return Item</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" v-on:click="closeMessageModal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form v-on:submit.prevent="returnItemMovement">
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Remark:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" class="form-control" v-model="remark" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Consignment Note:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Return Item</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--RECEIVE THEN RETURN INTERFACE -->
|
|
||||||
<div class="modal fade" id="returnModal" tabindex="-1" role="dialog" aria-labelledby="returnModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="returnModalLabel">Return Item</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" v-on:click="closeModal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form v-on:submit.prevent="receiveReturnAPI">
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Remark:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="text" class="form-control" v-model="remark" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-4 col-form-label">Consignment Note:</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Return Item</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ALREADY RETURN ITEM INTERFACE -->
|
|
||||||
<div v-if="displayStatus === 'requestAgain'" class="d-flex justify-content-center align-items-center vh-100">
|
|
||||||
<div class="col-lg-10 col-11 border rounded p-5 shadow-lg text-center min-vh-50">
|
|
||||||
<h1 class="text-danger">The item has been register as returned.</h1>
|
|
||||||
<h3 class="text-muted">You need to request this item again to used it legally.</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- NOT SIGN IN ITEM INTERFACE -->
|
|
||||||
<div v-if="displayStatus === 'differentUser'" class="d-flex justify-content-center align-items-center vh-100">
|
|
||||||
<div class="col-lg-10 col-11 border rounded p-5 shadow-lg text-center min-vh-50">
|
|
||||||
<h1 class="text-danger">The item is not assigned to you.</h1>
|
|
||||||
<h3 class="text-muted">You need to request this item to validly use it.</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script src="~/js/vue-qrcode-reader.umd.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
const app = Vue.createApp({
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
thisItem: null,
|
|
||||||
selectedUser: "",
|
|
||||||
selectedStation: "",
|
|
||||||
stationList: [],
|
|
||||||
displayStatus: null,
|
|
||||||
debounceTime: 500,
|
|
||||||
currentUser: null,
|
|
||||||
movementId: null,
|
|
||||||
currentUserId: null,
|
|
||||||
remark: null,
|
|
||||||
consignmentNote: null,
|
|
||||||
receiveReturn: null,
|
|
||||||
UniqueID: null,
|
|
||||||
InventoryMasterId: null,
|
|
||||||
|
|
||||||
//QR VARIABLE
|
|
||||||
qrCodeResult: null,
|
|
||||||
debounceTimeout: null,
|
|
||||||
error: "",
|
|
||||||
selectedConstraints: { facingMode: "user" },
|
|
||||||
trackFunctionSelected: { text: 'outline', value: null },
|
|
||||||
barcodeFormats: {
|
|
||||||
qr_code: true, // Hanya mendukung QR Code
|
|
||||||
code_128: true,
|
|
||||||
ean_13: true
|
|
||||||
},
|
|
||||||
constraintOptions: [
|
|
||||||
{ label: "Rear Camera", constraints: { facingMode: "environment" } },
|
|
||||||
{ label: "Front Camera", constraints: { facingMode: "user" } }
|
|
||||||
],
|
|
||||||
videoInputDevices: [],
|
|
||||||
selectedCameraId: null,
|
|
||||||
scanStartTime: null,
|
|
||||||
scanTime: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.trackFunctionSelected.value = this.paintOutline;
|
|
||||||
await this.fetchUser();
|
|
||||||
await Promise.all([
|
|
||||||
this.fetchStation(),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
handleFileUpload(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
this.consignmentNote = e.target.result.split(',')[1]; // Get Base64 string (remove metadata)
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
} else {
|
|
||||||
this.consignmentNote = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateStationItemMovement() {
|
|
||||||
const requiredFields = ['selectedStation'];
|
|
||||||
|
|
||||||
for (let field of requiredFields) {
|
|
||||||
if (!this[field]) {
|
|
||||||
alert(`Request Error: Please fill in required field ${field}.`, 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const now = new Date();
|
|
||||||
const formData = {
|
|
||||||
ItemId: this.thisItem.itemID,
|
|
||||||
ToStation: this.thisItem.currentStationId,
|
|
||||||
ToStore: this.thisItem.toStore,
|
|
||||||
ToUser: this.currentUserId,
|
|
||||||
ToOther: "Delivered",
|
|
||||||
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
Action: "Assign",
|
|
||||||
Quantity: this.thisItem.quantity,
|
|
||||||
Remark: this.remark,
|
|
||||||
ConsignmentNote: this.consignmentNote,
|
|
||||||
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
LastUser: this.currentUserId,
|
|
||||||
LastStore: this.thisItem.toStore,
|
|
||||||
LastStation: this.selectedStation,
|
|
||||||
LatestStatus: "Delivered",
|
|
||||||
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
MovementComplete: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch('/InvMainAPI/StationItemMovementUser', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
this.thisItem = await response.json();
|
|
||||||
this.fetchItem(this.thisItem.uniqueID);
|
|
||||||
alert('Success! Item assign to the Station.');
|
|
||||||
$('#stationMessage').modal('hide');
|
|
||||||
this.displayStatus = "return";
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to submit form.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Inventory PSTW Error: An error occurred.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateItemMovement() {
|
|
||||||
|
|
||||||
if (this.receiveReturn == null) {
|
|
||||||
if (!confirm("Are you sure you already received this item?")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const now = new Date();
|
|
||||||
const formData = {
|
|
||||||
Id: this.thisItem.id,
|
|
||||||
LastStore: this.thisItem.toStore,
|
|
||||||
LatestStatus: "Delivered",
|
|
||||||
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
MovementComplete: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch('/InvMainAPI/UpdateItemMovementUser', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
if (this.receiveReturn == null) {
|
|
||||||
alert('Success! Item has been successfully received.');
|
|
||||||
this.thisItem = await response.json();
|
|
||||||
this.displayStatus = "return";
|
|
||||||
this.fetchItem(this.UniqueID);
|
|
||||||
this.resetForm();
|
|
||||||
} else {
|
|
||||||
this.returnItemMovement();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to submit form.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Inventory PSTW Error: An error occurred.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async returnItemMovement() {
|
|
||||||
|
|
||||||
if (!confirm("Are you sure you want to return this item?")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const now = new Date();
|
|
||||||
const formData = {
|
|
||||||
ItemId: this.thisItem.itemID,
|
|
||||||
ToStation: this.thisItem.currentStationId,
|
|
||||||
ToStore: this.thisItem.currentStoreId,
|
|
||||||
ToUser: this.currentUserId,
|
|
||||||
ToOther: "Return",
|
|
||||||
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
Action: "StockIn",
|
|
||||||
Quantity: this.thisItem.quantity,
|
|
||||||
Remark: this.remark,
|
|
||||||
ConsignmentNote: this.consignmentNote,
|
|
||||||
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
|
||||||
LastUser: this.InventoryMasterId,
|
|
||||||
LastStore: this.thisItem.toStore,
|
|
||||||
LastStation: this.thisItem.toStation,
|
|
||||||
LatestStatus: null,
|
|
||||||
ReceiveDate: null,
|
|
||||||
MovementComplete: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch('/InvMainAPI/ReturnItemMovementUser', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
alert('Success! Item is on the delivery to return to Inventory Master.');
|
|
||||||
this.thisItem = await response.json();
|
|
||||||
$('#returnModal').modal('hide');
|
|
||||||
$('#returnMessage').modal('hide');
|
|
||||||
this.displayStatus = "requestAgain";
|
|
||||||
this.resetForm();
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to submit form.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('Inventory PSTW Error: An error occurred.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchItem(itemid) {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/GetItem/' + itemid, {
|
|
||||||
method: 'POST',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
|
|
||||||
this.thisItem = await response.json();
|
|
||||||
this.fetchStore(this.thisItem.toStore);
|
|
||||||
|
|
||||||
if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus == null && this.thisItem.currentUserId == this.currentUserId && this.thisItem.movementComplete == 0) {
|
|
||||||
this.displayStatus = "arrived";
|
|
||||||
} else if (this.thisItem.movementId != null && this.thisItem.latestStatus != null && this.thisItem.currentUserId == this.currentUserId && this.thisItem.latestStatus != "Ready To Deploy") {
|
|
||||||
this.displayStatus = "return";
|
|
||||||
} else if (this.thisItem.movementId != null && this.thisItem.toOther === "Return" && this.thisItem.latestStatus == null && this.thisItem.toUser == this.currentUserId) {
|
|
||||||
this.displayStatus = "requestAgain";
|
|
||||||
} else {
|
|
||||||
this.displayStatus = "differentUser";
|
|
||||||
this.thisItem = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.error = 'Qr Code Not Register to the system';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching item information:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchStation() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/StationList', {
|
|
||||||
method: 'POST', // Specify the HTTP method
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch suppliers');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
this.stationList = data.filter(station => station.stationPicID === this.currentUserId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching suppliers:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchStore(storeId) {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/InvMainAPI/StoreSpecificMaster/' + storeId, {
|
|
||||||
method: 'POST', // Specify the HTTP method
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch Store');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
this.InventoryMasterId = data.userId;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching suppliers:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.currentUser = data?.userInfo || null;
|
|
||||||
this.currentUserId = this.currentUser.id;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error('Failed to fetch user');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetScanner() {
|
|
||||||
this.displayStatus = null;
|
|
||||||
this.qrCodeResult = null;
|
|
||||||
this.thisItem = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
resetForm() {
|
|
||||||
this.selectedStation = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async receiveReturnAPI() {
|
|
||||||
this.receiveReturn = 1;
|
|
||||||
this.updateItemMovement();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Split Url dapatkan unique ID Je
|
|
||||||
onDecode(detectedCodes) {
|
|
||||||
if (detectedCodes.length > 0) {
|
|
||||||
this.qrCodeResult = detectedCodes[0].rawValue; // Ambil URL dari rawValue
|
|
||||||
this.UniqueID = this.qrCodeResult.split('/').pop(); // Ambil UniqueID dari URL
|
|
||||||
this.fetchItem(this.UniqueID);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//Showing Qr Error
|
|
||||||
onError(err) {
|
|
||||||
let message = `[${err.name}]: `;
|
|
||||||
if (err.name === "NotAllowedError") {
|
|
||||||
message += "You have to allow camera accecss.";
|
|
||||||
} else if (err.name === "NotFoundError") {
|
|
||||||
message += "There's no camera detect.";
|
|
||||||
} else if (err.name === "NotReadableError") {
|
|
||||||
message += "You are using camera on the other application.";
|
|
||||||
} else {
|
|
||||||
message += err.message;
|
|
||||||
}
|
|
||||||
this.error = message;
|
|
||||||
},
|
|
||||||
|
|
||||||
//Setting Camera to know that camera are turn on or not
|
|
||||||
async onCameraReady(videoElement) {
|
|
||||||
|
|
||||||
const video = document.querySelector("video");
|
|
||||||
|
|
||||||
const track = video.srcObject.getVideoTracks()[0];
|
|
||||||
|
|
||||||
if (track && track.getCapabilities) {
|
|
||||||
const capabilities = track.getCapabilities(); // Get camera capabilities
|
|
||||||
|
|
||||||
if (capabilities.sharpness) {
|
|
||||||
track.applyConstraints({
|
|
||||||
advanced: [{ width: 1280, height: 720 }]
|
|
||||||
}).then(() => {
|
|
||||||
|
|
||||||
// Step 2: Apply sharpness separately
|
|
||||||
return track.applyConstraints({ advanced: [{ sharpness: 10 }] });
|
|
||||||
}).then(() => {
|
|
||||||
|
|
||||||
return track.applyConstraints({ advanced: [{ exposureMode: 'continuous' }] });
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
}).catch(err => console.error("Failed to apply constraints:", err));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.warn("⚠️ Sharpness not supported on this camera");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
this.videoInputDevices = devices.filter(device => device.kind === 'videoinput');
|
|
||||||
|
|
||||||
if (this.videoInputDevices.length > 0) {
|
|
||||||
// Keep the selected camera if already chosen
|
|
||||||
if (!this.selectedCameraId) {
|
|
||||||
this.selectedCameraId = this.videoInputDevices[0].deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedConstraints = { deviceId: { exact: this.selectedCameraId } };
|
|
||||||
} else {
|
|
||||||
this.error = "No camera detected.";
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.error = "Error accessing camera: " + err.message;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//Update Camera Category
|
|
||||||
updateCamera() {
|
|
||||||
this.selectedConstraints = { deviceId: { exact: this.selectedCameraId } };
|
|
||||||
},
|
|
||||||
|
|
||||||
//Red box if QR Detect
|
|
||||||
paintOutline(detectedCodes, ctx) {
|
|
||||||
for (const detectedCode of detectedCodes) {
|
|
||||||
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;
|
|
||||||
|
|
||||||
ctx.strokeStyle = 'red'; // Warna garis merah
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(firstPoint.x, firstPoint.y);
|
|
||||||
for (const { x, y } of otherPoints) {
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
ctx.lineTo(firstPoint.x, firstPoint.y);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//Ni return message
|
|
||||||
receiveReturnMessage() {
|
|
||||||
$("#returnModal").modal("show");
|
|
||||||
},
|
|
||||||
|
|
||||||
closeModal() {
|
|
||||||
$('#returnModal').modal('hide'); // Manually hide the modal
|
|
||||||
},
|
|
||||||
|
|
||||||
ReturnMessage() {
|
|
||||||
$("#returnMessage").modal("show");
|
|
||||||
},
|
|
||||||
|
|
||||||
closeMessageModal() {
|
|
||||||
$('#returnMessage').modal('hide'); // Manually hide the modal
|
|
||||||
},
|
|
||||||
|
|
||||||
StationMessage() {
|
|
||||||
$("#stationMessage").modal("show");
|
|
||||||
},
|
|
||||||
|
|
||||||
closeStationMessageModal() {
|
|
||||||
$('#stationMessage').modal('hide'); // Manually hide the modal
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
app.component("qrcode-stream", VueQrcodeReader.QrcodeStream);
|
|
||||||
|
|
||||||
app.mount('#registerItem');
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
// Attach event listener to show modal
|
|
||||||
$('#addItemBtn').on('click', function () {
|
|
||||||
$('#registerItemModal').modal('show');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close modals
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Dashboard";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
<div class="container" id="invUser">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-center">
|
|
||||||
<p><h1 class="display-4">Inventory User Dashboard</h1></p>
|
|
||||||
<p v-show="currentUserCompanyDept.departmentName"><h2 class="display-6">Store: {{ currentUserCompanyDept.departmentName }}</h2></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
|
|
||||||
</div>
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
app.mount('#invUser');
|
|
||||||
|
|
||||||
$('.closeModal').on('click', function () {
|
|
||||||
// Show the modal with the ID 'addManufacturerModal'.
|
|
||||||
$('.modal').modal('hide');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const app = Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentUser: null,
|
|
||||||
currentUserCompanyDept: {
|
|
||||||
departmentName: null,
|
|
||||||
departmentId: null
|
|
||||||
},
|
|
||||||
reportData: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetchUser();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchUser() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.currentUser = data?.userInfo || null;
|
|
||||||
const companyDeptData = await this.currentUser.department;
|
|
||||||
const userRole = this.currentUser.role;
|
|
||||||
if(userRole == "SuperAdmin" || userRole == "SystemAdmin"){
|
|
||||||
this.currentUserCompanyDept = {departmentId : 0, departmentName : "All"}
|
|
||||||
this.fetchInventoryReport(0);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.currentUserCompanyDept = companyDeptData;
|
|
||||||
this.fetchInventoryReport(companyDeptData.departmentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchInventoryReport(deptId){
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/InvMainAPI/GetInventoryReport/` + deptId, {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
this.reportData = data;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('There was a problem with the fetch operation:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "PSTW Centralized System";
|
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-center">
|
|
||||||
<h1 class="display-4">Welcome To Invetory Module</h1>
|
|
||||||
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user