Compare commits

..

No commits in common. "OT_Project" and "main" have entirely different histories.

1438 changed files with 879 additions and 790119 deletions

View File

@ -1,5 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}

View File

@ -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>
}

View File

@ -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()
{
}
}
}

View File

@ -1,8 +0,0 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />

View File

@ -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();
}
}
}

View File

@ -1,8 +0,0 @@
@page
@model ConfirmEmailChangeModel
@{
ViewData["Title"] = "Confirm email change";
}
<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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;
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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()
{
}
}
}

View File

@ -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>

View File

@ -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()
{
}
}
}

View File

@ -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>
}

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}
}

View File

@ -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>

View File

@ -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();
}
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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("~/");
}
}
}

View File

@ -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>

View File

@ -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");
}
}
}

View File

@ -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" />
}

View File

@ -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");
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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&amp;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" />
}

View File

@ -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);
}
}
}

View File

@ -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
{
@: &nbsp;
}
</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>
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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");
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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");
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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>&nbsp;</text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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)
}

View File

@ -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>

View File

@ -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>
}

View File

@ -1 +0,0 @@
@using PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage

View File

@ -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" />
}

View File

@ -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;
}
}
}

View File

@ -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>
}
}

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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" />
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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()
{
}
}
}

View File

@ -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>
}

View File

@ -1 +0,0 @@
@using PSTW_CentralSystem.Areas.Identity.Pages.Account

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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

View File

@ -1,4 +0,0 @@

@{
Layout = "/Views/Shared/_Layout.cshtml";
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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>
}
}

View File

@ -1,31 +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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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

View File

