From bbe546b60dffecf9ff5f12d0ab66a2fd3c86217f Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:40:01 +0500 Subject: [PATCH 1/7] lesson 6 --- .../Constants/ApiResourceConstants.cs | 7 ++ .../Controllers/CategoriesController.cs | 18 +++- .../Controllers/ProductsController.cs | 43 ++++++++ .../WMS.WebUI/Exceptions/ApiException.cs | 8 ++ .../ApiResponse/PaginatedApiResponse.cs | 17 ++++ WMS.WebUI/WMS.WebUI/Program.cs | 4 +- .../QueryParams/ProductQueryParameters.cs | 12 +++ WMS.WebUI/WMS.WebUI/Services/ApiClient.cs | 66 +++++++++++++ WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs | 67 +++---------- WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs | 19 +--- .../Stores/Interfaces/ICategoryStore.cs | 5 +- .../Stores/Interfaces/IProductsStore.cs | 14 +-- .../WMS.WebUI/Stores/Mocks/CategoryDto.cs | 16 +++ .../Stores/Mocks/MockProductsStore.cs | 56 ----------- WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs | 97 +++++++++++++++++++ .../WMS.WebUI/ViewModels/CategoryViewModel.cs | 15 ++- .../WMS.WebUI/Views/Categories/Index.cshtml | 40 ++++++++ .../WMS.WebUI/Views/Products/Index.cshtml | 80 +++++++++++++++ WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj | 1 + WMS.WebUI/WMS.WebUI/wwwroot/css/site.css | 6 +- 20 files changed, 451 insertions(+), 140 deletions(-) create mode 100644 WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Exceptions/ApiException.cs create mode 100644 WMS.WebUI/WMS.WebUI/Models/ApiResponse/PaginatedApiResponse.cs create mode 100644 WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs create mode 100644 WMS.WebUI/WMS.WebUI/Services/ApiClient.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Mocks/CategoryDto.cs delete mode 100644 WMS.WebUI/WMS.WebUI/Stores/Mocks/MockProductsStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml diff --git a/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs new file mode 100644 index 0000000..6da50ba --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs @@ -0,0 +1,7 @@ +namespace WMS.WebUI.Constants; + +public static class ApiResourceConstants +{ + public const string Products = nameof(Products); + public const string Categories = nameof(Categories); +} diff --git a/WMS.WebUI/WMS.WebUI/Controllers/CategoriesController.cs b/WMS.WebUI/WMS.WebUI/Controllers/CategoriesController.cs index 0736e03..24d57cc 100644 --- a/WMS.WebUI/WMS.WebUI/Controllers/CategoriesController.cs +++ b/WMS.WebUI/WMS.WebUI/Controllers/CategoriesController.cs @@ -14,12 +14,20 @@ public CategoriesController(ICategoryStore categoryStore) } // GET: Categories - public async Task<ActionResult> Index(string? searchString) + public async Task<ActionResult> Index(string? searchString,int? pageNumber) { - var categories = await _categoryStore.GetCategoriesAsync(searchString); - ViewBag.SearchString = searchString; - - return View(categories); + var categories = await _categoryStore.GetCategoriesAsync(searchString, pageNumber); + + ViewBag.SearchString = searchString ; + ViewBag.PageSize = categories.PageSize; + ViewBag.TotalPages = categories.PagesCount; + ViewBag.TotalItems = categories.TotalCount; + ViewBag.CurrentPage = categories.CurrentPage; + ViewBag.HasPreviousPage = categories.HasPreviousPage; + ViewBag.HasNextPage = categories.HasNextPage; + + + return View(categories.Data); } // GET: Categories/Details/5 diff --git a/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs b/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs new file mode 100644 index 0000000..0afd51d --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels; + +namespace WMS.WebUI.Controllers; + +public class ProductsController(IProductsStore store,ICategoryStore categoryStore) : Controller +{ + private readonly ICategoryStore _categoryStore = categoryStore; + private readonly IProductsStore _productsStore = store ?? throw new ArgumentNullException(nameof(store)); + + public async Task<IActionResult> Index( + string? search, + int? pageNumber, + int? categoryId, + decimal? maxPrice, + decimal? minPrice, + bool? lowQuantityInStock) + { + var products = await _productsStore.GetProducts(new ProductQueryParameters()); + var categories = await _categoryStore.GetCategoriesAsync(); + + PopulateViewBag(products, search); + + return View(products.Data); + } + + private void PopulateViewBag( + PaginatedApiResponse<ProductViewModel> products, + string? searchString) + { + ViewBag.SearchString = searchString; + ViewBag.PageSize = products.PageSize; + ViewBag.TotalPages = products.PagesCount; + ViewBag.TotalItems = products.TotalCount; + ViewBag.CurrentPage = products.CurrentPage; + ViewBag.HasPreviousPage = products.HasPreviousPage; + ViewBag.HasNextPage = products.HasNextPage; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Exceptions/ApiException.cs b/WMS.WebUI/WMS.WebUI/Exceptions/ApiException.cs new file mode 100644 index 0000000..d7b9a6b --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Exceptions/ApiException.cs @@ -0,0 +1,8 @@ +namespace WMS.WebUI.Exceptions; + +public class ApiException : Exception +{ + public ApiException() : base() { } + public ApiException(string message) : base(message) { } + public ApiException(string message, Exception inner) : base(message, inner) { } +} diff --git a/WMS.WebUI/WMS.WebUI/Models/ApiResponse/PaginatedApiResponse.cs b/WMS.WebUI/WMS.WebUI/Models/ApiResponse/PaginatedApiResponse.cs new file mode 100644 index 0000000..f95ff3a --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Models/ApiResponse/PaginatedApiResponse.cs @@ -0,0 +1,17 @@ +namespace WMS.WebUI.Models.PaginatedResponse; + +public class PaginatedApiResponse<T> +{ + public List<T> Data { get; set; } + public int CurrentPage { get; set; } + public int PageSize { get; set; } + public int PagesCount { get; set; } + public int TotalCount { get; set; } + public bool HasNextPage { get; set; } + public bool HasPreviousPage { get; set; } + + public PaginatedApiResponse() + { + Data = []; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Program.cs b/WMS.WebUI/WMS.WebUI/Program.cs index 32f893d..57d4167 100644 --- a/WMS.WebUI/WMS.WebUI/Program.cs +++ b/WMS.WebUI/WMS.WebUI/Program.cs @@ -1,3 +1,4 @@ +using WMS.WebUI.Services; using WMS.WebUI.Stores; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.Stores.Mocks; @@ -8,7 +9,8 @@ builder.Services.AddControllersWithViews(); builder.Services.AddScoped<IDashboardStore, DashboardStore>(); builder.Services.AddScoped<ICategoryStore, CategoryStore>(); -builder.Services.AddScoped<IProductsStore, MockProductsStore>(); +builder.Services.AddScoped<IProductsStore, ProductStore>(); +builder.Services.AddSingleton<ApiClient>(); Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("Ngo9BigBOggjHTQxAR8/V1NBaF1cXmhPYVJwWmFZfVpgfF9DaFZQTGYuP1ZhSXxXdkNjUH9WdXxUTmNeVE0="); ; diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs new file mode 100644 index 0000000..43afcf7 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs @@ -0,0 +1,12 @@ +namespace WMS.WebUI.QueryParams; + +public class ProductQueryParameters +{ + public string? Search { get; set; } + public int? CategoryId { get; set; } + public int? PageNumber { get; set; } + public int? PageSize { get; set; } = 10; + public decimal? MaxPrice { get; set; } + public decimal? MinPrice { get; set; } + public bool? LowQuantityInStock { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs b/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs new file mode 100644 index 0000000..aae3a41 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; +using System.Text; +using WMS.WebUI.Exceptions; + +namespace WMS.WebUI.Services; + +public class ApiClient +{ + private readonly HttpClient _client; + + public ApiClient() + { + _client = new() + { + BaseAddress = new Uri("https://localhost:7097/api/") + }; + } + + public async Task<T> GetAsync<T>(string resource) + { + var response = await _client.GetAsync(resource); + response.EnsureSuccessStatusCode(); + + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject<T>(json) + ?? throw new ApiException($"Could not deserialize get response for resource {resource}."); + + return result; + } + + public async Task<T> PostAsync<T>(string resource, T body) + { + var content = GetStringContent(body); + var response = await _client.PostAsync(resource, content); + response.EnsureSuccessStatusCode(); + + var responseJson = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject<T>(responseJson) + ?? throw new ApiException($"Could not deserialize post response for resource {resource}."); + + return result; + } + + public async Task PutAsync<TBody>(string resource, TBody body) + { + var content = GetStringContent(body); + var response = await _client.PutAsync(resource, content); + response.EnsureSuccessStatusCode(); + } + + public async Task DeleteAsync(string resource, int id) + { + var response = await _client.DeleteAsync(resource + "/" + id); + response.EnsureSuccessStatusCode(); + } + + private static StringContent GetStringContent<T>(T value) + { + var bodyJson = JsonConvert.SerializeObject(value); + var content = new StringContent(bodyJson, Encoding.UTF8, "application/json"); + + return content; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs b/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs index 2db82cc..04ca545 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System.Text; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.Services; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.ViewModels; @@ -7,80 +7,45 @@ namespace WMS.WebUI.Stores; public class CategoryStore : ICategoryStore { - private readonly HttpClient _client; + private readonly ApiClient _client; + private const string RESOURCE = "categories"; - public CategoryStore() + public CategoryStore(ApiClient client) { - _client = new HttpClient(); - _client.BaseAddress = new Uri("https://localhost:7097/api/"); + _client = client; } public async Task<CategoryViewModel> CreateCategoryAsync(CategoryViewModel category) { - var json = JsonConvert.SerializeObject(category); - var request = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync("categories", request); - - response.EnsureSuccessStatusCode(); - - var responseJson = await response.Content.ReadAsStringAsync(); - var createdCategory = JsonConvert.DeserializeObject<CategoryViewModel>(responseJson); - - if (createdCategory is null) - { - throw new JsonSerializationException("Error serializing Category response from API."); - } + var result = await _client.PostAsync(RESOURCE, category); - return createdCategory; + return result; } public async Task DeleteCategoryAsync(int id) { - var response = await _client.DeleteAsync($"categories/{id}"); - - response.EnsureSuccessStatusCode(); + await _client.DeleteAsync(RESOURCE, id); } - public async Task<List<CategoryViewModel>> GetCategoriesAsync(string? search = null) + public async Task<PaginatedApiResponse<CategoryViewModel>> GetCategoriesAsync(string? search = null, int? pageNumber = 1) { - var response = await _client.GetAsync($"categories?search={search}"); - - response.EnsureSuccessStatusCode(); + pageNumber ??= 1; - var json = await response.Content.ReadAsStringAsync(); - var categories = JsonConvert.DeserializeObject<List<CategoryViewModel>>(json); + var queryParams = $"?search={search}&pageNumber={pageNumber}"; + var result = await _client.GetAsync<PaginatedApiResponse<CategoryViewModel>>(RESOURCE + queryParams); - if (categories is null) - { - throw new JsonSerializationException("Error serializing Category response from API."); - } - - return categories; + return result; } public async Task<CategoryViewModel> GetCategoryByIdAsync(int id) { - var response = await _client.GetAsync($"categories/{id}"); + var category = await _client.GetAsync<CategoryViewModel>($"categories/{id}"); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var category = JsonConvert.DeserializeObject<CategoryViewModel>(json); - - if (category is null) - { - throw new JsonSerializationException("Error serializing Category response from API."); - } - return category; } public async Task UpdateCategoryAsync(CategoryViewModel category) { - var json = JsonConvert.SerializeObject(category); - var request = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await _client.PutAsync($"categories/{category.Id}", request); - - response.EnsureSuccessStatusCode(); + await _client.PutAsync($"categories/{category.Id}", category); } } diff --git a/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs index 9a4a232..5f73dd9 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using WMS.WebUI.Services; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.ViewModels; @@ -6,26 +7,16 @@ namespace WMS.WebUI.Stores; public class DashboardStore : IDashboardStore { - private readonly HttpClient _client; + private readonly ApiClient _client; - public DashboardStore() + public DashboardStore(ApiClient client) { - _client = new HttpClient(); - _client.BaseAddress = new Uri("https://localhost:7097/api/"); + _client = client; } public async Task<DashboardViewModel> Get() { - var response = await _client.GetAsync("dashboard"); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - var dashboard = JsonConvert.DeserializeObject<DashboardViewModel>(json); - - if (dashboard is null) - { - throw new InvalidCastException("Could not convert json data to Dashboard format"); - } + var dashboard = await _client.GetAsync<DashboardViewModel>("dashboard"); return dashboard; } diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICategoryStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICategoryStore.cs index f4c6469..54188d0 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICategoryStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICategoryStore.cs @@ -1,10 +1,11 @@ -using WMS.WebUI.ViewModels; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.ViewModels; namespace WMS.WebUI.Stores.Interfaces; public interface ICategoryStore { - Task<List<CategoryViewModel>> GetCategoriesAsync(string? search = null); + Task<PaginatedApiResponse<CategoryViewModel>> GetCategoriesAsync(string? search = null,int? pageNumber = 1); Task<CategoryViewModel> GetCategoryByIdAsync(int id); Task<CategoryViewModel> CreateCategoryAsync(CategoryViewModel category); Task UpdateCategoryAsync(CategoryViewModel category); diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/IProductsStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/IProductsStore.cs index 9de5f36..1ee92eb 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/IProductsStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/IProductsStore.cs @@ -1,12 +1,14 @@ -using WMS.WebUI.ViewModels; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels; namespace WMS.WebUI.Stores.Interfaces; public interface IProductsStore { - List<ProductViewModel> GetProducts(); - ProductViewModel? GetById(int id); - ProductViewModel Create(ProductViewModel product); - void Update(ProductViewModel product); - void Delete(int id); + Task<PaginatedApiResponse<ProductViewModel>> GetProducts(ProductQueryParameters queryParameters); + Task<ProductViewModel>? GetById(int id); + Task<ProductViewModel> Create(ProductViewModel product); + Task Update(ProductViewModel product); + Task Delete(int id); } diff --git a/WMS.WebUI/WMS.WebUI/Stores/Mocks/CategoryDto.cs b/WMS.WebUI/WMS.WebUI/Stores/Mocks/CategoryDto.cs new file mode 100644 index 0000000..5a80327 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Mocks/CategoryDto.cs @@ -0,0 +1,16 @@ +namespace WMS.WebUI.Stores.Mocks +{ + public class CategoryDto + { + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public int? ParentId { get; set; } + public string? Parent { get; set; } + public virtual ICollection<CategoryDto> Children { get; set; } + public CategoryDto() + { + Children = new List<CategoryDto>(); + } + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockProductsStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockProductsStore.cs deleted file mode 100644 index d3fdabb..0000000 --- a/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockProductsStore.cs +++ /dev/null @@ -1,56 +0,0 @@ -using WMS.WebUI.Stores.Interfaces; -using WMS.WebUI.ViewModels; - -namespace WMS.WebUI.Stores.Mocks; - -public class MockProductsStore : IProductsStore -{ - private static int id = 1; - private static readonly List<ProductViewModel> _products = []; - - public ProductViewModel Create(ProductViewModel product) - { - product.Id = id++; - _products.Add(product); - - return product; - } - - public void Delete(int id) - { - var product = _products.FirstOrDefault(x => x.Id == id); - - if (product is null) - { - return; - } - - var index = _products.IndexOf(product); - - if(index > 0) - { - _products.RemoveAt(index); - } - } - - public ProductViewModel? GetById(int id) - { - return _products.FirstOrDefault(x => x.Id == id); - } - - public List<ProductViewModel> GetProducts() - { - return _products.ToList(); - } - - public void Update(ProductViewModel product) - { - var productToUpdate = _products.FirstOrDefault(x => x.Id == product.Id); - var index = _products.IndexOf(product); - - if (index > 0) - { - _products[index] = product; - } - } -} diff --git a/WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs b/WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs new file mode 100644 index 0000000..b7cdae0 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs @@ -0,0 +1,97 @@ +using System.Text; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Services; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels; + +namespace WMS.WebUI.Stores; + +public class ProductStore : IProductsStore +{ + private readonly ApiClient _client; + + public ProductStore(ApiClient client) + { + _client = client; + } + + public async Task<ProductViewModel> Create(ProductViewModel product) + { + var result = await _client.PostAsync(ApiResourceConstants.Products, product); + + return result; + } + + public async Task Delete(int id) + { + await _client.DeleteAsync(ApiResourceConstants.Products, id); + } + + public async Task<PaginatedApiResponse<ProductViewModel>> GetProducts(ProductQueryParameters queryParameters) + { + var queries = BuildQueryParameters(queryParameters); + var url = string.IsNullOrEmpty(queries) ? + ApiResourceConstants.Products : + ApiResourceConstants.Products + "?" + queries; + + var result = await _client.GetAsync<PaginatedApiResponse<ProductViewModel>>(url); + + return result; + + } + + public async Task<ProductViewModel> GetById(int id) + { + var result = await _client.GetAsync<ProductViewModel>(ApiResourceConstants.Products + "/" + id); + + return result; + } + + public async Task Update(ProductViewModel product) + { + await _client.PutAsync(ApiResourceConstants.Products, product); + } + + private static string BuildQueryParameters(ProductQueryParameters queryParameters) + { + StringBuilder queryBuilder = new(); + + if (queryParameters.PageNumber.HasValue) + { + queryBuilder.Append($"pageNumber={queryParameters.PageNumber}&"); + } + else + { + queryBuilder.Append($"pageNumber=1&"); + } + + if (!string.IsNullOrWhiteSpace(queryParameters.Search)) + { + queryBuilder.Append($"Search={queryParameters.Search}&"); + } + + if (queryParameters.CategoryId.HasValue) + { + queryBuilder.Append($"CategoryId={queryParameters.CategoryId}&"); + } + + if (queryParameters.MinPrice.HasValue) + { + queryBuilder.Append($"MinPrice={queryParameters.MinPrice}&"); + } + + if (queryParameters.MaxPrice.HasValue) + { + queryBuilder.Append($"MaxPrice={queryParameters.MaxPrice}&"); + } + + if (queryParameters.LowQuantityInStock.HasValue && queryParameters.LowQuantityInStock == true) + { + queryBuilder = queryBuilder.Append($"LowQuantityInStock={queryParameters.LowQuantityInStock}"); + } + + return queryBuilder.ToString(); + } +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/CategoryViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/CategoryViewModel.cs index 68a1716..209b9f8 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/CategoryViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/CategoryViewModel.cs @@ -1,10 +1,21 @@ -namespace WMS.WebUI.ViewModels; +using WMS.WebUI.Stores.Mocks; + +namespace WMS.WebUI.ViewModels; public class CategoryViewModel { public int Id { get; set; } public string Name { get; set; } - public string? Description { get; set; } + public string Description { get; set; } + public int? ParentId { get; set; } + public string? Parent { get; set; } + public virtual ICollection<CategoryDto> Children { get; set; } + public virtual ICollection<object> Products { get; set; } + public CategoryViewModel() + { + Children = new List<CategoryDto>(); + Products = new List<object>(); + } } // read diff --git a/WMS.WebUI/WMS.WebUI/Views/Categories/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Categories/Index.cshtml index 07762f6..e352236 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Categories/Index.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Categories/Index.cshtml @@ -2,6 +2,7 @@ @{ } +<br /> <div class="mb-2"> <form asp-action="Index"> <div class="d-flex justfiy-content-between gap-4"> @@ -40,6 +41,45 @@ </div> </div> +<div class="d-flex justify-content-between"> + <div class="p-2" style="font-size: 14px;"> + <!-- First page button --> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + + <!-- Previous page button --> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + + <!-- Previous page number --> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + + <!-- Current page number --> + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + + <!-- Next page number --> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + + <!-- Next page button --> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + + <!-- Last page button --> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + + <div class="p-2"> + <p class="h6">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> +</div> + +<br /> +<br /> +<br /> + <script id="idCellTemplate" type="text/template"> <div> <a rel='nofollow' href="categories/details/${Id}">${Id}</a> diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml new file mode 100644 index 0000000..80dcf64 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml @@ -0,0 +1,80 @@ +@model List<WMS.WebUI.ViewModels.ProductViewModel> + +<br /> +<div class="mb-4"> + <form asp-action="Index"> + <div class="row g-3"> + <div class="col-md-3"> + <div class="input-group"> + <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> Search + </button> + </div> + </div> + <div class="col-md-2"> + <select class="form-control" name="categoryId" asp-items="@ViewBag.Categories"> + <option value="">All Categories</option> + </select> + </div> + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Min Sale Price" name="minPrice" value="@ViewBag.MinPrice" /> + </div> + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Max Sale Price" name="maxPrice" value="@ViewBag.MaxPrice" /> + </div> + <div class="col-md-1 d-flex justify-content-end"> + <a asp-action="Create" class="btn btn-outline-success">New +</a> + </div> + </div> + </form> +</div> + +<div class="row"> + <div class="col-12"> + <ejs-grid id="products-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" type="Text"></e-grid-column> + <e-grid-column headerText="Name" field="Name" type="Text"></e-grid-column> + <e-grid-column headerText="Sale Price" field="SalePrice" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Supply Price" field="SupplyPrice" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Quantity In Stock" field="QuantityInStock" type="Number"></e-grid-column> + <e-grid-column headerText="Category" field="Category" type="Text"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> +</div> + +<div class="d-flex justify-content-between"> + <div class="p-2" style="font-size: 14px;"> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + <div class="p-2"> + <p class="h6">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> +</div> + +<br /> +<br /> +<br /> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel='nofollow' href="products/details/${Id}">${Id}</a> + </div> +</script> diff --git a/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj b/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj index 424a883..fabc02c 100644 --- a/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj +++ b/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj @@ -7,6 +7,7 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Syncfusion.EJ2.AspNet.Core" Version="25.2.7" /> <PackageReference Include="Syncfusion.Licensing" Version="25.2.7" /> </ItemGroup> diff --git a/WMS.WebUI/WMS.WebUI/wwwroot/css/site.css b/WMS.WebUI/WMS.WebUI/wwwroot/css/site.css index a3d0220..6bb9689 100644 --- a/WMS.WebUI/WMS.WebUI/wwwroot/css/site.css +++ b/WMS.WebUI/WMS.WebUI/wwwroot/css/site.css @@ -69,9 +69,9 @@ body { background-color: #222b33; } - .e-grid .e-gridheader .e-headercell { - background-color: #222b33; - } + .e-gird .e-gridheader .e-headercell { + background-color: #222b33; + } .e-grid .e-gridpager { background-color: #1a222b; From a19e2392ea170df1a72d789d8ea1145ecf24fe05 Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:56:42 +0500 Subject: [PATCH 2/7] Add Suplier and Customer VIews --- .../Constants/ApiResourceConstants.cs | 2 + .../Controllers/CustomersController.cs | 131 ++++++++++++++++++ .../Controllers/ProductsController.cs | 115 ++++++++++++++- .../Controllers/SuppliersController.cs | 128 +++++++++++++++++ WMS.WebUI/WMS.WebUI/Program.cs | 5 +- .../QueryParams/BaseQueryParameters.cs | 9 ++ .../QueryParams/CustomerQueryParameters.cs | 7 + .../QueryParams/ProductQueryParameters.cs | 7 +- .../QueryParams/SupplierQueryParameters.cs | 7 + WMS.WebUI/WMS.WebUI/Services/ApiClient.cs | 7 +- .../Stores/{ => DataStores}/CategoryStore.cs | 13 +- .../Stores/DataStores/CustomerStore.cs | 77 ++++++++++ .../Stores/{ => DataStores}/DashboardStore.cs | 2 +- .../Stores/{ => DataStores}/ProductStore.cs | 15 +- .../Stores/DataStores/SupplierStore.cs | 76 ++++++++++ .../Stores/Interfaces/ICustomerStore.cs | 14 ++ .../Stores/Interfaces/ISupplierStore.cs | 14 ++ .../CustomerActionViewModel.cs | 12 ++ .../CustomerDisplayViewModel.cs | 14 ++ .../WMS.WebUI/ViewModels/ProductViewModel.cs | 6 +- .../SupplierActionViewModel.cs | 15 ++ .../SupplierDisplayViewModel.cs | 13 ++ .../WMS.WebUI/Views/Categories/Delete.cshtml | 2 +- .../WMS.WebUI/Views/Customers/Create.cshtml | 59 ++++++++ .../WMS.WebUI/Views/Customers/Delete.cshtml | 53 +++++++ .../WMS.WebUI/Views/Customers/Details.cshtml | 60 ++++++++ .../WMS.WebUI/Views/Customers/Edit.cshtml | 58 ++++++++ .../WMS.WebUI/Views/Customers/Index.cshtml | 77 ++++++++++ .../WMS.WebUI/Views/Products/Create.cshtml | 72 ++++++++++ .../WMS.WebUI/Views/Products/Delete.cshtml | 44 ++++++ .../WMS.WebUI/Views/Products/Details.cshtml | 47 +++++++ .../WMS.WebUI/Views/Products/Edit.cshtml | 61 ++++++++ .../WMS.WebUI/Views/Products/Index.cshtml | 14 +- .../WMS.WebUI/Views/Shared/_Sidebar.cshtml | 36 ++++- .../WMS.WebUI/Views/Suppliers/Create.cshtml | 45 ++++++ .../WMS.WebUI/Views/Suppliers/Delete.cshtml | 41 ++++++ .../WMS.WebUI/Views/Suppliers/Details.cshtml | 50 +++++++ .../WMS.WebUI/Views/Suppliers/Edit.cshtml | 45 ++++++ .../WMS.WebUI/Views/Suppliers/Index.cshtml | 77 ++++++++++ 39 files changed, 1491 insertions(+), 39 deletions(-) create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/CustomersController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/SuppliersController.cs create mode 100644 WMS.WebUI/WMS.WebUI/QueryParams/BaseQueryParameters.cs create mode 100644 WMS.WebUI/WMS.WebUI/QueryParams/CustomerQueryParameters.cs create mode 100644 WMS.WebUI/WMS.WebUI/QueryParams/SupplierQueryParameters.cs rename WMS.WebUI/WMS.WebUI/Stores/{ => DataStores}/CategoryStore.cs (81%) create mode 100644 WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs rename WMS.WebUI/WMS.WebUI/Stores/{ => DataStores}/DashboardStore.cs (92%) rename WMS.WebUI/WMS.WebUI/Stores/{ => DataStores}/ProductStore.cs (83%) create mode 100644 WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICustomerStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplierStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierDisplayViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/Views/Customers/Create.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Customers/Delete.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Customers/Details.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Customers/Edit.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Products/Delete.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Products/Details.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Products/Edit.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Suppliers/Delete.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Suppliers/Details.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Suppliers/Edit.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Suppliers/Index.cshtml diff --git a/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs index 6da50ba..66a003c 100644 --- a/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs +++ b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs @@ -4,4 +4,6 @@ public static class ApiResourceConstants { public const string Products = nameof(Products); public const string Categories = nameof(Categories); + public const string Customers = nameof(Customers); + public const string Suppliers = nameof(Suppliers); } diff --git a/WMS.WebUI/WMS.WebUI/Controllers/CustomersController.cs b/WMS.WebUI/WMS.WebUI/Controllers/CustomersController.cs new file mode 100644 index 0000000..c7eb58f --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/CustomersController.cs @@ -0,0 +1,131 @@ +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.CustomerViewModels; + +namespace WMS.WebUI.Controllers; + +public class CustomersController(ICustomerStore customerStore) : Controller +{ + private readonly ICustomerStore _customerStore = customerStore; + public async Task<IActionResult> Index( + string? searchString, + decimal? balanceGreaterThan, + decimal? balanceLessThan, + int? pageNumber) + { + var queryParameters = new CustomerQueryParameters() + { + Search = searchString, + BalanceGreaterThan = balanceGreaterThan, + BalanceLessThan = balanceLessThan, + PageNumber = pageNumber + }; + + var customers = await _customerStore.GetCustomers(queryParameters); + + PopulateViewBag(customers, queryParameters); + + return View(customers.Data); + } + public async Task<IActionResult> Details(int id) + { + var customer = await _customerStore.GetById(id); + return View(customer); + } + [HttpGet] + public async Task<IActionResult> Create() + { + return View(); + } + [HttpPost] + public async Task<IActionResult> Create(CustomerActionViewModel customerActionViewModel) + { + if (!ModelState.IsValid) + { + return View(customerActionViewModel); + } + var createdCustomer = await _customerStore.Create(customerActionViewModel); + + return RedirectToAction(nameof(Details), new { id = createdCustomer.Id }); + } + [HttpGet] + public async Task<IActionResult> Edit(int id) + { + var customer = await _customerStore.GetById(id); + + var customerForEdit = ParseCustomerActionViewModel(customer); + + return View(customerForEdit); + } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task<IActionResult> Edit(int id,CustomerActionViewModel customerActionViewModel) + { + if (!ModelState.IsValid) + { + return View(customerActionViewModel); + } + if(customerActionViewModel.Id != id ) + { + return BadRequest("Does not match id with customer id"); + } + + await _customerStore.Update(customerActionViewModel); + + + return RedirectToAction(nameof(Details),new {id = customerActionViewModel.Id}); + } + [HttpGet] + public async Task<IActionResult> Delete(int id) + { + var customer = await _customerStore.GetById(id); + + return View(customer); + } + [HttpPost,ActionName("Delete")] + [ValidateAntiForgeryToken] + public async Task<IActionResult> DeleteConfirmed(int id) + { + await _customerStore.Delete(id); + return RedirectToAction(nameof(Index)); + } + private void PopulateViewBag(PaginatedApiResponse<CustomerDisplayViewModel> customers,CustomerQueryParameters queryParameters) + { + ViewBag.BalanceLessThan = queryParameters.BalanceLessThan; + ViewBag.BalanceGreaterThan= queryParameters.BalanceGreaterThan; + ViewBag.SearchString = queryParameters.Search; + + ViewBag.PageSize = customers.PageSize; + ViewBag.TotalPages = customers.PagesCount; + ViewBag.TotalItems = customers.TotalCount; + ViewBag.CurrentPage = customers.CurrentPage; + ViewBag.HasPreviousPage = customers.HasPreviousPage; + ViewBag.HasNextPage = customers.HasNextPage; + } + private CustomerActionViewModel ParseCustomerActionViewModel(CustomerDisplayViewModel model) + { + string[] nameParts = model.FullName.Split(new char[] { ' ' }, 2); + + string firstName = nameParts[0]; // First part is the first name + + // If there's more than one part, the rest is considered the last name + string lastName = (nameParts.Length > 1) ? nameParts[1] : ""; + + var customerForEdit = new CustomerActionViewModel() + { + Id = model.Id, + FirstName = firstName, + LastName = lastName, + Balance = model.Balance, + Address = model.Address, + Discount = model.Discount, + PhoneNumber = model.PhoneNumber, + }; + return customerForEdit; + } + +} diff --git a/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs b/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs index 0afd51d..91adc39 100644 --- a/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs +++ b/WMS.WebUI/WMS.WebUI/Controllers/ProductsController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using WMS.WebUI.Models.PaginatedResponse; using WMS.WebUI.QueryParams; @@ -13,21 +14,125 @@ public class ProductsController(IProductsStore store,ICategoryStore categoryStor private readonly IProductsStore _productsStore = store ?? throw new ArgumentNullException(nameof(store)); public async Task<IActionResult> Index( - string? search, + string? searchString, int? pageNumber, int? categoryId, decimal? maxPrice, decimal? minPrice, - bool? lowQuantityInStock) + string? isLowQuantity + ) { - var products = await _productsStore.GetProducts(new ProductQueryParameters()); + var queryParameters = new ProductQueryParameters() + { + Search = searchString, + PageNumber = pageNumber, + CategoryId = categoryId, + MaxPrice = maxPrice, + MinPrice = minPrice, + IsLowQuantity = isLowQuantity == "on", + }; + + var products = await _productsStore.GetProducts(queryParameters); + //Problem CAtegories get with pagination with max pagesize=15, I dont choose 16 category var categories = await _categoryStore.GetCategoriesAsync(); - PopulateViewBag(products, search); + ViewBag.Categories = new SelectList(categories.Data.OrderBy(x => x.Name), "Id", "Name", categoryId); + ViewBag.MinPrice = minPrice; + ViewBag.IsLowQuantity = isLowQuantity == "on"; + ViewBag.MaxPrice = maxPrice; + ViewBag.SearchString = searchString; + + PopulateViewBag(products, searchString); return View(products.Data); } + public async Task<IActionResult> Details(int id) + { + var product = await _productsStore.GetById(id); + + if(product is null) + return NotFound(); + + return View(product); + } + public async Task<IActionResult> Create() + { + var categories = await _categoryStore.GetCategoriesAsync(); + + ViewBag.Categories = new SelectList(categories.Data, "Id", "Name"); + + return View(); + } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task<IActionResult> Create(ProductViewModel product) + { + if (!ModelState.IsValid) + { + var categories = await _categoryStore.GetCategoriesAsync(); + + ViewBag.Categories = new SelectList(categories.Data, "Id", "Name", categories); + return View(product); + } + var createdProduct = await _productsStore.Create(product); + return RedirectToAction(nameof(Details),new { id = createdProduct.Id }); + } + public async Task<IActionResult> Edit(int id) + { + var product = await _productsStore.GetById(id); + + if (product is null) + return NotFound(); + + var categories = await _categoryStore.GetCategoriesAsync(); + + ViewBag.Categories = new SelectList(categories.Data, "Id", "Name"); + + return View(product); + } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task<IActionResult> Edit(int id,ProductViewModel product) + { + if (!ModelState.IsValid) + { + var categories = await _categoryStore.GetCategoriesAsync(); + ViewBag.Categories = new SelectList(categories.Data, "Id", "Name"); + + return View(product); + } + if (product.Id != id) + { + return BadRequest("Does not match id with product id"); + } + await _productsStore.Update(product); + return RedirectToAction(nameof(Details),new { id = product.Id }); + } + + + [HttpGet] + public async Task<IActionResult> Delete(int id) + { + var product = await _productsStore.GetById(id); + + if(product is null) + return NotFound(); + + return View(product); + } + [HttpPost(),ActionName(nameof(Delete))] + public async Task<IActionResult> DeleteConfirmed(int id) + { + var product = await _productsStore.GetById(id); + + if (product is null) + return NotFound(); + + await _productsStore.Delete(id); + + return RedirectToAction(nameof(Index)); + } private void PopulateViewBag( PaginatedApiResponse<ProductViewModel> products, string? searchString) diff --git a/WMS.WebUI/WMS.WebUI/Controllers/SuppliersController.cs b/WMS.WebUI/WMS.WebUI/Controllers/SuppliersController.cs new file mode 100644 index 0000000..943ad0a --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/SuppliersController.cs @@ -0,0 +1,128 @@ +using Microsoft.AspNetCore.Mvc; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.DataStores; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SupplierViewModels; + +namespace WMS.WebUI.Controllers +{ + public class SuppliersController(ISupplierStore store) : Controller + { + private readonly ISupplierStore _supplierStore = store; + public async Task<IActionResult> Index( + string? searchString, + decimal? balanceGreaterThan, + decimal? balanceLessThan, + int? pageNumber) + { + var queryParameters = new SupplierQueryParameters() + { + Search = searchString, + BalanceGreaterThan = balanceGreaterThan, + BalanceLessThan = balanceLessThan, + PageNumber = pageNumber + }; + + var suppliers = await _supplierStore.GetSuppliers(queryParameters); + + PopulateViewBag(suppliers, queryParameters); + + return View(suppliers.Data); + } + public async Task<IActionResult> Details(int id) + { + var supplier = await _supplierStore.GetById(id); + return View(supplier); + } + [HttpGet] + public async Task<IActionResult> Create() + { + return View(); + } + [HttpPost] + public async Task<IActionResult> Create(SupplierActionViewModel supplierActionViewModel) + { + if (!ModelState.IsValid) + { + return View(supplierActionViewModel); + } + var createdSupplier = await _supplierStore.Create(supplierActionViewModel); + + return RedirectToAction(nameof(Details), new { id = createdSupplier.Id }); + } + [HttpGet] + public async Task<IActionResult> Edit(int id) + { + var supplier = await _supplierStore.GetById(id); + + var supplierForEdit = ParseSupplierActionViewModel(supplier); + + return View(supplierForEdit); + } + [HttpPost] + [ValidateAntiForgeryToken] + public async Task<IActionResult> Edit(int id, SupplierActionViewModel supplierActionViewModel) + { + if (!ModelState.IsValid) + { + return View(supplierActionViewModel); + } + if (supplierActionViewModel.Id != id) + { + return BadRequest("Does not match id with supplier id"); + } + + await _supplierStore.Update(supplierActionViewModel); + + + return RedirectToAction(nameof(Details), new { id = supplierActionViewModel.Id }); + } + [HttpGet] + public async Task<IActionResult> Delete(int id) + { + var supplier = await _supplierStore.GetById(id); + + return View(supplier); + } + [HttpPost, ActionName("Delete")] + [ValidateAntiForgeryToken] + public async Task<IActionResult> DeleteConfirmed(int id) + { + await _supplierStore.Delete(id); + return RedirectToAction(nameof(Index)); + } + private void PopulateViewBag(PaginatedApiResponse<SupplierDisplayViewModel> suppliers, SupplierQueryParameters queryParameters) + { + ViewBag.BalanceLessThan = queryParameters.BalanceLessThan; + ViewBag.BalanceGreaterThan= queryParameters.BalanceGreaterThan; + ViewBag.SearchString = queryParameters.Search; + + ViewBag.PageSize = suppliers.PageSize; + ViewBag.TotalPages = suppliers.PagesCount; + ViewBag.TotalItems = suppliers.TotalCount; + ViewBag.CurrentPage = suppliers.CurrentPage; + ViewBag.HasPreviousPage = suppliers.HasPreviousPage; + ViewBag.HasNextPage = suppliers.HasNextPage; + } + private SupplierActionViewModel ParseSupplierActionViewModel(SupplierDisplayViewModel model) + { + string[] nameParts = model.FullName.Split(new char[] { ' ' }, 2); + + string firstName = nameParts[0]; // First part is the first name + + // If there's more than one part, the rest is considered the last name + string lastName = (nameParts.Length > 1) ? nameParts[1] : ""; + + var supplierForEdit = new SupplierActionViewModel() + { + Id = model.Id, + FirstName = firstName, + LastName = lastName, + Balance = model.Balance, + PhoneNumber = model.PhoneNumber, + }; + return supplierForEdit; + } + } +} diff --git a/WMS.WebUI/WMS.WebUI/Program.cs b/WMS.WebUI/WMS.WebUI/Program.cs index 57d4167..faa76cc 100644 --- a/WMS.WebUI/WMS.WebUI/Program.cs +++ b/WMS.WebUI/WMS.WebUI/Program.cs @@ -1,5 +1,5 @@ using WMS.WebUI.Services; -using WMS.WebUI.Stores; +using WMS.WebUI.Stores.DataStores; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.Stores.Mocks; @@ -10,6 +10,9 @@ builder.Services.AddScoped<IDashboardStore, DashboardStore>(); builder.Services.AddScoped<ICategoryStore, CategoryStore>(); builder.Services.AddScoped<IProductsStore, ProductStore>(); +builder.Services.AddScoped<ICustomerStore,CustomerStore>(); +builder.Services.AddScoped<ISupplierStore,SupplierStore>(); + builder.Services.AddSingleton<ApiClient>(); Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("Ngo9BigBOggjHTQxAR8/V1NBaF1cXmhPYVJwWmFZfVpgfF9DaFZQTGYuP1ZhSXxXdkNjUH9WdXxUTmNeVE0="); ; diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/BaseQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/BaseQueryParameters.cs new file mode 100644 index 0000000..b57e589 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/QueryParams/BaseQueryParameters.cs @@ -0,0 +1,9 @@ +namespace WMS.WebUI.QueryParams +{ + public abstract class BaseQueryParameters + { + public string? Search { get; set; } + public int? PageNumber { get; set; } = 1; + public int? PageSize { get; set; } = 15; + } +} diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/CustomerQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/CustomerQueryParameters.cs new file mode 100644 index 0000000..d105a6c --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/QueryParams/CustomerQueryParameters.cs @@ -0,0 +1,7 @@ +namespace WMS.WebUI.QueryParams; + +public class CustomerQueryParameters : BaseQueryParameters +{ + public decimal? BalanceGreaterThan { get; set; } + public decimal? BalanceLessThan { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs index 43afcf7..b4d0ea4 100644 --- a/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs +++ b/WMS.WebUI/WMS.WebUI/QueryParams/ProductQueryParameters.cs @@ -1,12 +1,9 @@ namespace WMS.WebUI.QueryParams; -public class ProductQueryParameters +public class ProductQueryParameters : BaseQueryParameters { - public string? Search { get; set; } public int? CategoryId { get; set; } - public int? PageNumber { get; set; } - public int? PageSize { get; set; } = 10; public decimal? MaxPrice { get; set; } public decimal? MinPrice { get; set; } - public bool? LowQuantityInStock { get; set; } + public bool? IsLowQuantity { get; set; } } diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/SupplierQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/SupplierQueryParameters.cs new file mode 100644 index 0000000..8b3c68b --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/QueryParams/SupplierQueryParameters.cs @@ -0,0 +1,7 @@ +namespace WMS.WebUI.QueryParams; + +public class SupplierQueryParameters : BaseQueryParameters +{ + public decimal? BalanceGreaterThan { get; set; } + public decimal? BalanceLessThan { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs b/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs index aae3a41..08f84d6 100644 --- a/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs +++ b/WMS.WebUI/WMS.WebUI/Services/ApiClient.cs @@ -12,15 +12,14 @@ public ApiClient() { _client = new() { - BaseAddress = new Uri("https://localhost:7097/api/") + BaseAddress = new Uri("https://localhost:7108/api/") }; } public async Task<T> GetAsync<T>(string resource) { var response = await _client.GetAsync(resource); - response.EnsureSuccessStatusCode(); - + response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); @@ -30,7 +29,7 @@ public async Task<T> GetAsync<T>(string resource) return result; } - public async Task<T> PostAsync<T>(string resource, T body) + public async Task<T> PostAsync<T,TBody>(string resource, TBody body) { var content = GetStringContent(body); var response = await _client.PostAsync(resource, content); diff --git a/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CategoryStore.cs similarity index 81% rename from WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs rename to WMS.WebUI/WMS.WebUI/Stores/DataStores/CategoryStore.cs index 04ca545..5aee98d 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/CategoryStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CategoryStore.cs @@ -1,14 +1,15 @@ -using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; using WMS.WebUI.Services; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.ViewModels; -namespace WMS.WebUI.Stores; +namespace WMS.WebUI.Stores.DataStores; public class CategoryStore : ICategoryStore { private readonly ApiClient _client; - private const string RESOURCE = "categories"; + private const string RESOURCE = ApiResourceConstants.Categories; public CategoryStore(ApiClient client) { @@ -17,8 +18,8 @@ public CategoryStore(ApiClient client) public async Task<CategoryViewModel> CreateCategoryAsync(CategoryViewModel category) { - var result = await _client.PostAsync(RESOURCE, category); - + var result = await _client.PostAsync<CategoryViewModel,CategoryViewModel>(RESOURCE, category); + return result; } @@ -40,7 +41,7 @@ public async Task<PaginatedApiResponse<CategoryViewModel>> GetCategoriesAsync(st public async Task<CategoryViewModel> GetCategoryByIdAsync(int id) { var category = await _client.GetAsync<CategoryViewModel>($"categories/{id}"); - + return category; } diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs new file mode 100644 index 0000000..063ea23 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs @@ -0,0 +1,77 @@ +using System.Reflection.Metadata.Ecma335; +using System.Runtime.InteropServices; +using System.Text; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Services; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.CustomerViewModels; + +namespace WMS.WebUI.Stores.DataStores; + +public class CustomerStore(ApiClient apiClient) : ICustomerStore +{ + private readonly ApiClient _apiClient = apiClient; + public async Task<CustomerDisplayViewModel> Create(CustomerActionViewModel customer) + { + var createdCustomer = await _apiClient.PostAsync<CustomerDisplayViewModel + ,CustomerActionViewModel>(ApiResourceConstants.Customers,customer); + + return createdCustomer; + } + + public async Task Delete(int id) + { + await _apiClient.DeleteAsync(ApiResourceConstants.Customers,id); + } + + public async Task<CustomerDisplayViewModel> GetById(int id) + { + var customer = await _apiClient.GetAsync<CustomerDisplayViewModel>(ApiResourceConstants.Customers + "/" + id); + return customer; + } + + public async Task<PaginatedApiResponse<CustomerDisplayViewModel>> GetCustomers(CustomerQueryParameters queryParameters) + { + var query = BuildQueryParameters(queryParameters); + + var url = string.IsNullOrEmpty(query) ? + ApiResourceConstants.Customers : + ApiResourceConstants.Customers + "?" + query; + + var customers = await _apiClient.GetAsync<PaginatedApiResponse<CustomerDisplayViewModel>>(url); + + return customers; + } + + public async Task Update(CustomerActionViewModel customer) + { + await _apiClient.PutAsync(ApiResourceConstants.Customers, customer); + } + private string BuildQueryParameters(CustomerQueryParameters queryParameters) + { + var query = new StringBuilder(); + if (queryParameters.PageNumber.HasValue) + { + query.Append($"pageNumber={queryParameters.PageNumber}&"); + } + else + { + query.Append($"pageNumber=1&"); + } + if (!string.IsNullOrWhiteSpace(queryParameters.Search)) + { + query.Append($"Search={queryParameters.Search}&"); + } + if (queryParameters.BalanceGreaterThan.HasValue) + { + query.Append($"BalanceGreaterThan={queryParameters.BalanceGreaterThan}&"); + } + if (queryParameters.BalanceLessThan.HasValue) + { + query.Append($"BalanceLessThan={queryParameters.BalanceLessThan}&"); + } + return query.ToString(); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/DashboardStore.cs similarity index 92% rename from WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs rename to WMS.WebUI/WMS.WebUI/Stores/DataStores/DashboardStore.cs index 5f73dd9..1672f53 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DashboardStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/DashboardStore.cs @@ -3,7 +3,7 @@ using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.ViewModels; -namespace WMS.WebUI.Stores; +namespace WMS.WebUI.Stores.DataStores; public class DashboardStore : IDashboardStore { diff --git a/WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/ProductStore.cs similarity index 83% rename from WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs rename to WMS.WebUI/WMS.WebUI/Stores/DataStores/ProductStore.cs index b7cdae0..a5f81f1 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/ProductStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/ProductStore.cs @@ -6,7 +6,7 @@ using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.ViewModels; -namespace WMS.WebUI.Stores; +namespace WMS.WebUI.Stores.DataStores; public class ProductStore : IProductsStore { @@ -19,9 +19,9 @@ public ProductStore(ApiClient client) public async Task<ProductViewModel> Create(ProductViewModel product) { - var result = await _client.PostAsync(ApiResourceConstants.Products, product); - - return result; + var result = await _client.PostAsync<ProductViewModel,ProductViewModel>(ApiResourceConstants.Products, product); + + return result; } public async Task Delete(int id) @@ -39,7 +39,6 @@ public async Task<PaginatedApiResponse<ProductViewModel>> GetProducts(ProductQue var result = await _client.GetAsync<PaginatedApiResponse<ProductViewModel>>(url); return result; - } public async Task<ProductViewModel> GetById(int id) @@ -51,7 +50,7 @@ public async Task<ProductViewModel> GetById(int id) public async Task Update(ProductViewModel product) { - await _client.PutAsync(ApiResourceConstants.Products, product); + await _client.PutAsync(ApiResourceConstants.Products + "/" + product.Id, product); } private static string BuildQueryParameters(ProductQueryParameters queryParameters) @@ -87,9 +86,9 @@ private static string BuildQueryParameters(ProductQueryParameters queryParameter queryBuilder.Append($"MaxPrice={queryParameters.MaxPrice}&"); } - if (queryParameters.LowQuantityInStock.HasValue && queryParameters.LowQuantityInStock == true) + if (queryParameters.IsLowQuantity.HasValue && queryParameters.IsLowQuantity == true) { - queryBuilder = queryBuilder.Append($"LowQuantityInStock={queryParameters.LowQuantityInStock}"); + queryBuilder = queryBuilder.Append($"IsLowQuantity={queryParameters.IsLowQuantity}"); } return queryBuilder.ToString(); diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs new file mode 100644 index 0000000..7035584 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs @@ -0,0 +1,76 @@ +using System.Text; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Services; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SupplierViewModels; + +namespace WMS.WebUI.Stores.DataStores; + +public class SupplierStore(ApiClient apiClient) : ISupplierStore +{ + private readonly ApiClient _apiClient = apiClient; + public async Task<SupplierDisplayViewModel> Create(SupplierActionViewModel supplier) + { + var createdSupplier = await _apiClient.PostAsync<SupplierDisplayViewModel + ,SupplierActionViewModel>(ApiResourceConstants.Suppliers, supplier); + + return createdSupplier; + } + + public async Task Delete(int id) + { + await _apiClient.DeleteAsync(ApiResourceConstants.Suppliers,id); + } + + public async Task<SupplierDisplayViewModel> GetById(int id) + { + var supplier = await _apiClient.GetAsync<SupplierDisplayViewModel>(ApiResourceConstants.Suppliers + "/" + id); + return supplier; + } + + public async Task<PaginatedApiResponse<SupplierDisplayViewModel>> GetSuppliers(SupplierQueryParameters queryParameters) + { + var query = BuildQueryParameters(queryParameters); + + var url = string.IsNullOrEmpty(query) ? + ApiResourceConstants.Suppliers : + ApiResourceConstants.Suppliers + "?" + query; + + var suppliers = await _apiClient.GetAsync<PaginatedApiResponse<SupplierDisplayViewModel>>(url); + + return suppliers; + } + + public async Task Update(SupplierActionViewModel supplier) + { + await _apiClient.PutAsync(ApiResourceConstants.Suppliers, supplier); + } + private string BuildQueryParameters(SupplierQueryParameters queryParameters) + { + var query = new StringBuilder(); + if (queryParameters.PageNumber.HasValue) + { + query.Append($"pageNumber={queryParameters.PageNumber}&"); + } + else + { + query.Append($"pageNumber=1&"); + } + if (!string.IsNullOrWhiteSpace(queryParameters.Search)) + { + query.Append($"Search={queryParameters.Search}&"); + } + if (queryParameters.BalanceGreaterThan.HasValue) + { + query.Append($"BalanceGreaterThan={queryParameters.BalanceGreaterThan}&"); + } + if (queryParameters.BalanceLessThan.HasValue) + { + query.Append($"BalanceLessThan={queryParameters.BalanceLessThan}&"); + } + + return query.ToString(); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICustomerStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICustomerStore.cs new file mode 100644 index 0000000..4b5d1ca --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ICustomerStore.cs @@ -0,0 +1,14 @@ +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels.CustomerViewModels; + +namespace WMS.WebUI.Stores.Interfaces; + +public interface ICustomerStore +{ + Task<PaginatedApiResponse<CustomerDisplayViewModel>> GetCustomers(CustomerQueryParameters queryParameters); + Task<CustomerDisplayViewModel>GetById(int id); + Task<CustomerDisplayViewModel> Create(CustomerActionViewModel customer); + Task Update(CustomerActionViewModel customer); + Task Delete(int id); +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplierStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplierStore.cs new file mode 100644 index 0000000..44a2188 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplierStore.cs @@ -0,0 +1,14 @@ +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels.SupplierViewModels; + +namespace WMS.WebUI.Stores.Interfaces; + +public interface ISupplierStore +{ + Task<PaginatedApiResponse<SupplierDisplayViewModel>> GetSuppliers(SupplierQueryParameters queryParameters); + Task<SupplierDisplayViewModel> GetById(int id); + Task<SupplierDisplayViewModel> Create(SupplierActionViewModel supplier); + Task Update(SupplierActionViewModel supplier); + Task Delete(int id); +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs new file mode 100644 index 0000000..6aedcce --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs @@ -0,0 +1,12 @@ +namespace WMS.WebUI.ViewModels.CustomerViewModels; + +public class CustomerActionViewModel +{ + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string PhoneNumber { get; set; } + public string Address { get; set; } + public decimal Balance { get; set; } + public decimal? Discount { get; set; } = 0; +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs new file mode 100644 index 0000000..11fe7e1 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs @@ -0,0 +1,14 @@ +namespace WMS.WebUI.ViewModels.CustomerViewModels +{ + public class CustomerDisplayViewModel + { + public int Id { get; set; } + public string FullName { get; set; } + public string PhoneNumber { get; set; } + public string Address { get; set; } + public decimal Balance { get; set; } + public decimal? Discount { get; set; } = 0; + //public ICollection<SaleDto> Sales { get; set; } + + } +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/ProductViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/ProductViewModel.cs index ea736c6..a59fd76 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/ProductViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/ProductViewModel.cs @@ -1,4 +1,6 @@ -namespace WMS.WebUI.ViewModels; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace WMS.WebUI.ViewModels; public class ProductViewModel { @@ -9,7 +11,7 @@ public class ProductViewModel public decimal SupplyPrice { get; set; } public int QuantityInStock { get; set; } public int LowQuantityAmount { get; set; } - + [ValidateNever] public string Category { get; set; } public int CategoryId { get; set; } } diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs new file mode 100644 index 0000000..5d0bade --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs @@ -0,0 +1,15 @@ +namespace WMS.WebUI.ViewModels.SupplierViewModels; + +public class SupplierActionViewModel +{ + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string PhoneNumber { get; set; } + public decimal Balance { get; set; } + //public ICollection<SupplyDto> Supplies { get; set; } + //public SupplierDto() + //{ + // Supplies = new List<SupplyDto>(); + //} +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierDisplayViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierDisplayViewModel.cs new file mode 100644 index 0000000..e990e29 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierDisplayViewModel.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.SupplierViewModels; + +public class SupplierDisplayViewModel +{ + public int Id { get; set; } + [DisplayName("Full name")] + public string FullName{ get; set; } + [DisplayName("Phone number")] + public string PhoneNumber { get; set; } + public decimal Balance { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Categories/Delete.cshtml b/WMS.WebUI/WMS.WebUI/Views/Categories/Delete.cshtml index 41959fc..b77efc5 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Categories/Delete.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Categories/Delete.cshtml @@ -20,7 +20,7 @@ <dt class="col-sm-2"> @Html.DisplayNameFor(model => model.Description) </dt> - <dd class="col-sm-10"> + <dd class="col-sm-10"> @Html.DisplayFor(model => model.Description) </dd> </dl> diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Create.cshtml new file mode 100644 index 0000000..4610820 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Create.cshtml @@ -0,0 +1,59 @@ +@model WMS.WebUI.ViewModels.CustomerViewModels.CustomerActionViewModel + +@{ + ViewData["Title"] = "Create Customer"; +} +<h2 class="m-5"> Create Customer </h2> +<hr /> + +<div class="row m-5"> + <div class="col-md-6"> + <form asp-action="Create" method="post"> + <div asp-validation-summary="ModelOnly" class="text-danger"></div> + <div class="form-group"> + <label asp-for="FirstName"></label> + <input asp-for="FirstName" class="form-control" /> + <span asp-validation-for="FirstName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="LastName"></label> + <input asp-for="LastName" class="form-control" /> + <span asp-validation-for="LastName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Address"></label> + <input asp-for="Address" type="text" class="form-control" /> + <span asp-validation-for="Address" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Balance"></label> + <input asp-for="Balance" class="form-control" /> + <span asp-validation-for="Balance" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="PhoneNumber"></label> + <input asp-for="PhoneNumber" type="tel" class="form-control" /> + <span asp-validation-for="PhoneNumber" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Discount"></label> + <input asp-for="Discount" type="number" class="form-control" /> + <span asp-validation-for="Discount" class="text-danger"></span> + </div> + <div class="form-group mt-5"> + <a asp-action="Index" class="btn btn-outline-info"> + Back + </a> + <button type="submit" class="btn btn-success"> + Save + </button> + </div> + </form> + </div> +</div> + +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Delete.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Delete.cshtml new file mode 100644 index 0000000..0145260 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Delete.cshtml @@ -0,0 +1,53 @@ +@using WMS.WebUI.ViewModels.CustomerViewModels; +@model CustomerDisplayViewModel; + +<h3 class="m-5"> Are you sure want to delete ?</h3> +<hr /> + +<div class="m-5"> + <dl class="row"> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Id) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Id) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.FullName) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.FullName) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.PhoneNumber) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.PhoneNumber) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Address) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Address) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Balance) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Balance) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Discount) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Discount) + </dd> + </dl> +</div> + +<form asp-action="Delete" method="post" class="m-5"> + <div class="form-group"> + <a asp-action="Index" class="btn btn-outline-info">Back</a> + <button asp-route-id="@Model.Id" type="submit" class="btn btn-danger">Delete</button> + </div> +</form> \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Details.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Details.cshtml new file mode 100644 index 0000000..9edcc2a --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Details.cshtml @@ -0,0 +1,60 @@ +@using WMS.WebUI.ViewModels.CustomerViewModels; +@model CustomerDisplayViewModel; + +<div class="m-5"> + <h4> Supplier details </h4> + + <hr /> + + <dl class="row"> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Id) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Id) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.FullName) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.FullName) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.PhoneNumber) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.PhoneNumber) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Address) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Address) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Balance) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Balance) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Discount) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Discount) + </dd> + + </dl> + <div class="m-1"> + <a asp-action="Index" class="btn btn-info fa-solid fa-arrow-left"> + Back + </a> + <a asp-action="Edit" asp-route-id="@Model?.Id" class="btn btn-outline-warning fa-solid fa-pen"> + Edit + </a> + <a asp-action="Delete" asp-route-id="@Model?.Id" class="btn btn-outline-danger fa-solid fa-trash"> + Delete + </a> + </div> + +</div> \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Edit.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Edit.cshtml new file mode 100644 index 0000000..620d66b --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Edit.cshtml @@ -0,0 +1,58 @@ +@model WMS.WebUI.ViewModels.CustomerViewModels.CustomerActionViewModel + +@{ + ViewData["Title"] = "Edit Customer"; +} + + +<div class="row m-5"> + <div class="col-md-8"> + <form asp-action="Edit" method="post"> + <div asp-validation-summary="ModelOnly" class="text-danger"></div> + <div class="form-group"> + <label asp-for="FirstName"></label> + <input asp-for="FirstName" class="form-control" /> + <span asp-validation-for="FirstName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="LastName"></label> + <input asp-for="LastName" class="form-control" /> + <span asp-validation-for="LastName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Address"></label> + <input asp-for="Address" type="text" class="form-control" /> + <span asp-validation-for="Address" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Balance"></label> + <input asp-for="Balance" type="number" class="form-control" /> + <span asp-validation-for="Balance" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="PhoneNumber"></label> + <input asp-for="PhoneNumber" type="tel" class="form-control" /> + <span asp-validation-for="PhoneNumber" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Discount"></label> + <input asp-for="Discount" type="number" class="form-control" /> + <span asp-validation-for="Discount" class="text-danger"></span> + </div> + <div class="form-group mt-5"> + <a asp-action="Index" class="btn btn-outline-info"> + Back + </a> + <button type="submit" class="btn btn-success"> + Save + </button> + </div> + </form> + </div> +</div> + +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml new file mode 100644 index 0000000..7b64e43 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml @@ -0,0 +1,77 @@ +@using WMS.WebUI.ViewModels.CustomerViewModels +@model IEnumerable<CustomerDisplayViewModel> + +<div class="mb-4 ms-2"> + <form asp-action="Index" method="get"> + <div class="row g-3 align-items-center"> + <div class="col-md-3"> + <div class="input-group"> + <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> Search + </button> + </div> + </div> + + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Balance greater than" name="balanceGreaterThan" value="@ViewBag.BalanceGreaterThan" /> + </div> + + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Balance less than" name="balanceLessThan" value="@ViewBag.BalanceLessThan" /> + </div> + + <div class="col-md-1 d-flex justify-content-end"> + <a asp-action="Create" asp-controller="customers" class="btn btn-outline-success">New +</a> + </div> + + </div> + </form> +</div> + +<div class="container"> + <div class="row"> + <div class="col-12"> + <ejs-grid id="customers-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" type="Text"></e-grid-column> + <e-grid-column headerText="Full Name" field="FullName" type="Text"></e-grid-column> + <e-grid-column headerText="Phone Number" field="PhoneNumber" type="Text"></e-grid-column> + <e-grid-column headerText="Balance" field="Balance" format="C" type="Number"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + </div> +</div> + +<div class="container mt-4"> + <div class="d-flex justify-content-between align-items-center"> + <div> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + <div> + <p class="h6 mb-0">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> + </div> +</div> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel="nofollow" href="Customers/details/${Id}">${Id}</a> + </div> +</script> diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml new file mode 100644 index 0000000..3a352d9 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml @@ -0,0 +1,72 @@ +@model WMS.WebUI.ViewModels.ProductViewModel + +@{ + ViewData["Title"] = "Create Product"; +} + +<div class="m-5"> + <h4>Create Product</h4> + <hr /> + + <form asp-action="Create"> + <div class="form-group row"> + <label asp-for="Name" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input asp-for="Name" class="form-control" /> + <span asp-validation-for="Name" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="Description" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <textarea asp-for="Description" class="form-control"></textarea> + <span asp-validation-for="Description" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="SalePrice" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input asp-for="SalePrice" class="form-control" /> + <span asp-validation-for="SalePrice" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="SupplyPrice" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input asp-for="SupplyPrice" class="form-control" /> + <span asp-validation-for="SupplyPrice" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="QuantityInStock" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input asp-for="QuantityInStock" class="form-control" /> + <span asp-validation-for="QuantityInStock" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="LowQuantityAmount" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input asp-for="LowQuantityAmount" class="form-control" /> + <span asp-validation-for="LowQuantityAmount" class="text-danger"></span> + </div> + </div> + <div class="form-group row"> + <label asp-for="CategoryId" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <select asp-for="CategoryId" class="form-control" asp-items="ViewBag.Categories"></select> + <span asp-validation-for="CategoryId" class="text-danger"></span> + </div> + </div> + <div class="form-group mt-3"> + <input type="submit" value="Create" class="btn btn-primary" /> + <a asp-action="Index" class="btn btn-secondary">Back to List</a> + </div> + </form> +</div> + +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Delete.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Delete.cshtml new file mode 100644 index 0000000..d16a613 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Delete.cshtml @@ -0,0 +1,44 @@ +@model WMS.WebUI.ViewModels.ProductViewModel + +@{ + ViewData["Title"] = "Delete Product"; +} +<br /> +<h2>Delete Product</h2> + +<hr /> +<div class="row"> + <div class="col-md-10 offset-md-1"> + <h4>Are you sure you want to delete this product?</h4> + <br /> + <dl class="row"> + <dt class="col-sm-3">Name</dt> + <dd class="col-sm-9">@Model.Name</dd> + + <dt class="col-sm-3">Description</dt> + <dd class="col-sm-9">@Model.Description</dd> + + <dt class="col-sm-3">Sale Price</dt> + <dd class="col-sm-9">@Model.SalePrice.ToString("C")</dd> + + <dt class="col-sm-3">Supply Price</dt> + <dd class="col-sm-9">@Model.SupplyPrice.ToString("C")</dd> + + <dt class="col-sm-3">Quantity In Stock</dt> + <dd class="col-sm-9">@Model.QuantityInStock</dd> + + <dt class="col-sm-3">Low Quantity Amount</dt> + <dd class="col-sm-9">@Model.LowQuantityAmount</dd> + + <dt class="col-sm-3">Category</dt> + <dd class="col-sm-9">@Model.Category</dd> + </dl> + <form asp-action="Delete"> + <input type="hidden" asp-for="Id" /> + <div class="form-group mt-3"> + <input type="submit" value="Delete" class="btn btn-danger" /> + <a asp-action="Index" class="btn btn-secondary">Cancel</a> + </div> + </form> + </div> +</div> diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Details.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Details.cshtml new file mode 100644 index 0000000..d75203e --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Details.cshtml @@ -0,0 +1,47 @@ +@model WMS.WebUI.ViewModels.ProductViewModel + +@{ + ViewData["Title"] = "Product Details"; +} + +<h2>Product Details</h2> + +<hr /> +<div class="row"> + <div class="col-md-10 offset-md-1"> + <dl class="row"> + <dt class="col-sm-3">Name</dt> + <dd class="col-sm-9">@Model.Name</dd> + + <dt class="col-sm-3">Description</dt> + <dd class="col-sm-9">@Model.Description</dd> + + <dt class="col-sm-3">Sale Price</dt> + <dd class="col-sm-9">@Model.SalePrice.ToString("C")</dd> + + <dt class="col-sm-3">Supply Price</dt> + <dd class="col-sm-9">@Model.SupplyPrice.ToString("C")</dd> + + <dt class="col-sm-3">Quantity In Stock</dt> + <dd class="col-sm-9">@Model.QuantityInStock</dd> + + <dt class="col-sm-3">Low Quantity Amount</dt> + <dd class="col-sm-9">@Model.LowQuantityAmount</dd> + + <dt class="col-sm-3">Category</dt> + <dd class="col-sm-9">@Model.Category</dd> + </dl> + + <div class="m-5"> + <a asp-action="Index" class="btn btn-info fa-solid fa-arrow-left"> + Back + </a> + <a asp-action="Edit" asp-route-id="@Model?.Id" class="btn btn-outline-warning fa-solid fa-pen"> + Edit + </a> + <a asp-action="Delete" asp-route-id="@Model?.Id" class="btn btn-outline-danger fa-solid fa-trash"> + Delete + </a> + </div> + </div> +</div> diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Edit.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Edit.cshtml new file mode 100644 index 0000000..ad33f58 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Edit.cshtml @@ -0,0 +1,61 @@ +@model WMS.WebUI.ViewModels.ProductViewModel + +@{ + ViewData["Title"] = "Edit Product"; +} + +<h2>Edit Product</h2> + +<hr /> +<div class="row"> + <div class="col-md-6 offset-md-3"> + <form asp-action="Edit"> + <input type="hidden" asp-for="Id" /> + <div class="form-group"> + <label asp-for="Name" class="control-label"></label> + <input asp-for="Name" class="form-control" /> + <span asp-validation-for="Name" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Description" class="control-label"></label> + <textarea asp-for="Description" class="form-control"></textarea> + <span asp-validation-for="Description" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="SalePrice" class="control-label"></label> + <input asp-for="SalePrice" class="form-control" /> + <span asp-validation-for="SalePrice" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="SupplyPrice" class="control-label"></label> + <input asp-for="SupplyPrice" class="form-control" /> + <span asp-validation-for="SupplyPrice" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="QuantityInStock" class="control-label"></label> + <input asp-for="QuantityInStock" class="form-control" /> + <span asp-validation-for="QuantityInStock" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="LowQuantityAmount" class="control-label"></label> + <input asp-for="LowQuantityAmount" class="form-control" /> + <span asp-validation-for="LowQuantityAmount" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="CategoryId" class="control-label"></label> + <select asp-for="CategoryId" class="form-control" asp-items="ViewBag.Categories"></select> + <span asp-validation-for="CategoryId" class="text-danger"></span> + </div> + <div class="form-group mt-3"> + <input type="submit" value="Save" class="btn btn-primary" /> + <a asp-action="Index" class="btn btn-secondary">Back to List</a> + </div> + </form> + </div> +</div> + +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml index 80dcf64..314f21d 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Index.cshtml @@ -2,8 +2,8 @@ <br /> <div class="mb-4"> - <form asp-action="Index"> - <div class="row g-3"> + <form asp-action="Index" method="get"> + <div class="row g-3 align-items-center"> <div class="col-md-3"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> @@ -23,8 +23,16 @@ <div class="col-md-2"> <input type="number" class="form-control" placeholder="Max Sale Price" name="maxPrice" value="@ViewBag.MaxPrice" /> </div> + <div class="col-md-2"> + <div class="form-check d-flex align-items-center"> + <input class="form-check-input" type="checkbox" name="isLowQuantity" @(ViewBag.IsLowQuantity ? "checked" : "")> + <label class="form-check-label ms-1" for="isLowQuantity"> + <i class="fa-solid fa-exclamation-triangle me-1"></i> <span style="font-size: 1.3rem;">Low quantity</span> + </label> + </div> + </div> <div class="col-md-1 d-flex justify-content-end"> - <a asp-action="Create" class="btn btn-outline-success">New +</a> + <a asp-action="Create" asp-controller="products" class="btn btn-outline-success">New +</a> </div> </div> </form> diff --git a/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml b/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml index 41ff20a..e681bcc 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml @@ -9,7 +9,7 @@ menuItems.Add(new { text = "Dashboard", - url = "/", + url = "/home", iconCss = "fa-solid fa-box" }); menuItems.Add(new @@ -46,13 +46,43 @@ { text = "Transactions", url = "#", - iconCss = "fa-solid fa-money-bill-transfer" + iconCss = "fa-solid fa-money-bill-transfer", + items = new List<object> + { + new + { + text = "Sales", + url = "/sales", + iconCss = "fa-solid fa-shopping-cart" + }, + new + { + text = "Supplies", + url = "/supplies", + iconCss = "fa-solid fa-truck" + } + } }); menuItems.Add(new { text = "Partners", url = "#", - iconCss = "fa-solid fa-users" + iconCss = "fa-solid fa-users", + items = new List<object>() + { + new + { + text = "Customers", + url = "/customers", + iconCss = "fa-solid fa-user-friends" + }, + new + { + text = "Suppliers", + url = "/suppliers", + iconCss = "fa-solid fa-truck" + } + } }); menuItems.Add(new diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml new file mode 100644 index 0000000..346c26f --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml @@ -0,0 +1,45 @@ +@using WMS.WebUI.ViewModels.SupplierViewModels; +@model SupplierActionViewModel; + +<h3 class="m-5" > Create Supplier </h3> +<hr /> +<div class="row m-5"> + <div class="col-md-8"> + <form asp-action="Create" method="post"> + <div asp-validation-summary="ModelOnly" class="text-danger"></div> + <div class="form-group"> + <label asp-for="FirstName"></label> + <input asp-for="FirstName" class="form-control"/> + <span asp-validation-for="FirstName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="LastName"></label> + <input asp-for="LastName" class="form-control" /> + <span asp-validation-for="LastName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="PhoneNumber"></label> + <input asp-for="PhoneNumber" type="tel" class="form-control" /> + <span asp-validation-for="PhoneNumber" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Balance"></label> + <input asp-for="Balance" class="form-control" /> + <span asp-validation-for="Balance" class="text-danger"></span> + </div> + <div class="form-group mt-5"> + <a asp-action="Index" class="btn btn-outline-info"> + Back + </a> + <button type="submit" class="btn btn-success"> + Save + </button> + </div> + </form> + </div> +</div> +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Delete.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Delete.cshtml new file mode 100644 index 0000000..2156eae --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Delete.cshtml @@ -0,0 +1,41 @@ +@using WMS.WebUI.ViewModels.SupplierViewModels; +@model SupplierDisplayViewModel ; + +<h3 class="m-5"> Are you sure want to delete ?</h3> +<hr /> + +<div class="m-5"> + <dl class="row"> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Id) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Id) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.FullName) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.FullName) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.PhoneNumber) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.PhoneNumber) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Balance) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Balance) + </dd> + </dl> +</div> + +<form asp-action="Delete" method="post" class="m-5"> + <div class="form-group"> + <a asp-action="Index" class="btn btn-outline-info">Back</a> + <button asp-route-id="@Model.Id" type="submit" class="btn btn-danger">Delete</button> + </div> +</form> \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Details.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Details.cshtml new file mode 100644 index 0000000..9072dc4 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Details.cshtml @@ -0,0 +1,50 @@ +@using WMS.WebUI.ViewModels.SupplierViewModels; +@model SupplierDisplayViewModel; + +<div class="m-5"> + <h4> Supplier details </h4> + + <hr /> + + <dl class="row"> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Id) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Id) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.FullName) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.FullName) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.PhoneNumber) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.PhoneNumber) + </dd> + <dt class="col-sm-4"> + @Html.DisplayNameFor(model => model.Balance) + </dt> + <dd class="col-sm-8"> + @Html.DisplayFor(model => model.Balance) + </dd> + + </dl> + <br /> + <br /> + + <div class="m-1"> + <a asp-action="Index" class="btn btn-info fa-solid fa-arrow-left"> + Back + </a> + <a asp-action="Edit" asp-route-id="@Model?.Id" class="btn btn-outline-warning fa-solid fa-pen"> + Edit + </a> + <a asp-action="Delete" asp-route-id="@Model?.Id" class="btn btn-outline-danger fa-solid fa-trash"> + Delete + </a> + </div> +</div> \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Edit.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Edit.cshtml new file mode 100644 index 0000000..d9d3f9f --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Edit.cshtml @@ -0,0 +1,45 @@ +@using WMS.WebUI.ViewModels.SupplierViewModels; +@model SupplierActionViewModel; + +<h3 class="m-5"> Edit Supplier </h3> + +<div class="row m-5"> + <div class="col-md-8"> + <form asp-action="Edit" method="post"> + <div asp-validation-summary="ModelOnly" class="text-danger"></div> + <div class="form-group"> + <label asp-for="FirstName"></label> + <input asp-for="FirstName" class="form-control" /> + <span asp-validation-for="FirstName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="LastName"></label> + <input asp-for="LastName" class="form-control" /> + <span asp-validation-for="LastName" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="PhoneNumber"></label> + <input asp-for="PhoneNumber" type="tel" class="form-control" /> + <span asp-validation-for="PhoneNumber" class="text-danger"></span> + </div> + <div class="form-group"> + <label asp-for="Balance"></label> + <input asp-for="Balance" class="form-control" /> + <span asp-validation-for="Balance" class="text-danger"></span> + </div> + <div class="form-group mt-5"> + <a asp-action="Index" class="btn btn-outline-info"> + Back + </a> + <button type="submit" class="btn btn-success"> + Save + </button> + </div> + </form> + </div> +</div> +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Index.cshtml new file mode 100644 index 0000000..076711d --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Index.cshtml @@ -0,0 +1,77 @@ +@using WMS.WebUI.ViewModels.SupplierViewModels +@model IEnumerable<SupplierDisplayViewModel> + +<div class="mb-4 ms-2"> + <form asp-action="Index" method="get"> + <div class="row g-3 align-items-center"> + <div class="col-md-3"> + <div class="input-group"> + <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> Search + </button> + </div> + </div> + + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Balance greater than" name="balanceGreaterThan" value="@ViewBag.BalanceGreaterThan" /> + </div> + + <div class="col-md-2"> + <input type="number" class="form-control" placeholder="Balance less than" name="balanceLessThan" value="@ViewBag.BalanceLessThan" /> + </div> + + <div class="col-md-1 d-flex justify-content-end"> + <a asp-action="Create" asp-controller="suppliers" class="btn btn-outline-success">New +</a> + </div> + + </div> + </form> +</div> + +<div class="container"> + <div class="row"> + <div class="col-12"> + <ejs-grid id="suppliers-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" type="Text"></e-grid-column> + <e-grid-column headerText="Full Name" field="FullName" type="Text"></e-grid-column> + <e-grid-column headerText="Phone Number" field="PhoneNumber" type="Text"></e-grid-column> + <e-grid-column headerText="Balance" field="Balance" format="C" type="Number"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + </div> +</div> + +<div class="container mt-4"> + <div class="d-flex justify-content-between align-items-center"> + <div> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + <div> + <p class="h6 mb-0">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> + </div> +</div> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel="nofollow" href="Suppliers/details/${Id}">${Id}</a> + </div> +</script> From 1f8df945cad66cd448e01490c700188383701edf Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:07:04 +0500 Subject: [PATCH 3/7] Fix all bugs --- .../WMS.WebUI/Stores/DataStores/CustomerStore.cs | 2 +- .../WMS.WebUI/Stores/DataStores/SupplierStore.cs | 2 +- .../CustomerViewModels/CustomerActionViewModel.cs | 10 +++++++++- .../CustomerViewModels/CustomerDisplayViewModel.cs | 6 +++++- .../SupplierViewModels/SupplierActionViewModel.cs | 11 ++++++++++- WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs index 063ea23..e160918 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs @@ -47,7 +47,7 @@ public async Task<PaginatedApiResponse<CustomerDisplayViewModel>> GetCustomers(C public async Task Update(CustomerActionViewModel customer) { - await _apiClient.PutAsync(ApiResourceConstants.Customers, customer); + await _apiClient.PutAsync(ApiResourceConstants.Customers + "/" + customer.Id, customer); } private string BuildQueryParameters(CustomerQueryParameters queryParameters) { diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs index 7035584..1d11697 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplierStore.cs @@ -45,7 +45,7 @@ public async Task<PaginatedApiResponse<SupplierDisplayViewModel>> GetSuppliers(S public async Task Update(SupplierActionViewModel supplier) { - await _apiClient.PutAsync(ApiResourceConstants.Suppliers, supplier); + await _apiClient.PutAsync(ApiResourceConstants.Suppliers + "/" + supplier.Id, supplier); } private string BuildQueryParameters(SupplierQueryParameters queryParameters) { diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs index 6aedcce..848a240 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerActionViewModel.cs @@ -1,10 +1,18 @@ -namespace WMS.WebUI.ViewModels.CustomerViewModels; +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.CustomerViewModels; public class CustomerActionViewModel { public int Id { get; set; } + + [DisplayName("First name")] public string FirstName { get; set; } + + [DisplayName("Last name")] public string LastName { get; set; } + + [DisplayName("Phone number")] public string PhoneNumber { get; set; } public string Address { get; set; } public decimal Balance { get; set; } diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs index 11fe7e1..f542311 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/CustomerViewModels/CustomerDisplayViewModel.cs @@ -1,9 +1,13 @@ -namespace WMS.WebUI.ViewModels.CustomerViewModels +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.CustomerViewModels { public class CustomerDisplayViewModel { public int Id { get; set; } + [DisplayName("Full name")] public string FullName { get; set; } + [DisplayName("Phone number")] public string PhoneNumber { get; set; } public string Address { get; set; } public decimal Balance { get; set; } diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs index 5d0bade..fe71d24 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SupplierViewModels/SupplierActionViewModel.cs @@ -1,11 +1,20 @@ -namespace WMS.WebUI.ViewModels.SupplierViewModels; +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.SupplierViewModels; public class SupplierActionViewModel { public int Id { get; set; } + + [DisplayName("First name")] public string FirstName { get; set; } + + [DisplayName("Last name")] public string LastName { get; set; } + + [DisplayName("Phone number")] public string PhoneNumber { get; set; } + public decimal Balance { get; set; } //public ICollection<SupplyDto> Supplies { get; set; } //public SupplierDto() diff --git a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml index 346c26f..6b21a0b 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Suppliers/Create.cshtml @@ -4,7 +4,7 @@ <h3 class="m-5" > Create Supplier </h3> <hr /> <div class="row m-5"> - <div class="col-md-8"> + <div class="col-md-6"> <form asp-action="Create" method="post"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> From eebfd1b06c6e3984f2dab532c736a6e30d931c60 Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:07:29 +0500 Subject: [PATCH 4/7] Oldest changes --- .../Constants/ApiResourceConstants.cs | 2 + .../Controllers/AdjustmentController.cs | 50 ++++++++++++ .../WMS.WebUI/Controllers/SalesController.cs | 57 ++++++++++++++ .../Controllers/SuppliesController.cs | 56 +++++++++++++ .../Models/Adjustment/Transactions.cs | 10 +++ WMS.WebUI/WMS.WebUI/Program.cs | 5 +- .../QueryParams/TransactionQueryParameters.cs | 9 +++ .../Stores/DataStores/CustomerStore.cs | 35 ++++----- .../WMS.WebUI/Stores/DataStores/SaleStore.cs | 68 ++++++++++++++++ .../Stores/DataStores/SupplyStore.cs | 66 ++++++++++++++++ .../WMS.WebUI/Stores/Interfaces/ISaleStore.cs | 13 ++++ .../Stores/Interfaces/ISupplyStore.cs | 14 ++++ .../SaleItemViewModels/SaleItemViewModel.cs | 15 ++++ .../SaleViewModels/SaleViewModel.cs | 20 +++++ .../SupplyItemViewModel.cs | 14 ++++ .../SupplyViewModels/SupplyViewModel.cs | 21 +++++ .../WMS.WebUI/Views/Customers/Index.cshtml | 7 +- .../WMS.WebUI/Views/Sales/Details.cshtml | 46 +++++++++++ WMS.WebUI/WMS.WebUI/Views/Sales/Index.cshtml | 78 +++++++++++++++++++ .../WMS.WebUI/Views/Supplies/Index.cshtml | 77 ++++++++++++++++++ WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj | 4 +- WMS.WebUI/WMS.WebUI/appsettings.json | 5 +- 22 files changed, 646 insertions(+), 26 deletions(-) create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/SalesController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/SuppliesController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Models/Adjustment/Transactions.cs create mode 100644 WMS.WebUI/WMS.WebUI/QueryParams/TransactionQueryParameters.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplyStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplyStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SaleItemViewModels/SaleItemViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SaleViewModels/SaleViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SupplyItemViewModels/SupplyItemViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/SupplyViewModels/SupplyViewModel.cs create mode 100644 WMS.WebUI/WMS.WebUI/Views/Sales/Details.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Sales/Index.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Supplies/Index.cshtml diff --git a/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs index 66a003c..b13c9e0 100644 --- a/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs +++ b/WMS.WebUI/WMS.WebUI/Constants/ApiResourceConstants.cs @@ -6,4 +6,6 @@ public static class ApiResourceConstants public const string Categories = nameof(Categories); public const string Customers = nameof(Customers); public const string Suppliers = nameof(Suppliers); + public const string Sales = nameof(Sales); + public const string Supplies = nameof(Supplies); } diff --git a/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs b/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs new file mode 100644 index 0000000..c237f31 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using WMS.WebUI.Models.Adjustment; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Controllers; + +public class AdjustmentController(ISupplyStore supplyStore,ISaleStore saleStore) : Controller +{ + private readonly ISaleStore _saleStore = saleStore; + private readonly ISupplyStore _supplyStore = supplyStore; + public async Task<IActionResult> Index(string? searchString, DateTime? fromDate, DateTime? toDate, int? pageNumber) + { + var queryParameters = new TransactionQueryParameters() + { + Search = searchString, + FromDate = fromDate, + ToDate = toDate, + PageNumber = pageNumber + }; + + var supplies = await _supplyStore.GetSupplies(queryParameters); + var sales = await _saleStore.GetSales(queryParameters); + + var transactions = new Transactions() + { + Sales = sales.Data, + Supplies = supplies.Data + }; + + // PopulateViewBag(transactions, queryParameters); + + return View(supplies.Data); + } + private void PopulateViewBag(PaginatedApiResponse<SupplyViewModel> sales, TransactionQueryParameters queryParameters) + { + ViewBag.FromDate = queryParameters.FromDate?.ToString("yyyy-MM-dd"); + ViewBag.ToDate = queryParameters.ToDate?.ToString("yyyy-MM-dd"); + ViewBag.SearchString = queryParameters.Search; + + ViewBag.PageSize = sales.PageSize; + ViewBag.TotalPages = sales.PagesCount; + ViewBag.TotalItems = sales.TotalCount; + ViewBag.CurrentPage = sales.CurrentPage; + ViewBag.HasPreviousPage = sales.HasPreviousPage; + ViewBag.HasNextPage = sales.HasNextPage; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Controllers/SalesController.cs b/WMS.WebUI/WMS.WebUI/Controllers/SalesController.cs new file mode 100644 index 0000000..57ae057 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/SalesController.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SaleViewModels; + +namespace WMS.WebUI.Controllers; + +public class SalesController : Controller +{ + private readonly ICustomerStore _customerStore; + private readonly ISaleStore _salesStore; + + public SalesController(ISaleStore saleStore, ICustomerStore customerStore) + { + _customerStore = customerStore; + _salesStore = saleStore; + } + + public async Task<IActionResult> Index(string? searchString, DateTime? fromDate, DateTime? toDate, int? pageNumber) + { + var queryParameters = new TransactionQueryParameters() + { + Search = searchString, + FromDate = fromDate, + ToDate = toDate, + PageNumber = pageNumber + }; + + var sales = await _salesStore.GetSales(queryParameters); + + PopulateViewBag(sales, queryParameters); + + return View(sales.Data); + } + + public async Task<IActionResult> Details(int id) + { + var sale = await _salesStore.GetById(id); + return View(sale); + } + + private void PopulateViewBag(PaginatedApiResponse<SaleViewModel> sales, TransactionQueryParameters queryParameters) + { + ViewBag.FromDate = queryParameters.FromDate?.ToString("yyyy-MM-dd"); + ViewBag.ToDate = queryParameters.ToDate?.ToString("yyyy-MM-dd"); + ViewBag.SearchString = queryParameters.Search; + + ViewBag.PageSize = sales.PageSize; + ViewBag.TotalPages = sales.PagesCount; + ViewBag.TotalItems = sales.TotalCount; + ViewBag.CurrentPage = sales.CurrentPage; + ViewBag.HasPreviousPage = sales.HasPreviousPage; + ViewBag.HasNextPage = sales.HasNextPage; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Controllers/SuppliesController.cs b/WMS.WebUI/WMS.WebUI/Controllers/SuppliesController.cs new file mode 100644 index 0000000..de3220b --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/SuppliesController.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Controllers; + +public class SuppliesController : Controller +{ + private readonly ICustomerStore _customerStore; + private readonly ISupplyStore _supplyStore; + + public SuppliesController(ISupplyStore supplyStore, ICustomerStore customerStore) + { + _customerStore = customerStore; + _supplyStore = supplyStore; + } + + public async Task<IActionResult> Index(string? searchString, DateTime? fromDate, DateTime? toDate, int? pageNumber) + { + var queryParameters = new TransactionQueryParameters() + { + Search = searchString, + FromDate = fromDate, + ToDate = toDate, + PageNumber = pageNumber + }; + + var supplies = await _supplyStore.GetSupplies(queryParameters); + + PopulateViewBag(supplies, queryParameters); + + return View(supplies.Data); + } + + public async Task<IActionResult> Details(int id) + { + var supply = await _supplyStore.GetById(id); + return View(supply); + } + + private void PopulateViewBag(PaginatedApiResponse<SupplyViewModel> sales, TransactionQueryParameters queryParameters) + { + ViewBag.FromDate = queryParameters.FromDate?.ToString("yyyy-MM-dd"); + ViewBag.ToDate = queryParameters.ToDate?.ToString("yyyy-MM-dd"); + ViewBag.SearchString = queryParameters.Search; + + ViewBag.PageSize = sales.PageSize; + ViewBag.TotalPages = sales.PagesCount; + ViewBag.TotalItems = sales.TotalCount; + ViewBag.CurrentPage = sales.CurrentPage; + ViewBag.HasPreviousPage = sales.HasPreviousPage; + ViewBag.HasNextPage = sales.HasNextPage; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Models/Adjustment/Transactions.cs b/WMS.WebUI/WMS.WebUI/Models/Adjustment/Transactions.cs new file mode 100644 index 0000000..48dae94 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Models/Adjustment/Transactions.cs @@ -0,0 +1,10 @@ +using WMS.WebUI.ViewModels.SaleViewModels; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Models.Adjustment; + +public class Transactions +{ + public IEnumerable<SaleViewModel> Sales { get; set; } + public IEnumerable<SupplyViewModel> Supplies { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/Program.cs b/WMS.WebUI/WMS.WebUI/Program.cs index faa76cc..1d3bfb6 100644 --- a/WMS.WebUI/WMS.WebUI/Program.cs +++ b/WMS.WebUI/WMS.WebUI/Program.cs @@ -12,10 +12,11 @@ builder.Services.AddScoped<IProductsStore, ProductStore>(); builder.Services.AddScoped<ICustomerStore,CustomerStore>(); builder.Services.AddScoped<ISupplierStore,SupplierStore>(); - +builder.Services.AddScoped<ISaleStore,SaleStore>(); +builder.Services.AddScoped<ISupplyStore, SupplyStore>(); builder.Services.AddSingleton<ApiClient>(); -Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("Ngo9BigBOggjHTQxAR8/V1NBaF1cXmhPYVJwWmFZfVpgfF9DaFZQTGYuP1ZhSXxXdkNjUH9WdXxUTmNeVE0="); ; +Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(builder.Configuration.GetValue<string>("Keys:Syncfusion")); var app = builder.Build(); diff --git a/WMS.WebUI/WMS.WebUI/QueryParams/TransactionQueryParameters.cs b/WMS.WebUI/WMS.WebUI/QueryParams/TransactionQueryParameters.cs new file mode 100644 index 0000000..483ad92 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/QueryParams/TransactionQueryParameters.cs @@ -0,0 +1,9 @@ +using Syncfusion.EJ2.Calendars; + +namespace WMS.WebUI.QueryParams; + +public class TransactionQueryParameters : BaseQueryParameters +{ + public DateTime? FromDate { get; set; } + public DateTime? ToDate { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs index e160918..6acba80 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/CustomerStore.cs @@ -13,42 +13,41 @@ namespace WMS.WebUI.Stores.DataStores; public class CustomerStore(ApiClient apiClient) : ICustomerStore { private readonly ApiClient _apiClient = apiClient; - public async Task<CustomerDisplayViewModel> Create(CustomerActionViewModel customer) + public async Task<PaginatedApiResponse<CustomerDisplayViewModel>> GetCustomers(CustomerQueryParameters queryParameters) { - var createdCustomer = await _apiClient.PostAsync<CustomerDisplayViewModel - ,CustomerActionViewModel>(ApiResourceConstants.Customers,customer); + var query = BuildQueryParameters(queryParameters); - return createdCustomer; - } + var url = string.IsNullOrEmpty(query) ? + ApiResourceConstants.Customers : + ApiResourceConstants.Customers + "?" + query; - public async Task Delete(int id) - { - await _apiClient.DeleteAsync(ApiResourceConstants.Customers,id); - } + var customers = await _apiClient.GetAsync<PaginatedApiResponse<CustomerDisplayViewModel>>(url); + return customers; + } public async Task<CustomerDisplayViewModel> GetById(int id) { var customer = await _apiClient.GetAsync<CustomerDisplayViewModel>(ApiResourceConstants.Customers + "/" + id); return customer; } - public async Task<PaginatedApiResponse<CustomerDisplayViewModel>> GetCustomers(CustomerQueryParameters queryParameters) + public async Task<CustomerDisplayViewModel> Create(CustomerActionViewModel customer) { - var query = BuildQueryParameters(queryParameters); - - var url = string.IsNullOrEmpty(query) ? - ApiResourceConstants.Customers : - ApiResourceConstants.Customers + "?" + query; - - var customers = await _apiClient.GetAsync<PaginatedApiResponse<CustomerDisplayViewModel>>(url); + var createdCustomer = await _apiClient.PostAsync<CustomerDisplayViewModel + ,CustomerActionViewModel>(ApiResourceConstants.Customers,customer); - return customers; + return createdCustomer; } public async Task Update(CustomerActionViewModel customer) { await _apiClient.PutAsync(ApiResourceConstants.Customers + "/" + customer.Id, customer); } + public async Task Delete(int id) + { + await _apiClient.DeleteAsync(ApiResourceConstants.Customers,id); + } + private string BuildQueryParameters(CustomerQueryParameters queryParameters) { var query = new StringBuilder(); diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs new file mode 100644 index 0000000..2375792 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs @@ -0,0 +1,68 @@ +using Syncfusion.EJ2.Diagrams; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Services; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SaleViewModels; + +namespace WMS.WebUI.Stores.DataStores; + +public class SaleStore(ApiClient apiClient) : ISaleStore +{ + private readonly ApiClient _apiClient = apiClient; + public async Task<PaginatedApiResponse<SaleViewModel>> GetSales(TransactionQueryParameters queryParameters) + { + var query = BuildQueryParameters(queryParameters); + var sales = await _apiClient.GetAsync<PaginatedApiResponse<SaleViewModel>>(ApiResourceConstants.Sales + "?" + query); + return sales; + } + public async Task<SaleViewModel> GetById(int id) + { + var sale = await _apiClient.GetAsync<SaleViewModel>(ApiResourceConstants.Sales + "/" + id); + return sale; + } + + public Task<SaleViewModel> Create(SaleViewModel sale) + { + var createdSale = _apiClient.PostAsync<SaleViewModel, SaleViewModel>(ApiResourceConstants.Sales, sale); + return createdSale; + } + public async Task Update(SaleViewModel sale) + { + await _apiClient.PutAsync<SaleViewModel>(ApiResourceConstants.Sales, sale); + } + + public async Task Delete(int id) + { + await _apiClient.DeleteAsync(ApiResourceConstants.Sales, id); + } + private string BuildQueryParameters(TransactionQueryParameters queryParameters) + { + StringBuilder query = new StringBuilder(); + + if (queryParameters.PageNumber.HasValue) + { + query.Append($"pageNumber={queryParameters.PageNumber}&"); + } + else + { + query.Append($"pageNumber=1&"); + } + if (!string.IsNullOrWhiteSpace(queryParameters.Search)) + { + query.Append($"Search={queryParameters.Search}&"); + } + if (queryParameters.FromDate.HasValue) + { + query.Append($"FromDate={queryParameters.FromDate.Value:yyyy-MM-ddTHH:mm:ss.fffZ}&"); + } + if (queryParameters.ToDate.HasValue) + { + query.Append($"ToDate={queryParameters.ToDate.Value:yyyy-MM-ddTHH:mm:ss.fffZ}&"); + } + return query.ToString(); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplyStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplyStore.cs new file mode 100644 index 0000000..22b0723 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SupplyStore.cs @@ -0,0 +1,66 @@ +using System.Text; +using WMS.WebUI.Constants; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.Services; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Stores.DataStores; + +public class SupplyStore(ApiClient apiClient) : ISupplyStore +{ + private readonly ApiClient _apiClient = apiClient; + public async Task<PaginatedApiResponse<SupplyViewModel>> GetSupplies(TransactionQueryParameters queryParameters) + { + var query = BuildQueryParameters(queryParameters); + var supplies = await _apiClient.GetAsync<PaginatedApiResponse<SupplyViewModel>>(ApiResourceConstants.Supplies + "?" + query); + return supplies; + } + public async Task<SupplyViewModel> GetById(int id) + { + var supply = await _apiClient.GetAsync<SupplyViewModel>(ApiResourceConstants.Supplies + "/" + id); + return supply; + } + + public Task<SupplyViewModel> Create(SupplyViewModel supply) + { + var createdSupply = _apiClient.PostAsync<SupplyViewModel, SupplyViewModel>(ApiResourceConstants.Supplies, supply); + return createdSupply; + } + public async Task Update(SupplyViewModel supply) + { + await _apiClient.PutAsync<SupplyViewModel>(ApiResourceConstants.Supplies, supply); + } + + public async Task Delete(int id) + { + await _apiClient.DeleteAsync(ApiResourceConstants.Supplies, id); + } + private string BuildQueryParameters(TransactionQueryParameters queryParameters) + { + StringBuilder query = new StringBuilder(); + + if (queryParameters.PageNumber.HasValue) + { + query.Append($"pageNumber={queryParameters.PageNumber}&"); + } + else + { + query.Append($"pageNumber=1&"); + } + if (!string.IsNullOrWhiteSpace(queryParameters.Search)) + { + query.Append($"Search={queryParameters.Search}&"); + } + if (queryParameters.FromDate.HasValue) + { + query.Append($"FromDate={queryParameters.FromDate.Value:yyyy-MM-ddTHH:mm:ss.fffZ}&"); + } + if (queryParameters.ToDate.HasValue) + { + query.Append($"ToDate={queryParameters.ToDate.Value:yyyy-MM-ddTHH:mm:ss.fffZ}&"); + } + return query.ToString(); + } +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs new file mode 100644 index 0000000..57084bf --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs @@ -0,0 +1,13 @@ +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels.SaleViewModels; + +namespace WMS.WebUI.Stores.Interfaces; +public interface ISaleStore +{ + Task<PaginatedApiResponse<SaleViewModel>> GetSales(TransactionQueryParameters queryParameters); + Task<SaleViewModel> GetById(int id); + Task<SaleViewModel> Create(SaleViewModel sale); + Task Update(SaleViewModel sale); + Task Delete(int id); +} diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplyStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplyStore.cs new file mode 100644 index 0000000..ce1a321 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISupplyStore.cs @@ -0,0 +1,14 @@ +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Stores.Interfaces; + +public interface ISupplyStore +{ + Task<PaginatedApiResponse<SupplyViewModel>> GetSupplies(TransactionQueryParameters queryParameters); + Task<SupplyViewModel> GetById(int id); + Task<SupplyViewModel> Create(SupplyViewModel supply); + Task Update(SupplyViewModel supply); + Task Delete(int id); +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SaleItemViewModels/SaleItemViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SaleItemViewModels/SaleItemViewModel.cs new file mode 100644 index 0000000..a7912a4 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SaleItemViewModels/SaleItemViewModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.SaleItemViewModels; + +public class SaleItemViewModel +{ + public int Id { get; set; } + public int Quantity { get; set; } + + [DisplayName("Unit price")] + public decimal UnitPrice { get; set; } + public int ProductId { get; set; } + public string Product { get; set; } + public int SaleId { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SaleViewModels/SaleViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SaleViewModels/SaleViewModel.cs new file mode 100644 index 0000000..133f3e7 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SaleViewModels/SaleViewModel.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using WMS.WebUI.ViewModels.SaleItemViewModels; + +namespace WMS.WebUI.ViewModels.SaleViewModels; + +public class SaleViewModel +{ + public int Id { get; set; } + + [DisplayName("Total due")] + public decimal TotalDue { get; set; } + + [DisplayName("Total paid")] + public decimal TotalPaid { get; set; } + public DateTime Date { get; set; } + public int CustomerId { get; set; } + public string Customer { get; set; } + public virtual ICollection<SaleItemViewModel> SaleItems { get; set; } + public SaleViewModel() => SaleItems = []; +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SupplyItemViewModels/SupplyItemViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SupplyItemViewModels/SupplyItemViewModel.cs new file mode 100644 index 0000000..86fa9bb --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SupplyItemViewModels/SupplyItemViewModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace WMS.WebUI.ViewModels.SupplyItemViewModels; + +public class SupplyItemViewModel +{ + public int Id { get; set; } + public int Quantity { get; set; } + [DisplayName("Unit price")] + public decimal UnitPrice { get; set; } + public int ProductId { get; set; } + public virtual string Product { get; set; } + public int SupplyId { get; set; } +} diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/SupplyViewModels/SupplyViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/SupplyViewModels/SupplyViewModel.cs new file mode 100644 index 0000000..6d099db --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/SupplyViewModels/SupplyViewModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; +using WMS.WebUI.ViewModels.SupplyItemViewModels; + +namespace WMS.WebUI.ViewModels.SupplyViewModels; + +public class SupplyViewModel +{ + public int Id { get; set; } + [DisplayName("Total due")] + public decimal TotalDue { get; set; } + [DisplayName("Total paid")] + public decimal TotalPaid { get; set; } + public DateTime Date { get; set; } + public int SupplierId { get; set; } + public string Supplier { get; set; } + public ICollection<SupplyItemViewModel> SupplyItems { get; set; } + public SupplyViewModel() + { + SupplyItems = []; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml index 7b64e43..3a7122f 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Customers/Index.cshtml @@ -1,10 +1,11 @@ @using WMS.WebUI.ViewModels.CustomerViewModels @model IEnumerable<CustomerDisplayViewModel> -<div class="mb-4 ms-2"> +<div class="mb-4 ms-6"> <form asp-action="Index" method="get"> - <div class="row g-3 align-items-center"> - <div class="col-md-3"> + <div class="row g-10 align-items-center"> + + <div class="col-md-4"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> <button type="submit" class="btn btn-primary"> diff --git a/WMS.WebUI/WMS.WebUI/Views/Sales/Details.cshtml b/WMS.WebUI/WMS.WebUI/Views/Sales/Details.cshtml new file mode 100644 index 0000000..f1ffa2b --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Sales/Details.cshtml @@ -0,0 +1,46 @@ +@model WMS.WebUI.ViewModels.SaleViewModels.SaleViewModel +@{ + ViewData["Title"] = "Sale Details"; +} + +<h2>@ViewData["Title"]</h2> + +<div class="mb-4"> + <h3>Sale Information</h3> + </div> + <div class="card-body"> + <dl class="row"> + <dt class="col-sm-3">Sale ID</dt> + <dd class="col-sm-9">@Model.Id</dd> + + <dt class="col-sm-3">Date</dt> + <dd class="col-sm-9">@Model.Date.ToString("dd MMM yyyy")</dd> + + <dt class="col-sm-3">Customer</dt> + <dd class="col-sm-9">@Model.Customer</dd> + + <dt class="col-sm-3">Total Due</dt> + <dd class="col-sm-9">@Model.TotalDue.ToString("C")</dd> + + <dt class="col-sm-3">Total Paid</dt> + <dd class="col-sm-9">@Model.TotalPaid.ToString("C")</dd> + </dl> +</div> +<div class="container"> + <div class="row"> + <div class="col-12"> + <ejs-grid id="customers-list" + dataSource="@Model.SaleItems" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Quantity" field="Quantity" type="Text"></e-grid-column> + <e-grid-column headerText="Unit price" field="UnitPrice" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Product" field="Product" type="Text"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + </div> +</div> + +<a asp-action="Index" class="btn btn-primary mt-4">Back to List</a> diff --git a/WMS.WebUI/WMS.WebUI/Views/Sales/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Sales/Index.cshtml new file mode 100644 index 0000000..b5c2d4e --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Sales/Index.cshtml @@ -0,0 +1,78 @@ +@using WMS.WebUI.ViewModels.SaleViewModels +@model IEnumerable<SaleViewModel> + +<div class="mb-4 ms-2"> + <form asp-action="Index" method="get"> + <div class="row g-3 align-items-center"> + <div class="col-md-3"> + <div class="input-group"> + <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> Search + </button> + </div> + </div> + + <div class="col-md-2"> + <input type="date" class="form-control" placeholder="From date" name="fromDate" value="@ViewBag.FromDate" /> + </div> + + <div class="col-md-2"> + <input type="date" class="form-control" placeholder="To date" name="toDate" value="@ViewBag.ToDate" /> + </div> + + <div class="col-md-1 d-flex justify-content-end"> + <a asp-action="Create" asp-controller="sales" class="btn btn-outline-success">New +</a> + </div> + + </div> + </form> +</div> + +<div class="container"> + <div class="row"> + <div class="col-12"> + <ejs-grid id="customers-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" type="Text"></e-grid-column> + <e-grid-column headerText="Customer" field="Customer" type="Text"></e-grid-column> + <e-grid-column headerText="Total due" field="TotalDue" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Total paid" field="TotalPaid" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Date" field="Date" type="Date"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + </div> +</div> + +<div class="container mt-4"> + <div class="d-flex justify-content-between align-items-center"> + <div> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + <div> + <p class="h6 mb-0">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> + </div> +</div> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel="nofollow" href="Sales/details/${Id}">${Id}</a> + </div> +</script> diff --git a/WMS.WebUI/WMS.WebUI/Views/Supplies/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Supplies/Index.cshtml new file mode 100644 index 0000000..29ff616 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Supplies/Index.cshtml @@ -0,0 +1,77 @@ +@using WMS.WebUI.ViewModels.SupplyViewModels +@model IEnumerable<SupplyViewModel> + +<div class="mb-4 ms-2"> + <form asp-action="Index" method="get"> + <div class="row g-3 align-items-center"> + <div class="col-md-3"> + <div class="input-group"> + <input type="text" class="form-control" placeholder="Search..." name="searchString" value="@ViewBag.SearchString" /> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> Search + </button> + </div> + </div> + <div class="col-md-2"> + <input type="date" class="form-control" placeholder="From date" name="fromDate" value="@ViewBag.FromDate" /> + </div> + + <div class="col-md-2"> + <input type="date" class="form-control" placeholder="To date" name="toDate" value="@ViewBag.ToDate" /> + </div> + + <div class="col-md-1 d-flex justify-content-end"> + <a asp-action="Create" asp-controller="Supplies" class="btn btn-outline-success">New +</a> + </div> + + </div> + </form> +</div> + +<div class="container"> + <div class="row"> + <div class="col-12"> + <ejs-grid id="customers-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" type="Text"></e-grid-column> + <e-grid-column headerText="Supplier" field="Supplier" type="Text"></e-grid-column> + <e-grid-column headerText="Total due" field="TotalDue" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Total paid" field="TotalPaid" format="C" type="Number"></e-grid-column> + <e-grid-column headerText="Date" field="Date" type="Date"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + </div> +</div> + +<div class="container mt-4"> + <div class="d-flex justify-content-between align-items-center"> + <div> + <a class="btn btn-outline-primary rounded-left no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><<</a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasPreviousPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })" @(ViewBag.HasPreviousPage ? "" : "tabindex=\"-1\"")><</a> + @if (ViewBag.CurrentPage > 1) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage - 1 })">@(@ViewBag.CurrentPage - 1)</a> + } + <button type="button" class="btn btn-primary rounded-right no-outline" disabled>@ViewBag.CurrentPage</button> + @if (ViewBag.CurrentPage < ViewBag.TotalPages) + { + <a class="btn btn-outline-primary rounded-right no-outline" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })">@(@ViewBag.CurrentPage + 1)</a> + } + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.CurrentPage + 1 })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>></a> + <a class="btn btn-outline-primary rounded-right no-outline @(ViewBag.HasNextPage ? "" : "disabled")" href="@Url.Action("Index", new { pageNumber = ViewBag.TotalPages })" @(ViewBag.HasNextPage ? "" : "tabindex=\"-1\"")>>></a> + </div> + <div> + <p class="h6 mb-0">@ViewBag.CurrentPage of @ViewBag.TotalPages pages (@ViewBag.TotalItems items)</p> + </div> + </div> +</div> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel="nofollow" href="Supplies/details/${Id}">${Id}</a> + </div> +</script> diff --git a/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj b/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj index fabc02c..527d341 100644 --- a/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj +++ b/WMS.WebUI/WMS.WebUI/WMS.WebUI.csproj @@ -8,8 +8,8 @@ <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> - <PackageReference Include="Syncfusion.EJ2.AspNet.Core" Version="25.2.7" /> - <PackageReference Include="Syncfusion.Licensing" Version="25.2.7" /> + <PackageReference Include="Syncfusion.EJ2.AspNet.Core" Version="26.1.40" /> + <PackageReference Include="Syncfusion.Licensing" Version="26.1.40" /> </ItemGroup> </Project> diff --git a/WMS.WebUI/WMS.WebUI/appsettings.json b/WMS.WebUI/WMS.WebUI/appsettings.json index 10f68b8..430059c 100644 --- a/WMS.WebUI/WMS.WebUI/appsettings.json +++ b/WMS.WebUI/WMS.WebUI/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Keys": { + "Syncfusion": "Ngo9BigBOggjHTQxAR8/V1NCaF5cXmZCf1FpRmJGdld5fUVHYVZUTXxaS00DNHVRdkdnWXhedHVUR2dZWUV2XUE=" + } } From 7c7ed307bb32e730c212fd4655ac19ac72a70d2b Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:33:27 +0500 Subject: [PATCH 5/7] Added Partner viewModels and Transactions --- .../Controllers/AdjustmentController.cs | 50 ----------------- .../Controllers/TransactionsController.cs | 55 +++++++++++++++++++ WMS.WebUI/WMS.WebUI/Helpers/Validator.cs | 13 +++++ .../WMS.WebUI/Stores/DataStores/SaleStore.cs | 8 ++- .../WMS.WebUI/Stores/Interfaces/ISaleStore.cs | 3 +- .../Stores/Interfaces/ITransactionsStore.cs | 10 ++++ .../ViewModels/DashboardViewModel.cs | 31 ++++++++++- .../WMS.WebUI/ViewModels/PartnerViewModel.cs | 15 +++++ 8 files changed, 130 insertions(+), 55 deletions(-) delete mode 100644 WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Controllers/TransactionsController.cs create mode 100644 WMS.WebUI/WMS.WebUI/Helpers/Validator.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/Interfaces/ITransactionsStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/ViewModels/PartnerViewModel.cs diff --git a/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs b/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs deleted file mode 100644 index c237f31..0000000 --- a/WMS.WebUI/WMS.WebUI/Controllers/AdjustmentController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using WMS.WebUI.Models.Adjustment; -using WMS.WebUI.Models.PaginatedResponse; -using WMS.WebUI.QueryParams; -using WMS.WebUI.Stores.Interfaces; -using WMS.WebUI.ViewModels.SupplyViewModels; - -namespace WMS.WebUI.Controllers; - -public class AdjustmentController(ISupplyStore supplyStore,ISaleStore saleStore) : Controller -{ - private readonly ISaleStore _saleStore = saleStore; - private readonly ISupplyStore _supplyStore = supplyStore; - public async Task<IActionResult> Index(string? searchString, DateTime? fromDate, DateTime? toDate, int? pageNumber) - { - var queryParameters = new TransactionQueryParameters() - { - Search = searchString, - FromDate = fromDate, - ToDate = toDate, - PageNumber = pageNumber - }; - - var supplies = await _supplyStore.GetSupplies(queryParameters); - var sales = await _saleStore.GetSales(queryParameters); - - var transactions = new Transactions() - { - Sales = sales.Data, - Supplies = supplies.Data - }; - - // PopulateViewBag(transactions, queryParameters); - - return View(supplies.Data); - } - private void PopulateViewBag(PaginatedApiResponse<SupplyViewModel> sales, TransactionQueryParameters queryParameters) - { - ViewBag.FromDate = queryParameters.FromDate?.ToString("yyyy-MM-dd"); - ViewBag.ToDate = queryParameters.ToDate?.ToString("yyyy-MM-dd"); - ViewBag.SearchString = queryParameters.Search; - - ViewBag.PageSize = sales.PageSize; - ViewBag.TotalPages = sales.PagesCount; - ViewBag.TotalItems = sales.TotalCount; - ViewBag.CurrentPage = sales.CurrentPage; - ViewBag.HasPreviousPage = sales.HasPreviousPage; - ViewBag.HasNextPage = sales.HasNextPage; - } -} diff --git a/WMS.WebUI/WMS.WebUI/Controllers/TransactionsController.cs b/WMS.WebUI/WMS.WebUI/Controllers/TransactionsController.cs new file mode 100644 index 0000000..3df277f --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Controllers/TransactionsController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels; +using Validator = WMS.WebUI.Helpers.Validator; + +namespace WMS.WebUI.Controllers; + +public class TransactionsController : Controller +{ + private readonly ITransactionsStore _transactionsStore; + private readonly IProductsStore _productsStore; + + public TransactionsController(ITransactionsStore transactionsStore, IProductsStore productsStore) + { + _transactionsStore = Validator.NotNull(transactionsStore); + _productsStore = Validator.NotNull(productsStore); + } + + public async Task<IActionResult> Index(string? searchString = null, string? transactionType = null) + { + var transactions = await _transactionsStore.GetTransactionsAsync(searchString, transactionType); + + ViewBag.SelectedType = transactionType ?? "All"; + + return View(transactions); + } + + public async Task<IActionResult> Create() + { + var partnersTask = _transactionsStore.GetPartnersAsync(); + var productsTask = _productsStore.GetProducts(new QueryParams.ProductQueryParameters()); + + await Task.WhenAll(partnersTask, productsTask); + + ViewBag.Types = new string[] { "Sale", "Supply" }; + ViewBag.SelectedType = "Sale"; + ViewBag.Partners = partnersTask.Result; + ViewBag.Products= productsTask.Result.Data; + + return View(); + } + + [HttpPost] + public async Task<IActionResult> Create( + [FromBody] CreateTransactionViewModel data) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + await _transactionsStore.Create(null); + return View(); + } +} \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Helpers/Validator.cs b/WMS.WebUI/WMS.WebUI/Helpers/Validator.cs new file mode 100644 index 0000000..2dc9c13 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Helpers/Validator.cs @@ -0,0 +1,13 @@ +using WMS.WebUI.ViewModels; + +namespace WMS.WebUI.Helpers; + +public static class Validator +{ + public static T NotNull<T>(T value) where T : class + { + ArgumentNullException.ThrowIfNull(value); + + return value; + } +} \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs index 2375792..e490b3f 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/SaleStore.cs @@ -6,6 +6,7 @@ using WMS.WebUI.QueryParams; using WMS.WebUI.Services; using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels; using WMS.WebUI.ViewModels.SaleViewModels; namespace WMS.WebUI.Stores.DataStores; @@ -25,10 +26,11 @@ public async Task<SaleViewModel> GetById(int id) return sale; } - public Task<SaleViewModel> Create(SaleViewModel sale) + public async Task<TransactionView> Create(CreateTransactionViewModel transaction) { - var createdSale = _apiClient.PostAsync<SaleViewModel, SaleViewModel>(ApiResourceConstants.Sales, sale); - return createdSale; + var transactionNew = new TransactionView(); + + return transactionNew; } public async Task Update(SaleViewModel sale) { diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs index 57084bf..29052ed 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ISaleStore.cs @@ -1,5 +1,6 @@ using WMS.WebUI.Models.PaginatedResponse; using WMS.WebUI.QueryParams; +using WMS.WebUI.ViewModels; using WMS.WebUI.ViewModels.SaleViewModels; namespace WMS.WebUI.Stores.Interfaces; @@ -7,7 +8,7 @@ public interface ISaleStore { Task<PaginatedApiResponse<SaleViewModel>> GetSales(TransactionQueryParameters queryParameters); Task<SaleViewModel> GetById(int id); - Task<SaleViewModel> Create(SaleViewModel sale); + Task<TransactionView> Create(CreateTransactionViewModel transaction); Task Update(SaleViewModel sale); Task Delete(int id); } diff --git a/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ITransactionsStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ITransactionsStore.cs new file mode 100644 index 0000000..4fb4677 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/Interfaces/ITransactionsStore.cs @@ -0,0 +1,10 @@ +using WMS.WebUI.ViewModels; + +namespace WMS.WebUI.Stores.Interfaces; + +public interface ITransactionsStore +{ + Task<List<TransactionView>> GetTransactionsAsync(string? search, string? type); + Task<List<PartnerViewModel>> GetPartnersAsync(); + Task<TransactionView> Create(CreateTransactionViewModel transaction); +} \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/DashboardViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/DashboardViewModel.cs index d1262e1..cd005e0 100644 --- a/WMS.WebUI/WMS.WebUI/ViewModels/DashboardViewModel.cs +++ b/WMS.WebUI/WMS.WebUI/ViewModels/DashboardViewModel.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Globalization; +using System.Runtime.Serialization; namespace WMS.WebUI.ViewModels; @@ -61,7 +62,35 @@ public class SplineChart public class TransactionView { public int Id { get; set; } - public string Type { get; set; } + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] + public TransactionType Type { get; set; } public decimal Amount { get; set; } + public int PartnerId { get; set; } + public string Partner { get; set; } public DateTime Date { get; set; } +} + +public class CreateTransactionViewModel +{ + public TransactionType Type { get; set; } + public int PartnerId { get; set; } + public DateTime Date { get; set; } + public List<TransactionItem> Items { get; set; } +} + +public class TransactionItem +{ + public int ProductId { get; set; } + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } +} + +public enum TransactionType +{ + [EnumMember(Value = "Sale")] + Sale, + [EnumMember(Value = "Supply")] + Supply, + [EnumMember(Value = "Refund")] + Refund } \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/ViewModels/PartnerViewModel.cs b/WMS.WebUI/WMS.WebUI/ViewModels/PartnerViewModel.cs new file mode 100644 index 0000000..8503c58 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/ViewModels/PartnerViewModel.cs @@ -0,0 +1,15 @@ +namespace WMS.WebUI.ViewModels; + +public class PartnerViewModel +{ + public int Id { get; set; } + public string FullName { get; set; } + public PartnerType Type { get; set; } + public string PhoneNumber { get; set; } +} + +public enum PartnerType +{ + Customer, + Supplier +} From 640234173cc8ae2b7bcdc5108b9c2cc20f9d217e Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:55:45 +0500 Subject: [PATCH 6/7] Changing Transactions but not Delete Sale and supply controllers --- WMS.WebUI/WMS.WebUI/Mappings/SalesMappings.cs | 20 ++ .../WMS.WebUI/Mappings/SupplyMappings.cs | 21 +++ WMS.WebUI/WMS.WebUI/Program.cs | 2 + .../Stores/DataStores/TransactionsStore.cs | 56 ++++++ .../Stores/Mocks/MockDashboardStore.cs | 14 +- .../WMS.WebUI/Views/Shared/_Sidebar.cshtml | 2 +- .../Views/Transactions/Create.cshtml | 178 ++++++++++++++++++ .../WMS.WebUI/Views/Transactions/Index.cshtml | 82 ++++++++ 8 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 WMS.WebUI/WMS.WebUI/Mappings/SalesMappings.cs create mode 100644 WMS.WebUI/WMS.WebUI/Mappings/SupplyMappings.cs create mode 100644 WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs create mode 100644 WMS.WebUI/WMS.WebUI/Views/Transactions/Create.cshtml create mode 100644 WMS.WebUI/WMS.WebUI/Views/Transactions/Index.cshtml diff --git a/WMS.WebUI/WMS.WebUI/Mappings/SalesMappings.cs b/WMS.WebUI/WMS.WebUI/Mappings/SalesMappings.cs new file mode 100644 index 0000000..475da2c --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Mappings/SalesMappings.cs @@ -0,0 +1,20 @@ +using WMS.WebUI.ViewModels; +using WMS.WebUI.ViewModels.SaleViewModels; + +namespace WMS.WebUI.Mappings; + +public static class SalesMappings +{ + public static TransactionView ToTransaction(this SaleViewModel sale) + { + return new TransactionView + { + Id = sale.Id, + Amount = sale.TotalPaid, + Date = sale.Date, + Partner = sale.Customer, + PartnerId = sale.CustomerId, + Type = TransactionType.Sale + }; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Mappings/SupplyMappings.cs b/WMS.WebUI/WMS.WebUI/Mappings/SupplyMappings.cs new file mode 100644 index 0000000..8876669 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Mappings/SupplyMappings.cs @@ -0,0 +1,21 @@ +using WMS.WebUI.ViewModels; +using WMS.WebUI.ViewModels.SaleViewModels; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Mappings; + +public static class SupplyMappings +{ + public static TransactionView ToTransaction(this SupplyViewModel supply) + { + return new TransactionView + { + Id = supply.Id, + Amount = supply.TotalPaid, + Date = supply.Date, + Partner = supply.Supplier, + PartnerId = supply.SupplierId, + Type = TransactionType.Supply, + }; + } +} diff --git a/WMS.WebUI/WMS.WebUI/Program.cs b/WMS.WebUI/WMS.WebUI/Program.cs index 1d3bfb6..7c8883c 100644 --- a/WMS.WebUI/WMS.WebUI/Program.cs +++ b/WMS.WebUI/WMS.WebUI/Program.cs @@ -1,4 +1,5 @@ using WMS.WebUI.Services; +using WMS.WebUI.Stores; using WMS.WebUI.Stores.DataStores; using WMS.WebUI.Stores.Interfaces; using WMS.WebUI.Stores.Mocks; @@ -13,6 +14,7 @@ builder.Services.AddScoped<ICustomerStore,CustomerStore>(); builder.Services.AddScoped<ISupplierStore,SupplierStore>(); builder.Services.AddScoped<ISaleStore,SaleStore>(); +builder.Services.AddScoped<ITransactionsStore,TransactionsStore>(); builder.Services.AddScoped<ISupplyStore, SupplyStore>(); builder.Services.AddSingleton<ApiClient>(); diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs new file mode 100644 index 0000000..0a49309 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs @@ -0,0 +1,56 @@ +using WMS.WebUI.Mappings; +using WMS.WebUI.Models.PaginatedResponse; +using WMS.WebUI.Stores.Interfaces; +using WMS.WebUI.ViewModels; +using WMS.WebUI.ViewModels.SaleViewModels; +using WMS.WebUI.ViewModels.SupplyViewModels; + +namespace WMS.WebUI.Stores; + +public class TransactionsStore : ITransactionsStore +{ + private readonly HttpClient _client; + + public TransactionsStore() + { + _client = new HttpClient(); + _client.BaseAddress = new Uri("https://localhost:7108/api/"); + } + + public async Task<List<TransactionView>> GetTransactionsAsync(string? search, string? type) + { + var salesTask = _client.GetFromJsonAsync<PaginatedApiResponse<SaleViewModel>>($"sales?search={search}"); + var suppliesTask = _client.GetFromJsonAsync<PaginatedApiResponse<SupplyViewModel>>($"supplies?search={search}"); + + await Task.WhenAll(salesTask, suppliesTask); + + var sales = salesTask.Result; + var supplies = suppliesTask.Result; + List<TransactionView> transactions = []; + + sales?.Data.ForEach(sale => transactions.Add(sale.ToTransaction())); + supplies?.Data.ForEach(supply => transactions.Add(supply.ToTransaction())); + + return transactions; + } + + public async Task<List<PartnerViewModel>> GetPartnersAsync() + { + var customersTask = _client.GetFromJsonAsync<List<PartnerViewModel>>("customers"); + var suppliersTask = _client.GetFromJsonAsync<List<PartnerViewModel>>("suppliers"); + + await Task.WhenAll(customersTask, suppliersTask); + + customersTask.Result!.ForEach(el => el.Type = PartnerType.Customer); + suppliersTask.Result!.ForEach(el => el.Type = PartnerType.Supplier); + + return [.. customersTask.Result, .. suppliersTask.Result]; + } + + public async Task<TransactionView> Create(CreateTransactionViewModel transaction) + { + var transactionNew = new TransactionView(); + + return transactionNew; + } +} \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockDashboardStore.cs b/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockDashboardStore.cs index bf46fb4..8b3628c 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockDashboardStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/Mocks/MockDashboardStore.cs @@ -156,42 +156,42 @@ public Task<DashboardViewModel> Get() Id = 1, Amount = 500, Date = DateTime.Now, - Type = "Sale" + Type = TransactionType.Sale, }, new TransactionView { Id = 2, Amount = 300, - Date = DateTime.Now, - Type = "Sale" + Type = TransactionType.Sale, + Date = DateTime.Now }, new TransactionView { Id = 3, Amount = 459, + Type = TransactionType.Supply, Date = DateTime.Now, - Type = "Supply" }, new TransactionView { Id = 4, Amount = 500, + Type = TransactionType.Supply, Date = DateTime.Now, - Type = "Supply" }, new TransactionView { Id = 5, Amount = 250, + Type = TransactionType.Refund, Date = DateTime.Now, - Type = "Refund" }, new TransactionView { Id = 7, Amount = 200, Date = DateTime.Now, - Type = "Sale" + Type = TransactionType.Sale, }, }; diff --git a/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml b/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml index e681bcc..cbe8257 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Shared/_Sidebar.cshtml @@ -45,7 +45,7 @@ menuItems.Add(new { text = "Transactions", - url = "#", + url = "/transactions", iconCss = "fa-solid fa-money-bill-transfer", items = new List<object> { diff --git a/WMS.WebUI/WMS.WebUI/Views/Transactions/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Transactions/Create.cshtml new file mode 100644 index 0000000..64ef6a5 --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Transactions/Create.cshtml @@ -0,0 +1,178 @@ +@model WMS.WebUI.ViewModels.TransactionView +@using System.Collections.Generic + +<div class="row m-5"> + <div id="product-form" class="d-flex flex-column gap-4"> + <h3 class="text-center">Create Transaction</h3> + <div class="form-group d-flex gap-5"> + <ejs-combobox id="types" + dataSource="@ViewBag.Types" + value="@ViewBag.SelectedType" + placeholder="Select Type" + name="Type" + floatLabelType="Auto" + change="onSelectedTypeChanged"> + </ejs-combobox> + <ejs-combobox id="partners" + dataSource="@ViewBag.Partners" + value="@ViewBag.SelectedPartnerId" + placeholder="Select Partner" + name="PartnerId" + floatLabelType="Auto"> + <e-combobox-fields value="Id" text="FullName"></e-combobox-fields> + </ejs-combobox> + <div style="display: none;"> + <ejs-combobox id="partners-copy" + dataSource="@ViewBag.Partners" + value="@ViewBag.SelectedPartnerId" + placeholder="Select Partner" + name="PartnerId" + floatLabelType="Auto"> + <e-combobox-fields value="Id" text="FullName"></e-combobox-fields> + </ejs-combobox> + </div> + <ejs-datetimepicker id="transaction-date" value="@DateTime.Now" name="Date" placeholder="Select Date" floatLabelType="Auto"></ejs-datetimepicker> + </div> + <div class="form-group d-flex gap-5"> + <ejs-combobox id="products" + dataSource="@ViewBag.Products" + placeholder="Select Product" + + name="Products"> + <e-combobox-fields value="Id" text="Name"></e-combobox-fields> + </ejs-combobox> + <ejs-button id="addProduct" content="Add" class="ml-auto" cssClass="e-success"></ejs-button> + </div> + <div class="form-group d-flex gap-5"> + <ejs-grid id="transaction-items" + dataSource="@ViewBag.TransactionItems" + gridLines="Vertical" + allowSorting="true"> + <e-grid-editsettings allowDeleting="true" allowEditing="true" mode="Normal"></e-grid-editsettings> + <e-grid-columns> + <e-grid-column headerText="Id" field="ProductId" isPrimaryKey="true" typeof="Text"></e-grid-column> + <e-grid-column headerText="Name" field="Name" allowEditing="false" typeof="Text"></e-grid-column> + <e-grid-column headerText="Unit Price" field="UnitPrice" typeof="Text" format="c2"></e-grid-column> + <e-grid-column headerText="Quantity" field="Quantity" typeof="Number" editType="numericedit"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> + <div class="form-group d-flex gap-5"> + <p class="ml-auto" id="total-due">0</p> + </div> + + <ejs-button id="submit" content="Submit" cssClass="e-success"></ejs-button> + </div> +</div> + +<script> + document.getElementById('addProduct').onclick = function () { + const grid = document.getElementById("transaction-items").ej2_instances[0]; + const productsList = document.getElementById('products').ej2_instances[0]; + const selectedProduct = productsList.dataSource.find(el => el.Id == productsList.value); + + const item = { + ProductId: selectedProduct.Id, + Name: selectedProduct.Name, + Quantity: 1, + UnitPrice: selectedProduct.SalePrice + }; + + if (grid.dataSource.some(el => el.ProductId == item?.ProductId)) { + return; + } + + grid.dataSource.unshift(item); + grid.refresh(); + + console.log(grid.dataSource); + const totalDue = grid.dataSource.map(el => el.Quantity * el.UnitPrice).reduce((current, previous) => previous + current); + console.log(totalDue); + let formatter = Intl.NumberFormat('en', { notation: 'compact' }); + document.getElementById('total-due').innerHTML = formatter.format(totalDue); + }; + + // // submit sale + document.getElementById('submit').onclick = function (e) { + e.stopPropagation(); + + const type = document.getElementById('types').ej2_instances[0].value; + const partnerId = document.getElementById('partners').ej2_instances[0].value; + const selectedDate = document.getElementById('transaction-date').ej2_instances[0].value; + const items = document.getElementById("transaction-items").ej2_instances[0].dataSource; + + const newSale = { + Date: selectedDate, + PartnerId: partnerId, + Type: type + }; + console.log(newSale); + $.ajax({ + url: '@Url.Action("Create", "Transactions")', + type: 'POST', + data: JSON.stringify(newSale), + contentType: "application/json; charset=utf-8", + success: function (response) { + console.log('success: '); + console.log(response); + window.location.href = response.redirectToUrl; + }, + error: function (data) { + console.log('error: '); + console.log(data); + alert('There was an error saving creating new sale! Please, try again.'); + } + }); + } + + + // document.addEventListener('DOMContentLoaded', function () { + // const options = { + // rules: { + // 'datevalue': { required: true } + // }, + // customPlacement: (inputElement, errorElement) => { + // //to place the error message in custom position + // //inputElement - target element where the error text will be appended + // //errorElement - error text which will be displayed. + // inputElement.parentElement.parentElement.appendChild(errorElement); + // } + // }; + // const formObject = new ej.inputs.FormValidator('#sale-date', options); + // }); + + // function dlgButtonClick() { + // const dialogObj = document.getElementById('dialog').ej2_instances[0]; + // dialogObj.hide(); + // } + + // function onSelectedProductChanged() { + // const productsList = document.getElementById('products-list').ej2_instances[0]; + // const selectedProduct = productsList.dataSource.find(el => el.Id == productsList.value); + // document.getElementById('item-price').ej2_instances[0].value = selectedProduct.SupplyPrice; + // document.getElementById('item-quantity').ej2_instances[0].max = selectedProduct.QuantityInStock; + // } + + function onSelectedTypeChanged() { + const selectedValue = document.getElementById('types').ej2_instances[0].value; + const partners = document.getElementById('partners').ej2_instances[0]; + const partnersCopy = document.getElementById('partners-copy').ej2_instances[0].dataSource; + + if (selectedValue == 'Sale') { + const customers = partnersCopy.filter(el => el.Type == 0); + partners.dataSource = customers; // 50 -> 25 + } else { + const suppliers = partnersCopy.filter(el => el.Type == 1); + partners.dataSource = suppliers; + } + + partners.index = 0; + } + + document.addEventListener('DOMContentLoaded', function () { + onSelectedTypeChanged(); + + const productsList = document.getElementById('products').ej2_instances[0]; + productsList.index = 0; + }); +</script> \ No newline at end of file diff --git a/WMS.WebUI/WMS.WebUI/Views/Transactions/Index.cshtml b/WMS.WebUI/WMS.WebUI/Views/Transactions/Index.cshtml new file mode 100644 index 0000000..cf7477c --- /dev/null +++ b/WMS.WebUI/WMS.WebUI/Views/Transactions/Index.cshtml @@ -0,0 +1,82 @@ +@using WMS.WebUI.ViewModels +@model List<TransactionView> + +@{ + var transactionTypes = new string[] { "All", "Sales", "Supplies", "Refunds" }; +} + +<form asp-controller="Transactions" asp-action="Index"> + <input type="hidden" /> + <div class="row mt-3"> + <div class="d-flex justify-content-start gap-4"> + <div class="col-md-3 mb-4"> + <ejs-combobox id="transactions" + dataSource="transactionTypes" + value="@ViewBag.SelectedType" + placeholder="Select type" + name="transactionType"> + </ejs-combobox> + </div> + <!-- Search --> + <div class="col-md-4 mb-4"> + <div class="d-flex"> + <ejs-textbox id="search" value="@ViewBag.SearchString" placeholder="Search Transactions..." name="searchString"></ejs-textbox> + <button type="submit" class="btn btn-primary"> + <i class="fa fa-search"></i> + </button> + </div> + </div> + + <div class="d-flex mb-4 justify-content-end" style="gap: 15px;"> + <a class="btn btn-outline-info" asp-action="download"> + <i class="fa fa-solid fa-download"></i> Download + </a> + <a class="btn btn-outline-info" asp-action="upload"> + <i class="fa fa-solid fa-upload"></i> Upload + </a> + <a class="btn btn-success" asp-action="create"> + <i class="fa fa-solid fa-plus"></i> Create + </a> + </div> + </div> + </div> +</form> + +<div class="row mb-4"> + <div class="col-12"> + <ejs-grid id="categories-list" + dataSource="@Model" + gridLines="Vertical" + allowSorting="true"> + <e-grid-columns> + <e-grid-column headerText="Id" field="Id" template="#idCellTemplate" typeof="Text"></e-grid-column> + <e-grid-column headerText="Amount" field="Amount" typeof="Text" format="c2"></e-grid-column> + <e-grid-column headerText="Date" field="date" typeof="Text" format="yMd"></e-grid-column> + <e-grid-column headerText="Partner" field="Partner" template="#partnerCellTemplate" typeof="Text"></e-grid-column> + <e-grid-column headerText="Type" field="Type" typeof="Text"></e-grid-column> + </e-grid-columns> + </ejs-grid> + </div> +</div> + +<script id="idCellTemplate" type="text/template"> + <div> + <a rel='nofollow' href="transactions/details/${Id}">${Id}</a> + </div> +</script> +<script id="partnerCellTemplate" type="text/template"> + <div> + <a rel='nofollow' href="transaction/partners/${Id}">${Partner}</a> + </div> +</script> + +<script id="stockTemplate" type="text/x-template"> + <div class="template_checkbox text-center"> + ${if(LowQuantityAmount > QuantityInStock)} + <p class="text-danger">${QuantityInStock}</p> + + ${else} + <p>${QuantityInStock} </p> + ${/if} + </div> +</script> \ No newline at end of file From 9cb275c174b8acb7a0ff4fb64c3a3b1ed4d801c0 Mon Sep 17 00:00:00 2001 From: FirdavsAX <137472686+FirdavsAX@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:03:48 +0500 Subject: [PATCH 7/7] a efe --- .../Stores/DataStores/TransactionsStore.cs | 10 +- .../WMS.WebUI/Views/Products/Create.cshtml | 99 ++++++------------- 2 files changed, 35 insertions(+), 74 deletions(-) diff --git a/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs b/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs index 0a49309..5b9c54e 100644 --- a/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs +++ b/WMS.WebUI/WMS.WebUI/Stores/DataStores/TransactionsStore.cs @@ -36,15 +36,15 @@ public async Task<List<TransactionView>> GetTransactionsAsync(string? search, st public async Task<List<PartnerViewModel>> GetPartnersAsync() { - var customersTask = _client.GetFromJsonAsync<List<PartnerViewModel>>("customers"); - var suppliersTask = _client.GetFromJsonAsync<List<PartnerViewModel>>("suppliers"); + var customersTask = _client.GetFromJsonAsync<PaginatedApiResponse<PartnerViewModel>>("customers"); + var suppliersTask = _client.GetFromJsonAsync<PaginatedApiResponse<PartnerViewModel>>("suppliers"); await Task.WhenAll(customersTask, suppliersTask); - customersTask.Result!.ForEach(el => el.Type = PartnerType.Customer); - suppliersTask.Result!.ForEach(el => el.Type = PartnerType.Supplier); + customersTask.Result!.Data.ForEach(el => el.Type = PartnerType.Customer); + suppliersTask.Result!.Data.ForEach(el => el.Type = PartnerType.Supplier); - return [.. customersTask.Result, .. suppliersTask.Result]; + return [.. customersTask.Result.Data, .. suppliersTask.Result.Data]; } public async Task<TransactionView> Create(CreateTransactionViewModel transaction) diff --git a/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml b/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml index 3a352d9..76d878a 100644 --- a/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml +++ b/WMS.WebUI/WMS.WebUI/Views/Products/Create.cshtml @@ -1,72 +1,33 @@ -@model WMS.WebUI.ViewModels.ProductViewModel +@using WMS.WebUI.ViewModels +@model ProductViewModel; -@{ - ViewData["Title"] = "Create Product"; -} - -<div class="m-5"> - <h4>Create Product</h4> - <hr /> - - <form asp-action="Create"> - <div class="form-group row"> - <label asp-for="Name" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <input asp-for="Name" class="form-control" /> - <span asp-validation-for="Name" class="text-danger"></span> - </div> - </div> - <div class="form-group row"> - <label asp-for="Description" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <textarea asp-for="Description" class="form-control"></textarea> - <span asp-validation-for="Description" class="text-danger"></span> - </div> - </div> - <div class="form-group row"> - <label asp-for="SalePrice" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <input asp-for="SalePrice" class="form-control" /> - <span asp-validation-for="SalePrice" class="text-danger"></span> - </div> - </div> - <div class="form-group row"> - <label asp-for="SupplyPrice" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <input asp-for="SupplyPrice" class="form-control" /> - <span asp-validation-for="SupplyPrice" class="text-danger"></span> - </div> - </div> - <div class="form-group row"> - <label asp-for="QuantityInStock" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <input asp-for="QuantityInStock" class="form-control" /> - <span asp-validation-for="QuantityInStock" class="text-danger"></span> - </div> +<div class="row m-5"> + <form id="product-form" class="d-flex flex-column gap-4" asp-action="Create"> + <h3 class="text-center">Create Product</h3> + <div class="form-group d-flex gap-5"> + <ejs-combobox id="categories" + dataSource="@ViewBag.Categories" + value="@ViewBag.SelectedCategoryId" + placeholder="Select a category" + name="CategoryId" + floatLabelType="Auto" + width="50%"> + <e-combobox-fields value="Id" text="Name"></e-combobox-fields> + </ejs-combobox> + <ejs-textbox id="name" name="Name" placeholder="Product Name" floatLabelType="Auto" width="50%"></ejs-textbox> + </div> + <div class="form-group d-flex gap-5"> + <ejs-numerictextbox id="sale-price" name="SalePrice" floatLabelType="Auto" format="c2" min="0" value="0" placeholder="Sale Price" width="50%"></ejs-numerictextbox> + <ejs-numerictextbox id="supply-price" name="SupplyPrice" floatLabelType="Auto" format="c2" min="0" value="0" placeholder="Supply Price" width="50%"></ejs-numerictextbox> + </div> + <div class="form-group d-flex gap-5"> + <ejs-numerictextbox id="in-stock" name="QuantityInStock" floatLabelType="Auto" min="0" value="0" placeholder="Quantity In Stock" width="50%"></ejs-numerictextbox> + <ejs-numerictextbox id="low-quantity" name="LowQuantityAmount" floatLabelType="Auto" min="0" value="0" placeholder="Low Quantity Amount" width="50%"></ejs-numerictextbox> + </div> + <div class="form-group"> + <ejs-textarea id="description" name="Description" floatLabelType="Auto" placeholder="Description"></ejs-textarea> </div> - <div class="form-group row"> - <label asp-for="LowQuantityAmount" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <input asp-for="LowQuantityAmount" class="form-control" /> - <span asp-validation-for="LowQuantityAmount" class="text-danger"></span> - </div> - </div> - <div class="form-group row"> - <label asp-for="CategoryId" class="col-sm-2 col-form-label"></label> - <div class="col-sm-10"> - <select asp-for="CategoryId" class="form-control" asp-items="ViewBag.Categories"></select> - <span asp-validation-for="CategoryId" class="text-danger"></span> - </div> - </div> - <div class="form-group mt-3"> - <input type="submit" value="Create" class="btn btn-primary" /> - <a asp-action="Index" class="btn btn-secondary">Back to List</a> - </div> - </form> -</div> -@section Scripts { - @{ - await Html.RenderPartialAsync("_ValidationScriptsPartial"); - } -} + <ejs-button id="submit" content="Submit" class="btn-success"></ejs-button> + </form> +</div> \ No newline at end of file