From 76656bb6a89789409385afe51c144f9c8422e2e2 Mon Sep 17 00:00:00 2001 From: elias Date: Sun, 15 Jun 2025 10:49:04 +0200 Subject: [PATCH] Lots of fixes --- Aberwyn/Controllers/ErrorController.cs | 23 + Aberwyn/Controllers/FoodMenuController.cs | 21 +- Aberwyn/Controllers/HomeController.cs | 2 +- Aberwyn/Controllers/MealController.cs | 35 +- Aberwyn/Data/ApplicationDbContext.cs | 14 + Aberwyn/Data/MenuService.cs | 65 +- ...4211356_AddMealCategorySupport.Designer.cs | 821 ++++++++++++++++++ .../20250614211356_AddMealCategorySupport.cs | 19 + .../ApplicationDbContextModelSnapshot.cs | 55 +- Aberwyn/Models/MenuViewModel.cs | 72 +- Aberwyn/Program.cs | 13 +- Aberwyn/Properties/launchSettings.json | 2 +- Aberwyn/Views/Error/Error.cshtml | 75 ++ Aberwyn/Views/FoodMenu/PizzaOrder.cshtml | 2 +- Aberwyn/Views/Home/Index.cshtml | 2 +- Aberwyn/Views/Meal/MealCategories.cshtml | 74 ++ Aberwyn/Views/Meal/View.cshtml | 35 +- Aberwyn/Views/Shared/_Layout.cshtml | 1 + Aberwyn/wwwroot/css/meal-categories.css | 110 +++ 19 files changed, 1395 insertions(+), 46 deletions(-) create mode 100644 Aberwyn/Controllers/ErrorController.cs create mode 100644 Aberwyn/Migrations/20250614211356_AddMealCategorySupport.Designer.cs create mode 100644 Aberwyn/Migrations/20250614211356_AddMealCategorySupport.cs create mode 100644 Aberwyn/Views/Error/Error.cshtml create mode 100644 Aberwyn/Views/Meal/MealCategories.cshtml create mode 100644 Aberwyn/wwwroot/css/meal-categories.css diff --git a/Aberwyn/Controllers/ErrorController.cs b/Aberwyn/Controllers/ErrorController.cs new file mode 100644 index 0000000..d17e1fb --- /dev/null +++ b/Aberwyn/Controllers/ErrorController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Mvc; + +namespace Aberwyn.Controllers +{ + public class ErrorController : Controller + { + [Route("Error/{statusCode}")] + public IActionResult HttpStatusCodeHandler(int statusCode) + { + ViewData["ErrorCode"] = statusCode; + return View("Error"); + } + + [Route("Error")] + public IActionResult Error() + { + var exceptionFeature = HttpContext.Features.Get(); + ViewData["Exception"] = exceptionFeature?.Error; + return View("Error"); + } + } +} diff --git a/Aberwyn/Controllers/FoodMenuController.cs b/Aberwyn/Controllers/FoodMenuController.cs index e76b821..0e37d8a 100644 --- a/Aberwyn/Controllers/FoodMenuController.cs +++ b/Aberwyn/Controllers/FoodMenuController.cs @@ -32,12 +32,12 @@ namespace Aberwyn.Controllers [HttpGet] public IActionResult PizzaOrder() { - var pizzas = _menuService.GetMealsByCategory("Pizza") - .Where(p => p.IsAvailable) - .ToList(); + var meals = _menuService.GetMealsByCategoryName("Pizza", onlyAvailable: true); + Console.WriteLine("Pizzas: ", meals); + var dtoList = meals.Select(m => MealListDto.FromMeal(m)).ToList(); + ViewBag.Pizzas = dtoList; - ViewBag.Pizzas = pizzas; - ViewBag.RestaurantIsOpen = GetRestaurantStatus(); + ViewBag.RestaurantIsOpen = GetRestaurantStatus(); int? lastId = HttpContext.Session.GetInt32("LastPizzaOrderId"); if (lastId.HasValue) @@ -53,6 +53,7 @@ namespace Aberwyn.Controllers } + [HttpGet] public IActionResult EditPizzaOrder(int id) { @@ -152,10 +153,14 @@ namespace Aberwyn.Controllers var allMeals = _menuService.GetMeals(); + var categories = _menuService.GetMealCategories(); + var pizzaCategory = categories.FirstOrDefault(c => c.Name.Equals("Pizza", StringComparison.OrdinalIgnoreCase)); + viewModel.AvailablePizzas = allMeals - .Where(m => m.Category == "Pizza") - .OrderBy(m => m.Name) - .ToList(); + .Where(m => m.MealCategoryId == pizzaCategory?.Id) + .OrderBy(m => m.Name) + .ToList(); + ViewBag.RestaurantIsOpen = GetRestaurantStatus(); diff --git a/Aberwyn/Controllers/HomeController.cs b/Aberwyn/Controllers/HomeController.cs index 81bdb1b..9ae735c 100644 --- a/Aberwyn/Controllers/HomeController.cs +++ b/Aberwyn/Controllers/HomeController.cs @@ -29,7 +29,7 @@ namespace Aberwyn.Controllers ViewBag.RestaurantIsOpen = isOpen; var now = DateTime.Now; - var showDate = now.Hour >= 18 ? now.Date.AddDays(1) : now.Date; + var showDate = now.Hour >= 20 ? now.Date.AddDays(1) : now.Date; var todaysMenu = _menuService.GetMenuForDate(showDate); diff --git a/Aberwyn/Controllers/MealController.cs b/Aberwyn/Controllers/MealController.cs index c677423..7ce7725 100644 --- a/Aberwyn/Controllers/MealController.cs +++ b/Aberwyn/Controllers/MealController.cs @@ -45,7 +45,10 @@ namespace Aberwyn.Controllers CreatedAt = DateTime.Now }; } - + ViewBag.Categories = _menuService.GetMealCategories() + .Where(c => c.IsActive) + .OrderBy(c => c.Name) + .ToList(); ViewData["IsEditing"] = edit; return View("View", meal); } @@ -164,5 +167,35 @@ namespace Aberwyn.Controllers //service.DeleteMeal(id); return RedirectToAction("Edit"); // eller tillbaka till lista } + + [Authorize(Roles = "Admin,Chef")] + [HttpGet("/meal/categories")] + public IActionResult Categories() + { + var categories = _menuService.GetMealCategories() + .Select(cat => { + cat.MealCount = _menuService.GetMealCountForCategory(cat.Id); + return cat; + }).ToList(); + + return View("MealCategories", categories); + } + + [Authorize(Roles = "Admin,Chef")] + [HttpPost("/meal/categories/save")] + public IActionResult SaveCategory(MealCategory category) + { + _menuService.SaveOrUpdateCategory(category); + return RedirectToAction("Categories"); + } + + [Authorize(Roles = "Admin,Chef")] + [HttpPost("/meal/categories/delete")] + public IActionResult DeleteCategory(int id) + { + _menuService.DeleteCategory(id); + return RedirectToAction("Categories"); + } + } } diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs index 5599d1c..f072826 100644 --- a/Aberwyn/Data/ApplicationDbContext.cs +++ b/Aberwyn/Data/ApplicationDbContext.cs @@ -16,7 +16,20 @@ namespace Aberwyn.Data base.OnModelCreating(builder); builder.Entity().ToTable("WeeklyMenu"); + builder.Entity().HasData( + new MealCategory + { + Id = 1, + Name = "Pizza", + Slug = "pizza", + Icon = "🍕", + Color = "#f97316", + IsActive = true, + DisplayOrder = 1 + } + ); } + public DbSet BudgetPeriods { get; set; } @@ -33,6 +46,7 @@ namespace Aberwyn.Data public DbSet Ingredients { get; set; } public DbSet UserPreferences { get; set; } public DbSet PushSubscriptions { get; set; } + public DbSet MealCategories { get; set; } } } diff --git a/Aberwyn/Data/MenuService.cs b/Aberwyn/Data/MenuService.cs index 3c91cfc..2b0c856 100644 --- a/Aberwyn/Data/MenuService.cs +++ b/Aberwyn/Data/MenuService.cs @@ -292,7 +292,7 @@ public List GetMealsSlim(bool includeThumbnail = false) public List GetMealsByCategory(string category) { return _context.Meals - .Where(m => m.Category == category) + //.Where(m => m.Category == category) .Include(m => m.Ingredients) .OrderBy(m => m.Name) .ToList(); @@ -383,6 +383,27 @@ public List GetWeeklyMenu(int weekNumber, int year) return menu; } + public List GetMealsByCategoryName(string categoryName, string? searchTerm = null, bool onlyAvailable = false) + { + var query = _context.Meals + .Include(m => m.Category) + .Include(m => m.Ingredients) + .Where(m => m.Category != null && m.Category.Name == categoryName); + + if (onlyAvailable) + query = query.Where(m => m.IsAvailable); + + if (!string.IsNullOrWhiteSpace(searchTerm)) + { + string lowered = searchTerm.Trim().ToLower(); + query = query.Where(m => m.Name.ToLower().Contains(lowered)); + } + + return query + .OrderBy(m => m.Name) + .ToList(); + } + public List GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate) { @@ -414,5 +435,47 @@ public List GetWeeklyMenu(int weekNumber, int year) } return results; } + public List GetMealCategories() + { + return _context.MealCategories.OrderBy(c => c.DisplayOrder).ToList(); + } + public int GetMealCountForCategory(int categoryId) + { + return _context.Meals.Count(m => m.MealCategoryId == categoryId); + } + + public void SaveOrUpdateCategory(MealCategory cat) + { + if (cat.Id == 0) + { + _context.MealCategories.Add(cat); + } + else + { + var existing = _context.MealCategories.Find(cat.Id); + if (existing != null) + { + existing.Name = cat.Name; + existing.Slug = cat.Slug; + existing.Description = cat.Description; + existing.Icon = cat.Icon; + existing.Color = cat.Color; + existing.IsActive = cat.IsActive; + existing.DisplayOrder = cat.DisplayOrder; + } + } + _context.SaveChanges(); + } + + public void DeleteCategory(int id) + { + var cat = _context.MealCategories.Find(id); + if (cat != null) + { + _context.MealCategories.Remove(cat); + _context.SaveChanges(); + } + } + } } diff --git a/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.Designer.cs b/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.Designer.cs new file mode 100644 index 0000000..aae57e5 --- /dev/null +++ b/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.Designer.cs @@ -0,0 +1,821 @@ +ï»ż// +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("20250614211356_AddMealCategorySupport")] + partial class AddMealCategorySupport + { + 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.AppSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("AppSettings"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetCategoryDefinitionId") + .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("BudgetCategoryDefinitionId"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetCategoryDefinitions"); + }); + + 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("BudgetItemDefinitionId") + .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.HasIndex("BudgetItemDefinitionId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultCategory") + .HasColumnType("longtext"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetItemDefinitions"); + }); + + 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.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CarbType") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ImageData") + .HasColumnType("longblob"); + + b.Property("ImageMimeType") + .HasColumnType("longtext"); + + b.Property("ImageUrl") + .HasColumnType("longtext"); + + b.Property("Instructions") + .HasColumnType("longtext"); + + b.Property("IsAvailable") + .HasColumnType("tinyint(1)"); + + b.Property("MealCategoryId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProteinType") + .HasColumnType("longtext"); + + b.Property("RecipeUrl") + .HasColumnType("longtext"); + + b.Property("ThumbnailData") + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.HasIndex("MealCategoryId"); + + b.ToTable("Meals"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("Icon") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Slug") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("MealCategories"); + }); + + 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.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", 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.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("Aberwyn.Models.TodoTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedTo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TodoTasks"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("NotifyBudget") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyMenu") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyPizza") + .HasColumnType("tinyint(1)"); + + b.HasKey("UserId"); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BreakfastMealId") + .HasColumnType("int"); + + b.Property("Cook") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("DinnerMealId") + .HasColumnType("int"); + + b.Property("LunchMealId") + .HasColumnType("int"); + + b.Property("WeekNumber") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("WeeklyMenu", (string)null); + }); + + 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.BudgetCategoryDefinition", "Definition") + .WithMany() + .HasForeignKey("BudgetCategoryDefinitionId"); + + b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod") + .WithMany("Categories") + .HasForeignKey("BudgetPeriodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetPeriod"); + + b.Navigation("Definition"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory") + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition") + .WithMany() + .HasForeignKey("BudgetItemDefinitionId"); + + b.Navigation("BudgetCategory"); + + b.Navigation("BudgetItemDefinition"); + }); + + modelBuilder.Entity("Aberwyn.Models.Ingredient", b => + { + b.HasOne("Aberwyn.Models.Meal", null) + .WithMany("Ingredients") + .HasForeignKey("MealId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.HasOne("Aberwyn.Models.MealCategory", "Category") + .WithMany("Meals") + .HasForeignKey("MealCategoryId"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithOne("Preferences") + .HasForeignKey("Aberwyn.Models.UserPreferences", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + 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.ApplicationUser", b => + { + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Navigation("Categories"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Navigation("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Navigation("Meals"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.cs b/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.cs new file mode 100644 index 0000000..0a41d0e --- /dev/null +++ b/Aberwyn/Migrations/20250614211356_AddMealCategorySupport.cs @@ -0,0 +1,19 @@ +ï»żusing Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddMealCategorySupport : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs index d71eed8..1cc00ef 100644 --- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs @@ -267,9 +267,6 @@ namespace Aberwyn.Migrations b.Property("CarbType") .HasColumnType("longtext"); - b.Property("Category") - .HasColumnType("longtext"); - b.Property("CreatedAt") .HasColumnType("datetime(6)"); @@ -291,6 +288,9 @@ namespace Aberwyn.Migrations b.Property("IsAvailable") .HasColumnType("tinyint(1)"); + b.Property("MealCategoryId") + .HasColumnType("int"); + b.Property("Name") .IsRequired() .HasColumnType("longtext"); @@ -306,9 +306,44 @@ namespace Aberwyn.Migrations b.HasKey("Id"); + b.HasIndex("MealCategoryId"); + b.ToTable("Meals"); }); + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("Icon") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Slug") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("MealCategories"); + }); + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => { b.Property("Id") @@ -660,6 +695,15 @@ namespace Aberwyn.Migrations .IsRequired(); }); + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.HasOne("Aberwyn.Models.MealCategory", "Category") + .WithMany("Meals") + .HasForeignKey("MealCategoryId"); + + b.Navigation("Category"); + }); + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => { b.HasOne("Aberwyn.Models.ApplicationUser", "User") @@ -764,6 +808,11 @@ namespace Aberwyn.Migrations { b.Navigation("Ingredients"); }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Navigation("Meals"); + }); #pragma warning restore 612, 618 } } diff --git a/Aberwyn/Models/MenuViewModel.cs b/Aberwyn/Models/MenuViewModel.cs index 34f452e..a6fa81f 100644 --- a/Aberwyn/Models/MenuViewModel.cs +++ b/Aberwyn/Models/MenuViewModel.cs @@ -13,8 +13,8 @@ namespace Aberwyn.Models public int WeekNumber { get; set; } // Week number for the menu public int Year { get; set; } // Year for the menu } - public class WeeklyMenuDto -{ + public class WeeklyMenuDto + { public int Id { get; set; } public int DayOfWeek { get; set; } @@ -73,7 +73,10 @@ public class WeeklyMenu public string? Description { get; set; } public string? ProteinType { get; set; } - public string? Category { get; set; } + public int? MealCategoryId { get; set; } + + [ForeignKey("MealCategoryId")] + public MealCategory? Category { get; set; } public string? CarbType { get; set; } public string? RecipeUrl { get; set; } public string? ImageUrl { get; set; } @@ -122,24 +125,57 @@ public class WeeklyMenu } public class MealListDto - { - public int Id { get; set; } - public string Name { get; set; } = ""; - public string? Description { get; set; } - public string? ThumbnailData { get; set; } +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public string? Description { get; set; } + public string? ThumbnailData { get; set; } - public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false) + public List Ingredients { get; set; } = new(); + + public static MealListDto FromMeal(Meal meal, bool includeThumbnail = false) + { + return new MealListDto { - return new MealListDto + Id = meal.Id, + Name = meal.Name, + Description = meal.Description, + ThumbnailData = includeThumbnail && meal.ThumbnailData != null + ? Convert.ToBase64String(meal.ThumbnailData) + : null, + Ingredients = meal.Ingredients?.Select(i => new IngredientDto { - Id = meal.Id, - Name = meal.Name, - Description = meal.Description, - ThumbnailData = includeThumbnail && meal.ThumbnailData != null - ? Convert.ToBase64String(meal.ThumbnailData) - : null - }; - } + Item = i.Item + }).ToList() ?? new List() + }; + } + + public class IngredientDto + { + public string Item { get; set; } = ""; } } + public class MealCategory + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + + [Required] + public string Name { get; set; } + + public string? Slug { get; set; } + public string? Description { get; set; } + public string? Icon { get; set; } + public string? Color { get; set; } + public bool IsActive { get; set; } = true; + public int DisplayOrder { get; set; } = 0; + [NotMapped] // krĂ€vs om du inte har kolumnen i databasen + public int MealCount { get; set; } + + public List Meals { get; set; } = new(); + } + +} + diff --git a/Aberwyn/Program.cs b/Aberwyn/Program.cs index 876091a..c95c694 100644 --- a/Aberwyn/Program.cs +++ b/Aberwyn/Program.cs @@ -191,14 +191,6 @@ app.Use(async (context, next) => -// Configure the HTTP request pipeline -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Home/Error"); - app.UseHsts(); -} - - app.UseRouting(); app.UseSession(); app.UseAuthentication(); @@ -211,6 +203,11 @@ app.MapControllerRoute( pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapRazorPages(); + app.UseExceptionHandler("/Error"); + app.UseStatusCodePagesWithReExecute("/Error/{0}"); + app.UseHsts(); + + // Init: migrera databas och skapa admin if (setup.IsConfigured) { diff --git a/Aberwyn/Properties/launchSettings.json b/Aberwyn/Properties/launchSettings.json index 2ef5bcb..a663ecc 100644 --- a/Aberwyn/Properties/launchSettings.json +++ b/Aberwyn/Properties/launchSettings.json @@ -14,7 +14,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:7290;http://localhost:5290;http://0.0.0.0:5000" + "applicationUrl": "https://localhost:7290;http://localhost:5290" }, "IIS Express": { "commandName": "IISExpress", diff --git a/Aberwyn/Views/Error/Error.cshtml b/Aberwyn/Views/Error/Error.cshtml new file mode 100644 index 0000000..27b626d --- /dev/null +++ b/Aberwyn/Views/Error/Error.cshtml @@ -0,0 +1,75 @@ +@{ + Layout = "_Layout"; + var errorCode = ViewData["ErrorCode"] as int?; + var exception = ViewData["Exception"] as Exception; + var isDebug = System.Diagnostics.Debugger.IsAttached; +} + + + +
+

