diff --git a/WebVentaCoche/Controllers/AccountController.cs b/WebVentaCoche/Controllers/AccountController.cs index f40d542..f60c960 100644 --- a/WebVentaCoche/Controllers/AccountController.cs +++ b/WebVentaCoche/Controllers/AccountController.cs @@ -34,7 +34,7 @@ namespace WebVentaCoche.Controllers var addresses = await _context.Addresses.Where(a => a.UserId == id).ToListAsync(); - var vm = new UserDetailsViewModel + var vm = new AccountDetailsViewModel { Id = user.Id, Name = user.Name, @@ -66,7 +66,7 @@ namespace WebVentaCoche.Controllers var addresses = await _context.Addresses.Where(a => a.UserId == userId).ToListAsync(); - var vm = new UserDetailsViewModel + var vm = new AccountDetailsViewModel { Id = user.Id, Name = user.Name, diff --git a/WebVentaCoche/Controllers/HomeController.cs b/WebVentaCoche/Controllers/HomeController.cs index 966fa37..2f75510 100644 --- a/WebVentaCoche/Controllers/HomeController.cs +++ b/WebVentaCoche/Controllers/HomeController.cs @@ -38,7 +38,7 @@ namespace WebVentaCoche.Controllers return View(products); } - //GET:/Home/ProductsDetailsHome/5 + //GET:/Home/ProductsDetailsHome/{id} public async Task ProductsDetailsHome(int id) { if (id <= 0) diff --git a/WebVentaCoche/Controllers/SalesController.cs b/WebVentaCoche/Controllers/SalesController.cs new file mode 100644 index 0000000..b8a7f6b --- /dev/null +++ b/WebVentaCoche/Controllers/SalesController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using WebVentaCoche.DataBase; +using WebVentaCoche.Enums; +using WebVentaCoche.ViewModels; + +namespace WebVentaCoche.Controllers +{ + [Authorize(Roles = "Administrador")] + public class SalesController : Controller + { + private readonly ApplicationDbContext _context; + public SalesController(ApplicationDbContext context) + { + _context = context; + } + + //GET:/Sales/ + public IActionResult Index() + { + return View(); + } + + //GET:/Sales/GetMonthlyData + [HttpGet] + public async Task GetMonthlyData() + { + var query = + from d in _context.OrderDetails + where d.Order.Status == OrderStatus.Delivered + let m = d.Order.OrderDate + group d by new { d.Product.Name, Year = m.Year, Month = m.Month } into g + orderby g.Key.Name, g.Key.Year, g.Key.Month + select new SalesViewModel + { + ProductName = g.Key.Name!, + Month = $"{g.Key.Year}-{g.Key.Month:D2}", + Units = g.Sum(x => x.Quantity) + }; + + var data = await query.ToListAsync(); + return Json(data); + } + + } +} diff --git a/WebVentaCoche/Controllers/UserController.cs b/WebVentaCoche/Controllers/UserController.cs index 277722c..d9db282 100644 --- a/WebVentaCoche/Controllers/UserController.cs +++ b/WebVentaCoche/Controllers/UserController.cs @@ -10,6 +10,8 @@ using WebVentaCoche.Models; using WebVentaCoche.Services; using WebVentaCoche.ViewModels; using static System.Runtime.InteropServices.JavaScript.JSType; +using WebVentaCoche.DataBase; +using Microsoft.EntityFrameworkCore; namespace WebVentaCoche.Controllers { @@ -19,13 +21,15 @@ namespace WebVentaCoche.Controllers private readonly IConfiguration _configuration; private readonly EmailService _emailService; private readonly VerificationService _verificationService; + private readonly ApplicationDbContext _context; - public UserController(IUserHelper userHelper, IConfiguration configuration, EmailService emailService, VerificationService verificationService) + public UserController(IUserHelper userHelper, IConfiguration configuration, EmailService emailService, VerificationService verificationService, ApplicationDbContext context) { _userHelper = userHelper; _configuration = configuration; _emailService = emailService; _verificationService = verificationService; + _context = context; } private async Task SenConfirmationEmail(User user, string token, string confirmationLink) @@ -198,6 +202,94 @@ namespace WebVentaCoche.Controllers return View(); } + public async Task Index() + { + var users = await _context.Users.ToListAsync(); + return View(users); + } + + //GET:/Users/Edit/{id} + [HttpGet] + public async Task Edit(string id) + { + if (string.IsNullOrEmpty(id)) + return NotFound(); + + var user = await _context.Users.FindAsync(id); + if (user == null) + return NotFound(); + + var vm = new UserViewModel + { + Id = user.Id, + Name = user.Name, + Surname = user.Surname, + Email = user.Email, + PhoneNumber = user.PhoneNumber, + UserType = user.UserType + }; + return View(vm); + } + + //POST:/Users/Edit + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(UserViewModel vm) + { + if (!ModelState.IsValid) + return View(vm); + + var user = await _context.Users.FindAsync(vm.Id); + if (user == null) + return NotFound(); + + // Actualiza campos + user.Name = vm.Name; + user.Surname = vm.Surname; + user.Email = vm.Email; + user.UserName = vm.Email; // si cambias email conviene actualizar UserName + user.PhoneNumber = vm.PhoneNumber; + user.UserType = vm.UserType; + + _context.Update(user); + await _context.SaveChangesAsync(); + + return RedirectToAction(nameof(Index)); + } + + //GET:/Users/Create + public IActionResult Create() + { + return View(); + } + + //POST:/Users/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create(RegisterViewModel model) + { + if (!ModelState.IsValid) return View(model); + + // Valida y crea el usuario + var user = new User + { + Name = model.Name, + Surname = model.Surname, + Email = model.Email, + PhoneNumber = model.PhoneNumber, + UserName = model.Email, + UserType = Enums.UserType.Usuario + }; + var result = await _userHelper.AddUserAsync(user, model.Password); + if (result.Succeeded) + return RedirectToAction(nameof(Index)); + + // Si hay errores, los mostramos + foreach (var e in result.Errors) + ModelState.AddModelError("", e.Description); + return View(model); + } + private TokenAuth BuildToken(User user) { var claims = new List diff --git a/WebVentaCoche/Helpers/MappingProfile.cs b/WebVentaCoche/Helpers/MappingProfile.cs index 3dbb009..ae3975e 100644 --- a/WebVentaCoche/Helpers/MappingProfile.cs +++ b/WebVentaCoche/Helpers/MappingProfile.cs @@ -8,7 +8,7 @@ namespace WebVentaCoche.Helpers { public MappingProfile() { - CreateMap() + CreateMap() .ForMember(dest => dest.Addresses, opt => opt.MapFrom(src => src.Addresses)); diff --git a/WebVentaCoche/ViewModels/UserDetailsViewModel.cs b/WebVentaCoche/ViewModels/AccountDetailsViewModel.cs similarity index 95% rename from WebVentaCoche/ViewModels/UserDetailsViewModel.cs rename to WebVentaCoche/ViewModels/AccountDetailsViewModel.cs index e5789ef..0cd9f47 100644 --- a/WebVentaCoche/ViewModels/UserDetailsViewModel.cs +++ b/WebVentaCoche/ViewModels/AccountDetailsViewModel.cs @@ -3,7 +3,7 @@ using WebVentaCoche.Enums; namespace WebVentaCoche.ViewModels { - public class UserDetailsViewModel + public class AccountDetailsViewModel { public string Id { get; set; } = null!; diff --git a/WebVentaCoche/ViewModels/ProductsHomeViewModel.cs b/WebVentaCoche/ViewModels/ProductsHomeViewModel.cs new file mode 100644 index 0000000..72f1076 --- /dev/null +++ b/WebVentaCoche/ViewModels/ProductsHomeViewModel.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using WebVentaCoche.Models; + +namespace WebVentaCoche.ViewModels +{ + public class ProductsHomeViewModel + { + public IEnumerable Products { get; set; } = new List(); + public int CurrentPage { get; set; } + public int TotalPages { get; set; } + } +} \ No newline at end of file diff --git a/WebVentaCoche/ViewModels/SalesViewModel.cs b/WebVentaCoche/ViewModels/SalesViewModel.cs new file mode 100644 index 0000000..8c310cb --- /dev/null +++ b/WebVentaCoche/ViewModels/SalesViewModel.cs @@ -0,0 +1,9 @@ +namespace WebVentaCoche.ViewModels +{ + public class SalesViewModel + { + public string ProductName { get; set; } = null!; + public string Month { get; set; } = null!; + public int Units { get; set; } + } +} diff --git a/WebVentaCoche/ViewModels/UserViewModel.cs b/WebVentaCoche/ViewModels/UserViewModel.cs new file mode 100644 index 0000000..902a555 --- /dev/null +++ b/WebVentaCoche/ViewModels/UserViewModel.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using WebVentaCoche.Enums; + +namespace WebVentaCoche.ViewModels +{ + public class UserViewModel + { + [Required] + public string Id { get; set; } = null!; + + [Required] + [Display(Name = "Nombre")] + public string Name { get; set; } = null!; + + [Required] + [Display(Name = "Apellidos")] + public string Surname { get; set; } = null!; + + [Required] + [EmailAddress] + public string Email { get; set; } = null!; + + [Phone] + [Display(Name = "Teléfono")] + public string? PhoneNumber { get; set; } + + [Required] + [Display(Name = "Tipo de Usuario")] + public UserType UserType { get; set; } + } +} diff --git a/WebVentaCoche/Views/Account/Details.cshtml b/WebVentaCoche/Views/Account/Details.cshtml index 2ca1975..2fb4f5e 100644 --- a/WebVentaCoche/Views/Account/Details.cshtml +++ b/WebVentaCoche/Views/Account/Details.cshtml @@ -1,4 +1,4 @@ -@model WebVentaCoche.ViewModels.UserDetailsViewModel +@model WebVentaCoche.ViewModels.AccountDetailsViewModel @{ ViewData["Title"] = "Detalles de Cuenta"; diff --git a/WebVentaCoche/Views/Account/Settings.cshtml b/WebVentaCoche/Views/Account/Settings.cshtml index 4745115..265bd6f 100644 --- a/WebVentaCoche/Views/Account/Settings.cshtml +++ b/WebVentaCoche/Views/Account/Settings.cshtml @@ -1,4 +1,4 @@ -@model WebVentaCoche.ViewModels.UserDetailsViewModel +@model WebVentaCoche.ViewModels.AccountDetailsViewModel @{ Layout = "_AccountLayout"; } diff --git a/WebVentaCoche/Views/Home/ProductsHome.cshtml b/WebVentaCoche/Views/Home/ProductsHome.cshtml index 2c041dc..70eea15 100644 --- a/WebVentaCoche/Views/Home/ProductsHome.cshtml +++ b/WebVentaCoche/Views/Home/ProductsHome.cshtml @@ -1,34 +1,144 @@ @model IEnumerable +@{ + ViewData["Title"] = "Nuestros Productos"; +} -
-

Nuestros Productos

+ + + +
+

@ViewData["Title"]

- @foreach (var product in Model) - { -
-
-
-
- @if (!string.IsNullOrEmpty(product.ImagePath)) - { - @product.Name - } - else - { - Imagen no disponible - } -
-
-
-
@product.Name
-

@product.ShortDescription

- Más detalles -
-
+ + + + +
+ + + + + + @foreach (var p in Model) + { + + + + } + +
+
+
+
+ +
+
+
@p.Name
+

@p.ShortDescription

+

@p.Price.ToString("0.00") €

+ + Más detalles + +
+
+
+
+
+ +@section Scripts { + +} diff --git a/WebVentaCoche/Views/Sales/Index.cshtml b/WebVentaCoche/Views/Sales/Index.cshtml new file mode 100644 index 0000000..ad4657e --- /dev/null +++ b/WebVentaCoche/Views/Sales/Index.cshtml @@ -0,0 +1,176 @@ +@{ + ViewData["Title"] = "Ventas Mensuales"; + Layout = "_Layout"; +} + +
+

@ViewData["Title"]

+ +
+
+ + + + Mantén Ctrl para seleccionar varios productos. + +
+
+ +
+ +
+
+ +@section Scripts { + + +} diff --git a/WebVentaCoche/Views/Shared/_Layout.cshtml b/WebVentaCoche/Views/Shared/_Layout.cshtml index b254b2b..aedd686 100644 --- a/WebVentaCoche/Views/Shared/_Layout.cshtml +++ b/WebVentaCoche/Views/Shared/_Layout.cshtml @@ -3,93 +3,94 @@ @using System.Security.Claims @inject IHttpContextAccessor HttpContextAccessor - @{ - // Determina si es la página de gestión para ajustar la clase body var isGestionPage = Context.Request.Path.ToString().StartsWith("/Products"); - // Obtener lista de IDs de productos almacenados en sesión var cartItemIds = CartSessionHelper.GetCartItems(HttpContextAccessor.HttpContext.Session); int cartItemCount = cartItemIds.Count; - var userId = HttpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; + + var user = HttpContextAccessor.HttpContext.User; + var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); } - + @ViewData["Title"] - WebVentaCoche - - - - - + + + -
+

Bienvenido a WebVentaCoche

Tu tienda de confianza para productos de coches.

@@ -115,6 +114,12 @@

© 2024 WebVentaCoche. Todos los derechos reservados.

+ + + + + + @RenderSection("Scripts", required: false) diff --git a/WebVentaCoche/Views/User/Create.cshtml b/WebVentaCoche/Views/User/Create.cshtml new file mode 100644 index 0000000..3be7d8e --- /dev/null +++ b/WebVentaCoche/Views/User/Create.cshtml @@ -0,0 +1,46 @@ +@model WebVentaCoche.ViewModels.RegisterViewModel + +@{ + ViewData["Title"] = "Crear Usuario"; +} + +

Crear Nuevo Usuario

+ +
+ @Html.AntiForgeryToken() + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ + + Cancelar +
+ +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WebVentaCoche/Views/User/Edit.cshtml b/WebVentaCoche/Views/User/Edit.cshtml new file mode 100644 index 0000000..4a2457a --- /dev/null +++ b/WebVentaCoche/Views/User/Edit.cshtml @@ -0,0 +1,57 @@ +@using WebVentaCoche.Enums +@model WebVentaCoche.ViewModels.UserViewModel + +@{ + ViewData["Title"] = "Editar Usuario"; +} + +
+

Editar Usuario

+ +
+ @Html.AntiForgeryToken() + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + + Cancelar +
+
+ +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } +} diff --git a/WebVentaCoche/Views/User/Index.cshtml b/WebVentaCoche/Views/User/Index.cshtml new file mode 100644 index 0000000..55c4d46 --- /dev/null +++ b/WebVentaCoche/Views/User/Index.cshtml @@ -0,0 +1,40 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Listado de Usuarios"; +} + +

Usuarios Registrados

+ + + Crear Usuario + + + + + + + + + + + + + + @foreach (var u in Model) + { + + + + + + + + } + +
EmailNombreApellidosTipo
@u.Email@u.Name@u.Surname@u.UserType + + + +
diff --git a/WebVentaCoche/WebVentaCoche.csproj b/WebVentaCoche/WebVentaCoche.csproj index 6a99885..ab58a77 100644 --- a/WebVentaCoche/WebVentaCoche.csproj +++ b/WebVentaCoche/WebVentaCoche.csproj @@ -18,9 +18,14 @@ + + + true + PreserveNewest + true PreserveNewest diff --git a/WebVentaCoche/wwwroot/css/site.css b/WebVentaCoche/wwwroot/css/site.css index c6da2d9..58f1e20 100644 --- a/WebVentaCoche/wwwroot/css/site.css +++ b/WebVentaCoche/wwwroot/css/site.css @@ -1,4 +1,4 @@ -html { +html { font-size: 14px; } @@ -23,6 +23,7 @@ body { position: relative; } + /* Capa para la imagen borrosa */ body::before { content: ""; @@ -37,23 +38,23 @@ body { width: 100%; height: 100%; filter: blur(3px); /* Ajusta el desenfoque */ - z-index: -1; /* Envía la capa al fondo */ + z-index: -1; /* Envía la capa al fondo */ opacity: 0.7; /* Transparencia opcional */ } -/* Asegura que el contenido esté por encima del fondo */ +/* Asegura que el contenido esté por encima del fondo */ .container, nav, footer { position: relative; z-index: 1; /* Solo los elementos necesarios por encima del fondo */ } -/* El jumbotron debe tener un índice más bajo para no interferir */ +/* El jumbotron debe tener un índice más bajo para no interferir */ .jumbotron { position: relative; z-index: 0; /* Jumbotron no debe tapar los desplegables */ } -/* Asegura que los desplegables del navbar estén por encima */ +/* Asegura que los desplegables del navbar estén por encima */ .dropdown-menu { z-index: 1000 !important; /* Bootstrap ya lo configura, pero forzamos si es necesario */ } @@ -70,3 +71,50 @@ body { } +#productsTable tbody { + display: grid; + grid-template-columns: repeat(auto-fill,minmax(45%,1fr)); + gap: 1rem; +} + +#productsTable tr { + display: contents; +} + +#productsTable td { + border: none; + padding: 0; +} + +#tooltip { + position: absolute; + padding: 6px 10px; + background: rgba(0,0,0,0.7); + color: #fff; + border-radius: 4px; + pointer-events: none; + opacity: 0; + z-index: 10000; +} + +#legend { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 1.5rem; +} + + #legend div { + display: flex; + align-items: center; + } + + #legend div > div { + width: 16px; + height: 16px; + margin-right: 6px; + } + + + + diff --git a/WebVentaCoche/wwwroot/images/ChatGPT Image 1 may 2025, 12_30_53.png b/WebVentaCoche/wwwroot/images/ChatGPT Image 1 may 2025, 12_30_53.png new file mode 100644 index 0000000..1fa79e1 Binary files /dev/null and b/WebVentaCoche/wwwroot/images/ChatGPT Image 1 may 2025, 12_30_53.png differ diff --git a/WebVentaCoche/wwwroot/images/ItLWKuZ771WH8owuo2Fq9koWKIEXx7OueEACmYfG.jpg b/WebVentaCoche/wwwroot/images/ItLWKuZ771WH8owuo2Fq9koWKIEXx7OueEACmYfG.jpg new file mode 100644 index 0000000..a1ea195 Binary files /dev/null and b/WebVentaCoche/wwwroot/images/ItLWKuZ771WH8owuo2Fq9koWKIEXx7OueEACmYfG.jpg differ diff --git a/WebVentaCoche/wwwroot/images/check-engine-warning.webp b/WebVentaCoche/wwwroot/images/check-engine-warning.webp new file mode 100644 index 0000000..9e36407 Binary files /dev/null and b/WebVentaCoche/wwwroot/images/check-engine-warning.webp differ