@ -1,947 +0,0 @@
@{
ViewData["Title"] = "Product Request";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
<div id="requestProduct" class="row">
<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>&nbsp;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 Requestt</h2>
@* <button id="addRequestBtn" class="btn btn-success col-md-3 col-lg-3 m-1 col-12"><i class="fa fa-plus"></i>&nbsp;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="settledrequestDatatable" style=" width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
<div class="modal fade" id="rejectModal" tabindex="-1" role="dialog" aria-labelledby="rejectRequestModalLabel" 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="rejectRequestModalLabel">Reject Request</h5>
<button type="button" class="closeModal" data-dismiss="modal" aria-label="Close" v-on:click="showRequestModal=false">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<form v-on:submit.prevent="rejectRequest" data-aos="fade-right">
@* <div class=" register" data-aos="fade-right"> *@
<div 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">REJECT REQUEST</h3>
<div class="row register-form">
<div class="col-md-12">
<div class="form-group row">
@* <label class="col-sm-4 col-form-label hidden-label">Request Id</label> *@
<div class="col-sm-8">
<div class="dropdown">
<input type="text" id="currentrequestID" name="currentrequestID" v-model="currentrequestID" class="form-control" hidden />
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Remark</label>
<div class="col-sm-8">
<div class="dropdown">
<input type="text" id="rejectremark" name="rejectremark" v-model="rejectremark" class="form-control" required />
</div>
</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">Submit</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="approveModal" tabindex="-1" role="dialog" aria-labelledby="approveRequestModalLabel" 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="approveRequestModalLabel">Approve Request</h5>
<button type="button" class="closeModal" data-dismiss="modal" aria-label="Close" v-on:click="showRequestModal=false">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<form v-on:submit.prevent="approveRequest" data-aos="fade-right">
@* <div class=" register" data-aos="fade-right"> *@
<div 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">APPROVE REQUEST</h3>
<div class="row register-form">
<div class="col-md-12">
<div class="form-group row">
@* <label class="col-sm-4 col-form-label hidden-label">Request Id</label> *@
<div class="col-sm-8">
<div class="dropdown">
<input type="text" id="currentrequestID" name="currentrequestID" v-model="currentrequestID" class="form-control" hidden />
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Remark</label>
<div class="col-sm-8">
<div class="dropdown">
<input type="text" id="approveremark" name="approveremark" v-model="approveremark" class="form-control" required />
</div>
</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">Submit</button>
</div>
</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 () {
// Show the modal
$('#requestModal').modal('show');
});
$('.closeModal').on('click', function () {
// Show the modal
$('.modal').modal('hide');
});
$('.submit-button').on('click', function () {
// Show the modal
$('#rejectModal').modal('hide');
$('#approveModal').modal('hide');
});
});
const app = Vue.createApp({
data() {
return {
requestID : null,
userId : null,
stationId : null,
productId : null,
remark: "",
document: null,
quantity: 0,
status: "",
requestDate : null,
approvalDate : null,
productCategory: "",
productName: null,
productCategory: null,
stations: [],
selectedProduct: "",
selectedStation: "",
selectedCategory: "",
showRequestModal: false,
showRejectModal: false,
showApproveModal: false,
loading: false,
request: [],
currentUser: null,
currentrequestID: "",
rejectremark: "",
approveremark: "",
}
},
mounted() {
this.fetchRequest();
this.fetchUser();
this.fetchProducts();
this.fetchStation();
},
computed: {
// filteredDepartments() {
// if (!this.selectedCompany) {
// return []; No company selected, return empty list
// }
// const company = this.companies.find(c => c.companyId === this.selectedCompany);
// this.selectedDepartment = '';
// return company ? company.departments : [];
// },
// showProduct() {
// if (!this.selectedProduct) {
// return []; No company selected, return empty list
// }
// const product = this.products.find(c => c.productId === this.selectedProduct);
// return product ? product : {};
// },
},
methods: {
resetForm() {
this.rejectremark = "";
},
async addRequest() {
try {
const requiredFields = ['stationId', 'productId', 'quantity', 'productCategory'];
// Loop through required fields and check if any are null or empty
for (let field of requiredFields) {
if (this[field] === null || this[field] === '') {
alert('Request Error', `Please fill in required fields: ${field}.`, 'warning');
return; // Exit early if validation fails
}
}
// Proceed to send the data to the API
const response = await fetch('/InvMainAPI/AddRequest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify(formData)
});
if (response.ok) {
// If the form submission was successful, display a success message
alert('Success!', 'Request form has been successfully submitted.', 'success');
const requestItem = await response.json();
this.items.push(requestItem);
this.fetchRequest();
} else {
throw new Error('Failed to submit form.');
}
} catch (error) {
console.error('Error:', error);
// Displaying error message
alert('Inventory PSTW Error', `An error occurred: ${error.message}`, 'error');
}
},
initiateTable() {
self = this;
this.requestDatatable = $('#requestDatatable').DataTable({
"data": this.items.filter(item => item.status == "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
},
{
"title": "Action",
"data" :"requestID",
"render": function (data, type, row) {
var actiontButtons = `<div class="row" style="padding: 5px;"> <button type="button" class="btn btn-success approve-btn" data-id="${data}">Approve</button></div> <div class="row" style="padding: 5px;"><button type="button" class="btn btn-danger reject-btn" data-id="${data}">Reject</button></div>`;
return actiontButtons
},
"className": "align-middle",
},
{
"title": "Product",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
var imageSrc = full.productImage;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
console.log(full);
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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="${imageSrc}" target="_blank">Download File</a>`;
// }
},
},
{
"title": "Requested by User",
"data": "userName",
},
{
"title": "Requested by Station",
"data": "stationName",
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Document/Picture",
"data": "document",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{
"title": "User Remark",
"data": "remarkUser",
},
// {
// "title": "InvMaster Remark",
// "data": "remarkMasterInv",
// },
{
"title": "Status",
"data": "status",
},
{
"title": "Request Date",
"data": "requestDate",
},
{
"title": "Approval Date",
"data": "approvalDate",
},
// {
// "title": "Reject",
// "data" :"requestID",
// "render": function (data, type, row) {
// var rejectButton = `<button type="button" class="btn btn-danger reject-btn" data-id="${data}">Reject</button>`;
// return rejectButton
// },
// "className": "align-middle",
// },
// {
// "title": "Approve",
// "data": "requestID",
// "render": function (data) {
// var approveButton = `<button type="button" class="btn btn-success approve-btn" data-id="${data}">Approve</button>`;
// return approveButton;
// },
// "className": "align-middle",
// }
],
responsive: true,
drawCallback: function (settings) {
// Generate QR codes after rows are rendered
// const api = this.api();
// api.rows().every(function () {
// const data = this.data(); Row data
// const containerId = `qr${data.requestID}`;
// const container = $(`#${containerId}`);
// container.empty();
// container.append(`${data.requestID}`);
// console.log(container[0]);
// if (container) {
// Generate QR code only if not already generated
// new QRCode(container[0], {
// text: data.qrString,
// width: 100,
// height: 100,
// colorDark: "#000000",
// colorLight: "#ffffff",
// correctLevel: QRCode.CorrectLevel.M
// });
// }
// container.on('click', function() {
// window.open(data.qrString, '_blank');
// });
// });
},
});
this.settledrequestDatatable = $('#settledrequestDatatable').DataTable({
"data": this.items.filter(item => item.status !== "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
},
{
"title": "Product",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
var imageSrc = full.productImage;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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="${imageSrc}" target="_blank">Download File</a>`;
// }
},
},
{
"title": "Requested by User",
"data": "userName",
},
{
"title": "Requested by Station",
"data": "stationName",
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Document/Picture",
"data": "document",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{
"title": "User Remark",
"data": "remarkUser",
},
{
"title": "InvMaster Remark",
"data": "remarkMasterInv",
},
{
"title": "Status",
"data": "status",
},
{
"title": "Request Date",
"data": "requestDate",
},
{
"title": "Approval Date",
"data": "approvalDate",
},
// {
// "title": "Reject",
// "data" :null,
// "render": function (data, type, row) {
// return `<button type="button" class="btn btn-danger reject-btn"
// data-id="${row.requestID}"
// data-remark="${row.remark || ''}">
// Reject
// </button>`;
// },
// "className": "align-middle",
// },
// {
// "title": "Approve",
// "data": "requestID",
// "render": function (data) {
// var approveButton = `<button type="button" class="btn btn-success approve-btn" data-id="${data}">Approve</button>`;
// return approveButton;
// },
// "className": "align-middle",
// }
],
responsive: true,
});
// Attach click event listener to the delete buttons
$('#requestDatatable tbody').on('click', '.reject-btn', function () {
const requestID = $(this).data('id');
self.rejectRequestModal(requestID);
});
$('#requestDatatable tbody').on('click', '.approve-btn', function () {
const requestID = $(this).data('id');
self.approveRequestModal(requestID);
});
$('#requestDatatable tbody').on('click', '.print-btn', function () {
const $button = $(this); // The clicked button
const $row = $button.closest('tr'); // The parent row of the button
const itemId = $button.data('id'); // Get the item ID from the button's data attribute
let imageSrc;
// Check if the table is collapsed
if ($row.hasClass('child')) {
// For collapsed view: Look for the closest `.dtr-data` that contains the img
imageSrc = $row.prev('tr').find('td:nth-child(1) img').attr('src');
} else {
// For expanded view: Find the img in the first column of the current row
imageSrc = $row.find('td:nth-child(1) img').attr('src');
}
if (imageSrc) {
self.printItem(itemId, imageSrc); // Call the print function with the itemId and imageSrc
} else {
console.error("Image source not found.");
}
});
$('#itemDatatable tbody').on('click', '.reject-btn', function () {
const $button = $(this); // The clicked button
const $row = $button.closest('tr'); // The parent row of the button
const itemId = $button.data('id'); // Get the item ID from the button's data attribute
self.printItem(itemId, imageSrc); // Call the print function with the itemId and imageSrc
});
this.loading = false;
},
async fetchRequest() {
try {
// const token = localStorage.getItem('token'); // Get the token from localStorage
const response = await fetch('/InvMainAPI/ItemRequestList', {
method: 'GET', // Specify the HTTP method
headers: {
'Content-Type': 'application/json', // Set content type
// 'Authorization': `Bearer ${token}` // Include the token in the headers
}
});
if (!response.ok) {
throw new Error('Failed to fetch item');
}
this.items = await response.json();
if (this.requestDatatable) {
this.requestDatatable.clear().destroy();
}
this.initiateTable();
}
catch (error) {
console.error('Error fetching item:', error);
}
},
async fetchProducts() {
try {
// const token = localStorage.getItem('token'); // Get the token from localStorage
const response = await fetch('/InvMainAPI/ProductList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json', // Set content type
// 'Authorization': `Bearer ${token}` // Include the token in the headers
}
});
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');
}
this.stations = await response.json(); // Get the full response object
} catch (error) {
console.error('Error fetching suppliers:', error);
}
},
async fetchStore() {
try {
const response = await fetch('/InvMainAPI/StoreList/', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Failed to fetch suppliers');
}
this.stores = await response.json(); // Get the full response object
} catch (error) {
console.error('Error fetching suppliers:', error);
}
},
async fetchUsers() {
try {
const response = await fetch('/InvMainAPI/UserList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Failed to fetch suppliers');
}
this.users = await response.json(); // Get the full response object
} catch (error) {
console.error('Error fetching suppliers:', error);
}
},
// FRONT END FUNCTIONS
//----------------------//
//Calculate Total Price
convertCurrency() {
const total = this.DefaultPrice * this.currencyRate;
this.convertPrice = total.toFixed(2);
this.DefaultPrice = this.DefaultPrice
// .replace(/[^0-9.]/g, '') // Remove non-numeric characters except decimal points
// .replace(/(\..*)\..*/g, '$1') // Allow only one decimal point
// .replace(/^(\d*\.\d{0,2})\d*$/, '$1'); // Limit to two decimal places
},
calculateWarrantyEndDate() {
// Check if DODate and warranty are valid
if (!this.DODate || isNaN(Date.parse(this.DODate))) {
this.EndWDate = null;
return;
}
const DODates = new Date(this.DODate);
const warrantyMonth = parseInt(this.warranty);
// Ensure warranty is a valid number
if (!isNaN(warrantyMonth)) {
DODates.setMonth(DODates.getMonth() + warrantyMonth);
this.EndWDate = DODates.toISOString().split('T')[0];
} else {
this.EndWDate = null;
}
},
async approveRequest() {
// if (!confirm("Are you sure you want to approve this request?")) {
// return;
// }
const formData = {
RemarkMasterInv: this.rejectremark,
};
let requestID = this.currentrequestID;
try {
const response = await fetch(`/InvMainAPI/ApproveRequest/${requestID}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
alert(result.message);
//static update
const row = $(`.approve-btn[data-id="${requestID}"]`).closest('tr');
let rowData = this.requestDatatable.row(row).data();
// Update the status and remark
rowData.status = "Approved";
// Remove row from requestDatatable
this.requestDatatable.row(row).remove().draw();
// Add updated row to settledrequestDatatable
this.settledrequestDatatable.row.add(rowData).draw();
} else {
alert(result.message);
}
}
catch (error) {
console.error("Error approving request:", error);
// alert("An error occurred while approving the request.");
}
finally {
this.loading = false;
}
},
async rejectRequest() {
const formData = {
RemarkMasterInv: this.rejectremark,
};
let requestID = this.currentrequestID;
try {
const response = await fetch(`/InvMainAPI/RejectRequest/${requestID}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify(formData)
});
if (response.ok) {
// If the form submission was successful, display a success message
// alert('Success!', 'Request has been Rejected.', 'success');
const row = $(`.approve-btn[data-id="${requestID}"]`).closest('tr');
let rowData = this.requestDatatable.row(row).data();
// Update the status and remark
rowData.status = "Rejected";
rowData.remarkMasterInv = this.rejectremark;
// Remove row from requestDatatable
this.requestDatatable.row(row).remove().draw();
// Add updated row to settledrequestDatatable
this.settledrequestDatatable.row.add(rowData).draw();
} else {
throw new Error('Failed to submit form.');
}
} catch (error) {
console.error('Error:', error);
// Displaying error message
alert('Inventory PSTW Error', `An error occurred: ${error.message}`, 'error');
}
finally {
this.loading = false;
}
},
async rejectRequestModal(requestID, remark) {
this.currentrequestID = requestID;
this.rejectremark = remark;
$(`#rejectModal`).modal('show');
},
async approveRequestModal(requestID, remark) {
this.currentrequestID = requestID;
this.approveremark = remark;
$(`#approveModal`).modal('show');
},
async printItem(itemId, imgSrc) {
try {
this.thisQRInfo.uniqueID = itemId;
const uniqueQR = itemId;
const container = document.getElementById("QrContainer");
if (!container) {
console.error("Container not found.");
return;
}
// Safely set image content
const sanitizedImgSrc = encodeURI(imgSrc); // Sanitize the URL
container.innerHTML = `<img src="${sanitizedImgSrc}" alt="QR Code" class="text-center " />`;
// Fetch QR information
const qrInfo = this.getPrintedQR(uniqueQR);
if (!qrInfo) {
console.error("QR Info not found.");
return;
}
this.thisQRInfo = qrInfo;
this.thisQRInfo.imgSrc = sanitizedImgSrc
this.thisQRInfo.imgContainer = container.innerHTML
$(`#QrItemModal`).modal('show'); // Show modal
}
catch (error) {
console.error("Error generating QR code:", error);
alert("An error occurred while generating the QR code.");
}
},
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;
this.currentUserCompanyDept = companyDeptData;
this.selectedCompany = companyDeptData?.companyId || "";
this.selectedDepartment = companyDeptData?.departmentId || "";
}
else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
}
catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
},
},
});
</script>
}

