diff --git a/Aberwyn/Controllers/FoodMenuController.cs b/Aberwyn/Controllers/FoodMenuController.cs index c405fd5..6a663c9 100644 --- a/Aberwyn/Controllers/FoodMenuController.cs +++ b/Aberwyn/Controllers/FoodMenuController.cs @@ -17,13 +17,155 @@ namespace Aberwyn.Controllers { private readonly IConfiguration _configuration; private readonly IHostEnvironment _env; + private readonly MenuService _menuService; + private readonly ApplicationDbContext _context; - public FoodMenuController(IConfiguration configuration, IHostEnvironment env) + public FoodMenuController(MenuService menuService, IConfiguration configuration, IHostEnvironment env, ApplicationDbContext context) { + _menuService = menuService; _configuration = configuration; _env = env; + _context = context; } - [Authorize(Roles = "Budget")] + + [HttpGet] + public IActionResult PizzaOrder() + { + var pizzas = _menuService.GetMealsByCategory("Pizza"); + ViewBag.Pizzas = pizzas; + + int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId"); + if (lastId.HasValue) + { + var previousOrder = _context.PizzaOrders.FirstOrDefault(o => o.Id == lastId.Value); + if (previousOrder != null) + { + ViewBag.PreviousOrder = previousOrder; + } + } + + return View(); + } + + [HttpGet] + public IActionResult EditPizzaOrder(int id) + { + var order = _context.PizzaOrders.FirstOrDefault(o => o.Id == id); + if (order == null) + { + return RedirectToAction("PizzaOrder"); + } + + // Sätt session sć vi vet att det är en uppdatering + HttpContext.Session.SetInt32("LastPizzaOrderId", order.Id); + + // Visa formuläret + TempData["ForceShowForm"] = "true"; + + return RedirectToAction("PizzaOrder"); + } + + + + [HttpPost] + public IActionResult ClearPizzaSession() + { + HttpContext.Session.Remove("LastPizzaOrderId"); + return RedirectToAction("PizzaOrder"); + } + + + [HttpPost] + public IActionResult PizzaOrder(string customerName, string pizzaName, string ingredients, int? orderId) + { + if (string.IsNullOrWhiteSpace(customerName) || string.IsNullOrWhiteSpace(pizzaName)) + { + TempData["Error"] = "Fyll i bćde namn och pizza!"; + return RedirectToAction("PizzaOrder"); + } + + int? lastId = orderId ?? HttpContext.Session.GetInt32("LastPizzaOrderId"); + PizzaOrder order; + + if (lastId.HasValue) + { + // Uppdatera befintlig order + order = _context.PizzaOrders.FirstOrDefault(o => o.Id == lastId.Value); + if (order != null) + { + order.CustomerName = customerName.Trim(); + order.PizzaName = pizzaName.Trim(); + order.IngredientsJson = ingredients; + order.Status = "Ej bekräftad"; // ćterställ status om du vill + _context.SaveChanges(); + + TempData["Success"] = $"Din beställning har uppdaterats!"; + return RedirectToAction("PizzaOrder"); + } + } + + // Annars skapa ny + order = new PizzaOrder + { + CustomerName = customerName.Trim(), + PizzaName = pizzaName.Trim(), + IngredientsJson = ingredients, + Status = "Ej bekräftad", + OrderedAt = DateTime.Now + }; + + _context.PizzaOrders.Add(order); + _context.SaveChanges(); + TempData["ForceShowForm"] = "true"; + + + HttpContext.Session.SetInt32("LastPizzaOrderId", order.Id); + + TempData["Success"] = $"Tack {order.CustomerName}! Din pizza är beställd!"; + return RedirectToAction("PizzaOrder"); + } + + + + [Authorize(Roles = "Chef")] + public IActionResult PizzaAdmin(DateTime? date) + { + var selectedDate = date ?? DateTime.Today; + var nextDay = selectedDate.AddDays(1); + + var allOrders = _context.PizzaOrders + .Where(o => o.OrderedAt >= selectedDate && o.OrderedAt < nextDay) + .OrderBy(o => o.OrderedAt) + .ToList(); + + ViewBag.ActiveOrders = allOrders + .Where(o => o.Status != "Klar") + .ToList(); + + ViewBag.CompletedOrders = allOrders + .Where(o => o.Status == "Klar") + .ToList(); + + return View(); + } + + + [HttpPost] + [Authorize(Roles = "Chef")] + public IActionResult UpdatePizzaOrder(int id, string status, string ingredientsJson) + { + var order = _context.PizzaOrders.FirstOrDefault(p => p.Id == id); + if (order != null) + { + order.Status = status; + order.IngredientsJson = ingredientsJson; + _context.SaveChanges(); + } + + return RedirectToAction("PizzaAdmin"); + } + + [Authorize(Roles = "Chef")] public IActionResult Veckomeny(int? week, int? year) { var menuService = new MenuService(_configuration, _env); diff --git a/Aberwyn/Controllers/LoginController.cs b/Aberwyn/Controllers/LoginController.cs new file mode 100644 index 0000000..e3324b8 --- /dev/null +++ b/Aberwyn/Controllers/LoginController.cs @@ -0,0 +1,15 @@ +ï»żusing Microsoft.AspNetCore.Mvc; + +namespace Aberwyn.Controllers +{ + public class LoginController : Controller + { + [HttpGet] + public IActionResult Login(string returnUrl = "/") + { + ViewBag.ReturnUrl = returnUrl; + return View(); // View mĂ„ste heta "Login.cshtml" + } + + } +} \ No newline at end of file diff --git a/Aberwyn/Controllers/PizzaController.cs b/Aberwyn/Controllers/PizzaController.cs new file mode 100644 index 0000000..f9cd16c --- /dev/null +++ b/Aberwyn/Controllers/PizzaController.cs @@ -0,0 +1,58 @@ +ï»żusing Microsoft.AspNetCore.Mvc; +using Aberwyn.Data; +using Aberwyn.Models; +using System.Linq; + +namespace Aberwyn.Controllers +{ + public class PizzaController : Controller + { + private readonly MenuService _menuService; + private readonly ApplicationDbContext _context; + + public PizzaController(MenuService menuService, ApplicationDbContext context) + { + _menuService = menuService; + _context = context; + } + + [HttpGet] + public IActionResult Order() + { + var pizzas = _menuService.GetMeals() + .Where(m => m.Name.ToLower().Contains("pizza")) + .OrderBy(m => m.Name) + .ToList(); + + var orders = _context.PizzaOrders + .OrderByDescending(o => o.OrderedAt) + .ToList(); + + ViewBag.Pizzas = pizzas; + ViewBag.ExistingOrders = orders; + return View(); + } + + [HttpPost] + public IActionResult Order(string customerName, string pizzaName) + { + if (string.IsNullOrWhiteSpace(customerName) || string.IsNullOrWhiteSpace(pizzaName)) + { + TempData["Error"] = "BĂ„de namn och pizza mĂ„ste anges."; + return RedirectToAction("Order"); + } + + var order = new PizzaOrder + { + CustomerName = customerName.Trim(), + PizzaName = pizzaName.Trim() + }; + + _context.PizzaOrders.Add(order); + _context.SaveChanges(); + + TempData["Success"] = "BestĂ€llningen har lagts!"; + return RedirectToAction("Order"); + } + } +} diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs index 3d1558a..b8b01d7 100644 --- a/Aberwyn/Data/ApplicationDbContext.cs +++ b/Aberwyn/Data/ApplicationDbContext.cs @@ -15,5 +15,7 @@ namespace Aberwyn.Data public DbSet BudgetCategories { get; set; } public DbSet BudgetItems { get; set; } public DbSet PushSubscribers { get; set; } + public DbSet PizzaOrders { get; set; } + } } diff --git a/Aberwyn/Data/MenuService.cs b/Aberwyn/Data/MenuService.cs index 07fb322..2e1f6ba 100644 --- a/Aberwyn/Data/MenuService.cs +++ b/Aberwyn/Data/MenuService.cs @@ -471,6 +471,61 @@ namespace Aberwyn.Data SaveIngredients(meal.Id, meal.Ingredients); } } + public List GetMealsByCategory(string category) + { + var meals = new List(); + + using var connection = GetConnection(); + connection.Open(); + + string query = @" + SELECT m.Id, m.Name, m.Category, m.Description, m.ImageUrl, m.ImageData, m.ImageMimeType, + i.Id AS IngredientId, i.Quantity, i.Item + FROM Meals m + LEFT JOIN Ingredients i ON m.Id = i.MealId + WHERE m.Category = @category + ORDER BY m.Name, i.Id"; + + using var cmd = new MySqlCommand(query, connection); + cmd.Parameters.AddWithValue("@category", category); + + using var reader = cmd.ExecuteReader(); + Dictionary mealMap = new(); + + while (reader.Read()) + { + int mealId = reader.GetInt32("Id"); + + if (!mealMap.ContainsKey(mealId)) + { + mealMap[mealId] = new Meal + { + Id = mealId, + Name = reader.GetString("Name"), + Category = reader.IsDBNull(reader.GetOrdinal("Category")) ? null : reader.GetString("Category"), + Description = reader.IsDBNull(reader.GetOrdinal("Description")) ? null : reader.GetString("Description"), + ImageUrl = reader.IsDBNull(reader.GetOrdinal("ImageUrl")) ? null : reader.GetString("ImageUrl"), + ImageData = reader.IsDBNull(reader.GetOrdinal("ImageData")) ? null : (byte[])reader["ImageData"], + ImageMimeType = reader.IsDBNull(reader.GetOrdinal("ImageMimeType")) ? null : reader.GetString("ImageMimeType"), + Ingredients = new List() + }; + } + + if (!reader.IsDBNull(reader.GetOrdinal("IngredientId"))) + { + mealMap[mealId].Ingredients.Add(new Ingredient + { + Id = reader.GetInt32("IngredientId"), + MealId = mealId, + Quantity = reader.IsDBNull(reader.GetOrdinal("Quantity")) ? "" : reader.GetString("Quantity"), + Item = reader.IsDBNull(reader.GetOrdinal("Item")) ? "" : reader.GetString("Item") + }); + } + } + + return mealMap.Values.ToList(); + } + public List GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate) { var results = new List(); diff --git a/Aberwyn/Migrations/20250524103706_AddPizzaOrder.Designer.cs b/Aberwyn/Migrations/20250524103706_AddPizzaOrder.Designer.cs new file mode 100644 index 0000000..0a9bd2a --- /dev/null +++ b/Aberwyn/Migrations/20250524103706_AddPizzaOrder.Designer.cs @@ -0,0 +1,403 @@ +ï»ż// +using System; +using Aberwyn.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aberwyn.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250524103706_AddPizzaOrder")] + partial class AddPizzaOrder + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.36") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetPeriodId") + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BudgetCategoryId") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BudgetPeriods"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod") + .WithMany("Categories") + .HasForeignKey("BudgetPeriodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetPeriod"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory") + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetCategory"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Navigation("Categories"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250524103706_AddPizzaOrder.cs b/Aberwyn/Migrations/20250524103706_AddPizzaOrder.cs new file mode 100644 index 0000000..300b3bc --- /dev/null +++ b/Aberwyn/Migrations/20250524103706_AddPizzaOrder.cs @@ -0,0 +1,19 @@ +ï»żusing Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddPizzaOrder : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.Designer.cs b/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.Designer.cs new file mode 100644 index 0000000..dc88690 --- /dev/null +++ b/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.Designer.cs @@ -0,0 +1,432 @@ +ï»ż// +using System; +using Aberwyn.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aberwyn.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250524121511_AddPizzaOrderTable")] + partial class AddPizzaOrderTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.36") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetPeriodId") + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BudgetCategoryId") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BudgetPeriods"); + }); + + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CustomerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IngredientsJson") + .HasColumnType("longtext"); + + b.Property("OrderedAt") + .HasColumnType("datetime(6)"); + + b.Property("PizzaName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PizzaOrders"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod") + .WithMany("Categories") + .HasForeignKey("BudgetPeriodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetPeriod"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory") + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetCategory"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Navigation("Categories"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.cs b/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.cs new file mode 100644 index 0000000..ccd8b2f --- /dev/null +++ b/Aberwyn/Migrations/20250524121511_AddPizzaOrderTable.cs @@ -0,0 +1,42 @@ +ï»żusing System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddPizzaOrderTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PizzaOrders", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CustomerName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + PizzaName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IngredientsJson = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Status = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + OrderedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PizzaOrders", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PizzaOrders"); + } + } +} diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs index d58148b..d7eb181 100644 --- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs @@ -162,6 +162,35 @@ namespace Aberwyn.Migrations b.ToTable("BudgetPeriods"); }); + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CustomerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IngredientsJson") + .HasColumnType("longtext"); + + b.Property("OrderedAt") + .HasColumnType("datetime(6)"); + + b.Property("PizzaName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PizzaOrders"); + }); + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => { b.Property("Id") diff --git a/Aberwyn/Models/MenuViewModel.cs b/Aberwyn/Models/MenuViewModel.cs index 849eb2f..c23b5a3 100644 --- a/Aberwyn/Models/MenuViewModel.cs +++ b/Aberwyn/Models/MenuViewModel.cs @@ -40,6 +40,7 @@ namespace Aberwyn.Models public string Name { get; set; } public string Description { get; set; } public string ProteinType { get; set; } + public string Category { get; set; } public string CarbType { get; set; } public string RecipeUrl { get; set; } public string ImageUrl { get; set; } @@ -61,6 +62,8 @@ namespace Aberwyn.Models { public int Id { get; set; } public string Name { get; set; } + public string Category { get; set; } + public string? ImageUrl { get; set; } public string? ImageData { get; set; } // base64 public string? ImageMimeType { get; set; } @@ -71,6 +74,7 @@ namespace Aberwyn.Models { Id = meal.Id, Name = meal.Name, + Category = meal.Category, ImageUrl = meal.ImageUrl, ImageMimeType = meal.ImageMimeType, ImageData = meal.ImageData != null ? Convert.ToBase64String(meal.ImageData) : null diff --git a/Aberwyn/Models/PizzaOrder.cs b/Aberwyn/Models/PizzaOrder.cs new file mode 100644 index 0000000..ddeee67 --- /dev/null +++ b/Aberwyn/Models/PizzaOrder.cs @@ -0,0 +1,22 @@ +ï»żusing System.ComponentModel.DataAnnotations; + +namespace Aberwyn.Models +{ + public class PizzaOrder + { + public int Id { get; set; } + + [Required] + public string CustomerName { get; set; } + + [Required] + public string PizzaName { get; set; } + + public string? IngredientsJson { get; set; } // lista i JSON-form + + public string Status { get; set; } = "Ej bekrĂ€ftad"; // "Ej bekrĂ€ftad", "BekrĂ€ftad", "Klar" + + public DateTime OrderedAt { get; set; } = DateTime.Now; + } + +} diff --git a/Aberwyn/Program.cs b/Aberwyn/Program.cs index 0ce3cc7..d4b8af4 100644 --- a/Aberwyn/Program.cs +++ b/Aberwyn/Program.cs @@ -21,6 +21,12 @@ builder.Services.AddControllersWithViews() // Ignorera null-vïżœrden vid serialisering opts.JsonSerializerOptions.IgnoreNullValues = true; }); +builder.Services.AddSession(options => +{ + options.IdleTimeout = TimeSpan.FromHours(1); + options.Cookie.HttpOnly = true; + options.Cookie.IsEssential = true; +}); builder.Services.AddRazorPages(); builder.Services.AddHttpClient(); @@ -64,8 +70,10 @@ builder.Services.AddSingleton(sp => }); builder.Services.Configure(builder.Configuration.GetSection("Vapid")); - - +builder.Services.ConfigureApplicationCookie(options => +{ + options.LoginPath = "/Identity/Account/Login"; // korrekt för ditt nuvarande upplĂ€gg +}); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); builder.Services.Configure(options => { @@ -99,6 +107,7 @@ if (!app.Environment.IsDevelopment()) app.UseStaticFiles(); app.UseRouting(); +app.UseSession(); app.UseAuthentication();; app.UseAuthorization(); diff --git a/Aberwyn/Views/FoodMenu/PizzaAdmin.cshtml b/Aberwyn/Views/FoodMenu/PizzaAdmin.cshtml new file mode 100644 index 0000000..b0b5a2d --- /dev/null +++ b/Aberwyn/Views/FoodMenu/PizzaAdmin.cshtml @@ -0,0 +1,145 @@ +ï»ż@using Newtonsoft.Json +@using Newtonsoft.Json.Serialization +@{ + ViewData["Title"] = "Pizza Admin"; + var activeOrders = ViewBag.ActiveOrders as List ?? new List(); + var completedOrders = ViewBag.CompletedOrders as List ?? new List(); +} + + + +

Aktiva bestÀllningar

+
+@foreach (var order in activeOrders) +{ + var ingredients = new List(); + try + { + if (!string.IsNullOrEmpty(order.IngredientsJson)) + { + ingredients = System.Text.Json.JsonSerializer.Deserialize>(order.IngredientsJson); + } + } + catch + { + ingredients = order.IngredientsJson.Split('\n').ToList(); + } + + var cardClass = order.Status switch + { + "BekrÀftad" => "border-warning", + "Klar" => "border-success", + _ => "border-info" + }; + +
+
@order.CustomerName
+
+
@order.PizzaName
+
    + @foreach (var ing in ingredients) + { +
  • @ing
  • + } +
+
+ + +
+ @if (order.Status == "Ej bekrÀftad") + { + + + } + else if (order.Status == "BekrÀftad") + { + + + } + +
+
+
+
+} +
+ +

FĂ€rdiga pizzor

+@if (completedOrders.Any()) +{ +
    + @foreach (var order in completedOrders) + { +
  • + @order.CustomerName – @order.PizzaName +
  • + } +
+} +else +{ +
Inga pizzor Àr klara Ànnu.
+} diff --git a/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml b/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml new file mode 100644 index 0000000..3733546 --- /dev/null +++ b/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml @@ -0,0 +1,226 @@ +ï»ż@using Newtonsoft.Json +@using Newtonsoft.Json.Serialization +@{ + ViewData["Title"] = "BestĂ€ll pizza"; + var pizzas = ViewBag.Pizzas as List; + var previousOrder = ViewBag.PreviousOrder as PizzaOrder; + var lastId = Context.Session.GetInt32("LastPizzaOrderId"); + var isCurrentOrder = previousOrder?.Id == lastId; +var forceShow = TempData["ForceShowForm"]?.ToString() == "true"; +var justOrdered = TempData["Success"]?.ToString()?.Contains("bestĂ€lld") == true; +var showForm = previousOrder == null || (forceShow && !justOrdered); + +} + + + +
+ + @if (TempData["Success"] != null) + { +
+ 🍕 @TempData["Success"] +
+ } + + @if (previousOrder != null) + { + var ingredienser = string.IsNullOrEmpty(previousOrder.IngredientsJson) + ? new List() + : System.Text.Json.JsonSerializer.Deserialize>(previousOrder.IngredientsJson); + +
+ 🍕 Du har redan bestĂ€llt:
+ @previousOrder.PizzaName + @if (ingredienser.Count > 0) + { + (med @string.Join(", ", ingredienser)) + } +
+ Status: @previousOrder.Status +
+
+ +
+
+ +
+
+ } + + @if (showForm) + { + +
+

Hej! Vad heter du? 😊

+ + +
+ + +
+

🍕 VĂ€lj en pizza

+
+ @foreach (var pizza in pizzas) + { +
+

@pizza.Name

+

@string.Join(", ", pizza.Ingredients?.Select(i => i.Item) ?? new List())

+
+ } +
+

🧑‍🍳 Skapa egen pizza

+

LĂ€gg till dina egna ingredienser

+
+
+
+ + +
+

Redigera pizza

+
    +
    + + +
    + + +
    + + +
    +

    BekrÀfta din bestÀllning

    +

    Din pizza:

    +

    +
      + +
      + + + + +
      +
      + } +
      + +@section Scripts { + +} diff --git a/Aberwyn/Views/Login/Index.cshtml b/Aberwyn/Views/Login/Index.cshtml new file mode 100644 index 0000000..c8bf475 --- /dev/null +++ b/Aberwyn/Views/Login/Index.cshtml @@ -0,0 +1,21 @@ +ï»ż@{ + ViewData["Title"] = "Logga in"; + var returnUrl = ViewBag.ReturnUrl as string ?? "/"; +} + +

      Logga in

      + +
      + + +
      + + +
      +
      + + +
      + + +
      diff --git a/Aberwyn/wwwroot/css/pizza.css b/Aberwyn/wwwroot/css/pizza.css new file mode 100644 index 0000000..0cd3af2 --- /dev/null +++ b/Aberwyn/wwwroot/css/pizza.css @@ -0,0 +1,174 @@ +ï»ż/* === PIZZERIA-INSPIRERAD STIL === */ + +.pizza-step { + display: none; + background-color: #fffaf0; + border-radius: 12px; + padding: 24px; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + max-width: 600px; + margin: 2rem auto; + animation: fadeIn 0.3s ease-in-out; +} + + .pizza-step.active { + display: block; + } + + .pizza-step h2, + #editTitle { + font-family: 'Georgia', serif; + font-size: 1.8rem; + color: #a00000; + margin-bottom: 1rem; + } + +/* === PIZZA LISTA === */ + +.pizza-list { + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + margin-top: 1rem; +} + +@media (min-width: 600px) { + .pizza-list { + grid-template-columns: 1fr 1fr; + } +} + +.pizza-card { + border: 2px solid #f4d5b3; + border-radius: 10px; + background: #fff8e1; + padding: 12px; + cursor: pointer; + transition: transform 0.2s ease, border-color 0.2s; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06); +} + + .pizza-card:hover { + border-color: #e63946; + transform: scale(1.02); + background-color: #fff0dc; + } + + .pizza-card h3 { + margin: 0; + color: #b30000; + font-weight: bold; + } + + .pizza-card p { + font-size: 0.9rem; + color: #555; + margin-top: 6px; + } + +/* === INGREDIENSER === */ + +#ingredientList { + list-style: none; + padding-left: 0; + margin-bottom: 1rem; +} + + #ingredientList .list-group-item { + border: none; + border-bottom: 1px dashed #ccc; + font-family: 'Segoe UI', sans-serif; + font-size: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; + } + +/* === KNAPPAR === */ + +.btn { + font-weight: bold; + border-radius: 6px; + padding: 8px 16px; + font-size: 1rem; + font-family: 'Segoe UI', sans-serif; + transition: all 0.2s ease-in-out; +} + +.btn-outline-secondary, +.btn-outline-primary, +.btn-outline-danger { + background-color: transparent; +} + +.btn-outline-secondary { + border: 2px solid #6a0dad; + color: #6a0dad; +} + + .btn-outline-secondary:hover { + background-color: #6a0dad; + color: white; + } + +.btn-primary, +.btn-success { + background-color: #b30000; + border: 2px solid #b30000; + color: white; +} + + .btn-primary:hover, + .btn-success:hover { + background-color: #cc0000; + border-color: #cc0000; + } + +.btn-success { + background-color: #218838; + border-color: #218838; +} + +.btn-danger { + background-color: #dc3545; + border-color: #dc3545; +} + + .btn-danger:hover { + background-color: #c82333; + border-color: #bd2130; + } + +/* === FORM INPUT === */ + +.input-group input, +.input-group button { + font-size: 1rem; +} + +.input-group button { + border-radius: 6px; + border: 2px solid #6a0dad; + background-color: white; + color: #6a0dad; +} + + .input-group button:hover { + background-color: #6a0dad; + color: white; + } + +/* === ANIMATION === */ + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/Aberwyn/wwwroot/service-worker.js b/Aberwyn/wwwroot/service-worker.js index 2b0b38c..42b540e 100644 --- a/Aberwyn/wwwroot/service-worker.js +++ b/Aberwyn/wwwroot/service-worker.js @@ -2,7 +2,6 @@ const urlsToCache = [ '/', '/css/site.css', - '/js/site.js', '/images/lewel-icon.png', '/manifest.json' ]; @@ -15,12 +14,22 @@ self.addEventListener('install', event => { ); }); -self.addEventListener('fetch', event => { +self.addEventListener("fetch", function (event) { + const url = new URL(event.request.url); + + // Hoppa över root / om du inte vill cachea den + if (url.pathname === "/") { + return; + } + + // Annars cacha som vanligt event.respondWith( - caches.match(event.request) - .then(response => response || fetch(event.request)) + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }) ); }); + self.addEventListener('push', function (event) { console.log("📹 Push event mottagen!", event);