Pizzaorder
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-05-24 16:24:17 +02:00
parent 871cba12c8
commit 9e3b7a079e
18 changed files with 1815 additions and 8 deletions

View File

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

View File

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

View File

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

View File

@@ -15,5 +15,7 @@ namespace Aberwyn.Data
public DbSet<BudgetCategory> BudgetCategories { get; set; }
public DbSet<BudgetItem> BudgetItems { get; set; }
public DbSet<PushSubscriber> PushSubscribers { get; set; }
public DbSet<PizzaOrder> PizzaOrders { get; set; }
}
}

View File

@@ -471,6 +471,61 @@ namespace Aberwyn.Data
SaveIngredients(meal.Id, meal.Ingredients);
}
}
public List<Meal> GetMealsByCategory(string category)
{
var meals = new List<Meal>();
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<int, Meal> 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<Ingredient>()
};
}
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<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
{
var results = new List<WeeklyMenu>();

View File

@@ -0,0 +1,403 @@
// <auto-generated />
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<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View File

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

View File

@@ -0,0 +1,432 @@
// <auto-generated />
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<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View File

@@ -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<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
CustomerName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PizzaName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
IngredientsJson = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
OrderedAt = table.Column<DateTime>(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");
}
}
}

View File

@@ -162,6 +162,35 @@ namespace Aberwyn.Migrations
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")

View File

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

View File

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

View File