🚹 NĂ„got gick fel

+ Till startsidan + + @if (errorCode.HasValue) + { +
Statuskod: @errorCode
+ } + + @if (exception != null) + { +

Felmeddelande:

+
@exception.Message
+ + @if (isDebug) + { +
+ Visa stacktrace +
@exception.StackTrace
+
+ } + } + else + { +

Vi kunde tyvÀrr inte visa sidan. Det kan bero pÄ ett tillfÀlligt fel.

+ } +
diff --git a/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml b/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml index 201a119..3c5186b 100644 --- a/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml +++ b/Aberwyn/Views/FoodMenu/PizzaOrder.cshtml @@ -2,7 +2,7 @@ @using Newtonsoft.Json.Serialization @{ ViewData["Title"] = "BestÀll pizza"; - var pizzas = ViewBag.Pizzas as List; + var pizzas = ViewBag.Pizzas as List ?? new List(); var previousOrder = ViewBag.PreviousOrder as PizzaOrder; var lastId = Context.Session.GetInt32("LastPizzaOrderId"); var isCurrentOrder = previousOrder?.Id == lastId; diff --git a/Aberwyn/Views/Home/Index.cshtml b/Aberwyn/Views/Home/Index.cshtml index be36cc8..f7e1dd9 100644 --- a/Aberwyn/Views/Home/Index.cshtml +++ b/Aberwyn/Views/Home/Index.cshtml @@ -13,7 +13,7 @@ W E L -

Idag Ă€r det @day – dagens meny Ă€r:

+

Meny för @day Àr:

@if (Model != null) { diff --git a/Aberwyn/Views/Meal/MealCategories.cshtml b/Aberwyn/Views/Meal/MealCategories.cshtml new file mode 100644 index 0000000..6c81a61 --- /dev/null +++ b/Aberwyn/Views/Meal/MealCategories.cshtml @@ -0,0 +1,74 @@ +@model List +@{ + ViewData["Title"] = "MÄltidskategorier"; +} + +

MÄltidskategorier

+ +
+ @foreach (var cat in Model) + { +
+ + +
+
+ @if (!string.IsNullOrWhiteSpace(cat.Icon)) + { + @cat.Icon + } + else + { + đŸœïž + } +
+ +
+ + + + + + + + @cat.MealCount st + +
+ + + + + +
+ + } + + +
+
+
➕
+ +
+ + + + + + + + + +
+ +
+
+
+ + + diff --git a/Aberwyn/Views/Meal/View.cshtml b/Aberwyn/Views/Meal/View.cshtml index 8ae2584..1faf3e1 100644 --- a/Aberwyn/Views/Meal/View.cshtml +++ b/Aberwyn/Views/Meal/View.cshtml @@ -14,7 +14,7 @@ } else { - imageSrc = "/images/placeholder-meal.jpg"; + imageSrc = "/images/fallback.jpg"; } bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef"); @@ -64,11 +64,16 @@ +
- - + + +
+
@@ -171,6 +176,30 @@