View File

@ -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>&nbsp;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">&times;</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>
}

File diff suppressed because it is too large Load Diff

View File

@ -1,377 +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>&nbsp;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>&nbsp;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() {
console.log(this.products)
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.productId}">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

View File

@ -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>&nbsp;{{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>
}

View File

@ -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>&nbsp;{{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>
}

View File

@ -1,867 +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;
}, {});
// Sort items from newest to oldest & filter them
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
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
grouped[itemId].movements = movements;
}
return grouped;
},
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
);
// Remove older movements
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
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();
return Object.fromEntries(
Object.entries(this.processedGroupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower)
)
);
},
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);
}
},
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
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);
}
// Distribute items based on priority
let latestMovements = getLatestMovements(this.itemMovements);
let notCompleteData = [];
let completeData = [];
let assignedData = [];
latestMovements.forEach(movement => {
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];
},
handleSorting() {
this.renderTables();
},
},
});
</script>
}

View File

@ -1,843 +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">&times;</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>&nbsp;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">&times;</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">&times;</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() {
self = this;
this.requestDatatable = $('#requestDatatable').DataTable({
"data": this.request.filter(request => request.status == "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
"createdCell": function (td, cellData, rowData, row, col) {
// Assign a unique ID to the <td> element
$(td).attr('id', `qr${cellData}`);
},
},
{
"title": "Product Name",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
var imageSrc = full.productPicture;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
console.log(full);
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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>`;
}
},
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Document / Picture",
"data": "document",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{
"title": "Remark",
"data": "remarkUser",
},
{
"title": "Station Deploy",
"data": "stationName",
"render": function (data, type, full, meta) {
return data ? data : "Self Assign";
}
},
{
"title": "Request Date",
"data": "requestDate",
},
{
"title": "Status",
"data": "status",
},
{
"title": "Delete",
"data": "requestID",
"render": function (data) {
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
return deleteButton;
},
"className": "align-middle",
}
],
responsive: true,
});
this.requestDatatable = $('#settledrequestDatatable').DataTable({
"data": this.request.filter(request => request.status !== "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
"createdCell": function (td, cellData, rowData, row, col) {
// Assign a unique ID to the <td> element
$(td).attr('id', `qr${cellData}`);
},
},
{
"title": "Status",
"data": "status",
},
{
"title": "Product Name",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
var imageSrc = full.productPicture;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
console.log(full);
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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>`;
}
},
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Station Deploy",
"data": "stationName",
"render": function (data, type, full, meta) {
return data ? data : "Self Assign";
}
},
{
"title": "Document / Picture",
"data": "document",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{
"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>
}

View File

@ -1,845 +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>
<!-- Station -->
<!-- <li class="list-group-item d-flex justify-content-between align-items-center"> -->
<!-- <span class="fw-bold"> -->
<!-- <i class="fas fa-map-marker-alt me-2 text-secondary"></i>Station: -->
<!-- </span> -->
<!-- <span class="text-muted">{{ thisItem.currentStation || 'N/A' }}</span> -->
<!-- </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">&times;</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">&times;</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">&times;</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() {
// const requiredFields = ['remark', 'consignmentNote'];
// for (let field of requiredFields) {
// if (!this[field]) {
// alert(`Request Error: Please fill in required field ${field}.`, 'warning');
// return;
// }
// }
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>
}

View File

@ -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>
}

View File

@ -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