@@ -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<PushNotificationService>(sp =>
});
builder.Services.Configure<VapidOptions>(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<RequestLocalizationOptions>(options =>
{
@@ -99,6 +107,7 @@ if (!app.Environment.IsDevelopment())
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();;
app.UseAuthorization();

View File

@@ -0,0 +1,145 @@
@using Newtonsoft.Json
@using Newtonsoft.Json.Serialization
@{
ViewData["Title"] = "Pizza Admin";
var activeOrders = ViewBag.ActiveOrders as List<PizzaOrder> ?? new List<PizzaOrder>();
var completedOrders = ViewBag.CompletedOrders as List<PizzaOrder> ?? new List<PizzaOrder>();
}
<style>
.card {
border-radius: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
transition: transform 0.2s ease-in-out;
background-color: #f8f9fa;
flex: 1 1 260px;
min-width: 240px;
max-width: 280px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0;
overflow: hidden;
}
.card-header {
font-weight: bold;
padding: 10px 12px;
color: white;
}
.card-body {
padding: 12px;
flex-grow: 1;
background-color: #ffffff;
}
.border-info .card-header {
background-color: #0dcaf0;
}
.border-warning .card-header {
background-color: #ffc107;
color: #333;
}
.border-success .card-header {
background-color: #28a745;
}
.card .btn {
font-weight: 600;
flex: 1 1 auto;
}
.card ul {
margin-bottom: 0;
padding-left: 1.2rem;
}
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: flex-start;
}
@@media only screen and (max-width: 600px) {
.card {
width: 100% !important;
}
}
</style>
<h2 class="mt-4">Aktiva beställningar</h2>
<div class="card-grid">
@foreach (var order in activeOrders)
{
var ingredients = new List<string>();
try
{
if (!string.IsNullOrEmpty(order.IngredientsJson))
{
ingredients = System.Text.Json.JsonSerializer.Deserialize<List<string>>(order.IngredientsJson);
}
}
catch
{
ingredients = order.IngredientsJson.Split('\n').ToList();
}
var cardClass = order.Status switch
{
"Bekräftad" => "border-warning",
"Klar" => "border-success",
_ => "border-info"
};
<div class="card @cardClass">
<div class="card-header">@order.CustomerName</div>
<div class="card-body">
<div class="fw-bold mb-1">@order.PizzaName</div>
<ul class="mb-2">
@foreach (var ing in ingredients)
{
<li>@ing</li>
}
</ul>
<form id="form-@order.Id" method="post" asp-action="UpdatePizzaOrder">
<input type="hidden" name="id" value="@order.Id" />
<input type="hidden" name="ingredientsJson" value='@System.Text.Json.JsonSerializer.Serialize(ingredients)' />
<div class="d-flex gap-2">
@if (order.Status == "Ej bekräftad")
{
<input type="hidden" name="status" value="Bekräftad" />
<button type="submit" class="btn btn-sm btn-outline-primary">Bekräfta</button>
}
else if (order.Status == "Bekräftad")
{
<input type="hidden" name="status" value="Klar" />
<button type="submit" class="btn btn-sm btn-outline-success">✔ Klar</button>
}
<button type="submit" formaction="@Url.Action("UpdatePizzaOrder", new { status = "Neka", id = order.Id })" class="btn btn-sm btn-outline-danger">Neka</button>
</div>
</form>
</div>
</div>
}
</div>
<h2 class="mt-5">Färdiga pizzor</h2>
@if (completedOrders.Any())
{
<ul class="list-group">
@foreach (var order in completedOrders)
{
<li class="list-group-item">
@order.CustomerName @order.PizzaName
</li>
}
</ul>
}
else
{
<div class="text-muted">Inga pizzor är klara ännu.</div>
}

View File

@@ -0,0 +1,226 @@
@using Newtonsoft.Json
@using Newtonsoft.Json.Serialization
@{
ViewData["Title"] = "Beställ pizza";
var pizzas = ViewBag.Pizzas as List<Meal>;
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);
}
<link rel="stylesheet" href="~/css/pizza.css" />
<div class="pizza-container">
@if (TempData["Success"] != null)
{
<div class="alert alert-success text-center mb-4">
🍕 @TempData["Success"]
</div>
}
@if (previousOrder != null)
{
var ingredienser = string.IsNullOrEmpty(previousOrder.IngredientsJson)
? new List<string>()
: System.Text.Json.JsonSerializer.Deserialize<List<string>>(previousOrder.IngredientsJson);
<div class="alert alert-info mb-4">
<strong>🍕 Du har redan beställt:</strong><br />
<strong>@previousOrder.PizzaName</strong>
@if (ingredienser.Count > 0)
{
<span>(med @string.Join(", ", ingredienser))</span>
}
<div class="mt-2">
<span class="badge bg-secondary">Status: @previousOrder.Status</span>
</div>
<form method="get" asp-action="EditPizzaOrder" asp-route-id="@previousOrder.Id" class="d-inline mt-2">
<button class="btn btn-sm btn-outline-primary me-2">✏️ Uppdatera min beställning</button>
</form>
<form method="post" asp-action="ClearPizzaSession" class="d-inline">
<button class="btn btn-sm btn-outline-secondary"> Beställ ny pizza</button>
</form>
</div>
}
@if (showForm)
{
<!-- Steg 1: Namn -->
<div id="step1" class="pizza-step active">
<h2>Hej! Vad heter du? 😊</h2>
<input type="text" id="customerName" placeholder="Ditt namn" class="form-control" />
<button onclick="goToStep(2)" class="btn btn-primary mt-3">Fortsätt</button>
</div>
<!-- Steg 2: Välj pizza -->
<div id="step2" class="pizza-step">
<h2>🍕 Välj en pizza</h2>
<div class="pizza-list">
@foreach (var pizza in pizzas)
{
<div class="pizza-card" onclick="selectPizza(@pizza.Id)">
<h3>@pizza.Name</h3>
<p>@string.Join(", ", pizza.Ingredients?.Select(i => i.Item) ?? new List<string>())</p>
</div>
}
<div class="pizza-card custom" onclick="selectPizza(null)">
<h3>🧑‍🍳 Skapa egen pizza</h3>
<p>Lägg till dina egna ingredienser</p>
</div>
</div>
</div>
<!-- Steg 3: Redigera ingredienser -->
<div id="step3" class="pizza-step">
<h2 id="editTitle">Redigera pizza</h2>
<ul id="ingredientList" class="list-group mb-3"></ul>
<div class="input-group mb-3">
<input type="text" id="newIngredient" class="form-control" placeholder="Lägg till ingrediens" />
<button type="button" class="btn btn-outline-secondary" onclick="addIngredient()"></button>
</div>
<button class="btn btn-secondary" onclick="goToStep(2)">⬅ Tillbaka</button>
<button class="btn btn-primary" onclick="goToStep(4)">Fortsätt</button>
</div>
<!-- Steg 4: Summering -->
<div id="step4" class="pizza-step">
<h2>Bekräfta din beställning</h2>
<p>Din pizza:</p>
<p><strong id="summaryPizza"></strong></p>
<ul id="summaryIngredients" class="list-group mb-3"></ul>
<form method="post">
<input type="hidden" name="customerName" id="formName" />
<input type="hidden" name="pizzaName" id="formPizza" />
<input type="hidden" name="ingredients" id="formIngredients" />
<button type="submit" class="btn btn-success">✅ Skicka beställning</button>
</form>
</div>
}
</div>
@section Scripts {
<script>
@if (ViewBag.PreviousOrder is PizzaOrder p)
{
<text>
window.addEventListener("DOMContentLoaded", () => {
const forceEdit = @((TempData["ForceShowForm"]?.ToString() == "true").ToString().ToLower());
const customerField = document.getElementById("customerName");
if (customerField) customerField.value = "@p.CustomerName";
const selected = allMeals.find(m => m.name === "@p.PizzaName") || { name: "Egen pizza", ingredients: [] };
if (forceEdit && selected) {
selectedPizza = selected;
selectedIngredients = @Html.Raw(p.IngredientsJson ?? "[]");
document.getElementById("editTitle").innerText = `Redigera: ${selected.name}`;
renderIngredientList();
selectedName = "@p.CustomerName";
goToStep(3);
}
});
</text>
}
const allMeals = @Html.Raw(JsonConvert.SerializeObject(
pizzas,
new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
}));
let selectedPizza = null;
let selectedIngredients = [];
let selectedName = "";
function goToStep(step) {
if (step === 2) {
const nameInput = document.getElementById("customerName").value.trim();
if (!nameInput) {
alert("Skriv ditt namn!");
return;
}
selectedName = nameInput;
}
if (step === 4) {
const pizzaName = selectedPizza?.name ?? "Egen pizza";
const originalIngredients = selectedPizza?.ingredients?.map(i => i.item) ?? [];
const current = selectedIngredients;
const removed = [];
const added = [];
let details = [];
if (removed.length > 0) details.push("utan " + removed.join(", "));
if (added.length > 0) details.push("extra " + added.join(", "));
const fullName = details.length > 0
? `${pizzaName} (${details.join(", ")})`
: pizzaName;
document.getElementById("summaryPizza").innerText = fullName;
document.getElementById("formName").value = selectedName;
document.getElementById("formPizza").value = fullName;
document.getElementById("formIngredients").value = JSON.stringify(selectedIngredients);
const list = document.getElementById("summaryIngredients");
list.innerHTML = '';
selectedIngredients.forEach(ing => {
const li = document.createElement("li");
li.className = "list-group-item";
li.innerText = ing;
list.appendChild(li);
});
}
document.querySelectorAll(".pizza-step").forEach(s => s.classList.remove("active"));
document.getElementById(`step${step}`)?.classList.add("active");
}
function selectPizza(pizzaId) {
pizzaId = parseInt(pizzaId);
selectedPizza = allMeals.find(m => m.id === pizzaId);
if (!selectedPizza) {
selectedPizza = { name: "Egen pizza", ingredients: [] };
}
selectedIngredients = [...(selectedPizza.ingredients?.map(i => i.item) ?? [])];
document.getElementById("editTitle").innerText = `Redigera: ${selectedPizza.name}`;
renderIngredientList();
goToStep(3);
}
function renderIngredientList() {
const list = document.getElementById("ingredientList");
list.innerHTML = '';
selectedIngredients.forEach((ing, i) => {
const li = document.createElement("li");
li.className = "list-group-item d-flex justify-content-between";
li.innerHTML = `${ing} <button class="btn btn-sm btn-danger" onclick="removeIngredient(${i})">🗑</button>`;
list.appendChild(li);
});
}
function addIngredient() {
const input = document.getElementById("newIngredient");
const value = input.value.trim();
if (value) {
selectedIngredients.push(value);
input.value = '';
renderIngredientList();
}
}
function removeIngredient(index) {
selectedIngredients.splice(index, 1);
renderIngredientList();
}
document.getElementById("customerName")?.addEventListener("keydown", function (e) {
if (e.key === "Enter") goToStep(2);
});
</script>
}

View File

@@ -0,0 +1,21 @@
@{
ViewData["Title"] = "Logga in";
var returnUrl = ViewBag.ReturnUrl as string ?? "/";
}
<h2>Logga in</h2>
<form method="post" asp-area="Identity" asp-page="/Account/Login">
<input type="hidden" name="returnUrl" value="@returnUrl" />
<div class="mb-3">
<label for="username" class="form-label">Användarnamn</label>
<input type="text" class="form-control" id="username" name="username" required />
</div>
<div class="mb-3">
<label for="password" class="form-label">Lösenord</label>
<input type="password" class="form-control" id="password" name="password" required />
</div>
<button type="submit" class="btn btn-primary">Logga in</button>
</form>

View File

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

View File

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