Menu stuff

This commit is contained in:
Elias Jansson
2025-04-29 21:20:06 +02:00
parent 4e1eb05141
commit 3cdf630af2
24 changed files with 880 additions and 230 deletions

View File

@@ -3,12 +3,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseAppHost>false</UseAppHost>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>d748f28f-7908-4174-b2a1-c21d4d06499f</UserSecretsId> <UserSecretsId>d748f28f-7908-4174-b2a1-c21d4d06499f</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="NewFolder\**" /> <Compile Remove="NewFolder\**" />
<Content Remove="NewFolder\**" /> <Content Remove="NewFolder\**" />
@@ -29,4 +29,9 @@
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" /> <PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Views\NewFolder\" />
<Folder Include="wwwroot\uploads\" />
</ItemGroup>
</Project> </Project>

Binary file not shown.

View File

@@ -0,0 +1,143 @@
using Microsoft.AspNetCore.Mvc;
using Aberwyn.Models;
using Aberwyn.Data;
using System.Globalization;
namespace Aberwyn.Controllers
{
public class FoodMenuController : Controller
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _env;
public FoodMenuController(IConfiguration configuration, IHostEnvironment env)
{
_configuration = configuration;
_env = env;
}
public IActionResult Veckomeny(int week = -1)
{
var menuService = new MenuService(_configuration, _env);
int currentWeek = week == -1 ? ISOWeek.GetWeekOfYear(DateTime.Now) : week;
int currentYear = DateTime.Now.Year;
// Hämta meny från DB
var menus = menuService.GetWeeklyMenu(currentWeek, currentYear);
var model = new WeeklyMenuViewModel
{
WeekNumber = currentWeek,
Year = currentYear,
WeeklyMenus = menus,
AvailableCooks = menuService.GetUsers()
};
return View(model);
}
public IActionResult MealAdmin()
{
var service = new MenuService(_configuration, _env);
var meals = service.GetMealsDetailed(); // Se till att denna metod finns och returnerar fullständiga Meal-objekt
return View("MealAdmin", meals);
}
[HttpGet]
public JsonResult SearchMeals(string term)
{
if (string.IsNullOrWhiteSpace(term))
return Json(new List<string>());
var menuService = new MenuService(_configuration, _env);
var meals = menuService.GetMeals();
var result = meals
.Where(m => m.Name != null && m.Name.Contains(term, StringComparison.OrdinalIgnoreCase))
.Select(m => m.Name)
.Distinct()
.Take(10)
.ToList();
return Json(result);
}
[HttpPost]
public IActionResult SaveVeckomeny(IFormCollection form)
{
var menuService = new MenuService(_configuration, _env);
var allMeals = menuService.GetMeals();
var model = new MenuViewModel
{
WeekNumber = ISOWeek.GetWeekOfYear(DateTime.Now),
Year = DateTime.Now.Year,
WeeklyMenus = new List<WeeklyMenu>()
};
var entriesByDay = new Dictionary<int, WeeklyMenu>();
foreach (var key in form.Keys.Where(k => k.StartsWith("Meal[", StringComparison.OrdinalIgnoreCase)))
{
var match = System.Text.RegularExpressions.Regex.Match(key, @"Meal\[(\d+)\]\[(\w+)\]");
if (match.Success)
{
int day = int.Parse(match.Groups[1].Value);
string mealType = match.Groups[2].Value;
string mealName = form[key];
string cook = form[$"Cook[{day}][{mealType}]"];
if (string.IsNullOrWhiteSpace(mealName)) continue;
var meal = allMeals.FirstOrDefault(m => m.Name.Equals(mealName, StringComparison.OrdinalIgnoreCase));
if (meal == null)
{
meal = new Meal { Name = mealName };
menuService.SaveMeal(meal);
}
if (!entriesByDay.TryGetValue(day, out var entry))
{
entry = new WeeklyMenu
{
WeekNumber = model.WeekNumber,
Year = model.Year
};
entriesByDay[day] = entry;
}
entry.DayOfWeek = day + 1; // se till att alltid sätta rätt dag
entry.Cook = cook;
if (mealType.Equals("Lunch", StringComparison.OrdinalIgnoreCase))
{
entry.LunchMealId = meal.Id;
entry.LunchMealName = meal.Name;
}
else if (mealType.Equals("Middag", StringComparison.OrdinalIgnoreCase))
{
entry.DinnerMealId = meal.Id;
entry.DinnerMealName = meal.Name;
}
else if (mealType.Equals("Frukost", StringComparison.OrdinalIgnoreCase))
{
entry.BreakfastMealId = meal.Id;
entry.BreakfastMealName = meal.Name;
}
}
}
model.WeeklyMenus = entriesByDay.Values.ToList();
menuService.UpdateWeeklyMenu(model);
return RedirectToAction("Veckomeny");
}
}
}

View File

@@ -39,6 +39,8 @@ namespace Aberwyn.Controllers
public IActionResult Menu() public IActionResult Menu()
{ {
ViewData["HideSidebar"] = true;
// Get the current date and week // Get the current date and week
var currentDate = DateTime.Now; var currentDate = DateTime.Now;
var currentYear = currentDate.Year; var currentYear = currentDate.Year;

View File

@@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Mvc;
using Aberwyn.Models;
using Aberwyn.Data;
namespace Aberwyn.Controllers
{
public class MealController : Controller
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _env;
public MealController(IConfiguration configuration, IHostEnvironment env)
{
_configuration = configuration;
_env = env;
}
[HttpGet]
public IActionResult View(int id)
{
var service = new MenuService(_configuration, _env);
var meal = service.GetMealById(id);
if (meal == null)
return NotFound();
return View("View", meal);
}
[HttpGet]
public IActionResult Edit(int? id)
{
var service = new MenuService(_configuration, _env);
var meal = id.HasValue ? service.GetMealById(id.Value) : new Meal();
return View("Meal", meal);
}
[HttpPost]
public async Task<IActionResult> SaveMeal(Meal meal, IFormFile ImageFile)
{
var service = new MenuService(_configuration, _env);
if (ImageFile != null && ImageFile.Length > 0)
{
var uploadsFolder = Path.Combine(_env.ContentRootPath, "wwwroot/uploads");
Directory.CreateDirectory(uploadsFolder); // skapa om saknas
var uniqueFileName = Guid.NewGuid().ToString() + Path.GetExtension(ImageFile.FileName);
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await ImageFile.CopyToAsync(fileStream);
}
meal.ImageUrl = "/uploads/" + uniqueFileName;
}
service.SaveOrUpdateMeal(meal);
return RedirectToAction("Edit", new { id = meal.Id });
}
[HttpPost]
public IActionResult DeleteMeal(int id)
{
var service = new MenuService(_configuration, _env);
//service.DeleteMeal(id);
return RedirectToAction("Edit"); // eller tillbaka till lista
}
}
}

View File

@@ -25,6 +25,65 @@ namespace Aberwyn.Data
return new MySqlConnection(connectionString); return new MySqlConnection(connectionString);
} }
public List<User> GetUsers()
{
var users = new List<User>();
using (var connection = GetConnection())
{
connection.Open();
string query = @"SELECT userID, Username, Name FROM Nevyn.tblUsers WHERE Chef = 1";
using (var cmd = new MySqlCommand(query, connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
users.Add(new User
{
UserID = reader.GetInt32("userID"),
Username = reader.GetString("Username"),
Name = reader.GetString("Name")
});
}
}
}
return users;
}
public void SaveMeal(Meal meal)
{
if (string.IsNullOrWhiteSpace(meal?.Name))
return;
meal.Name = meal.Name.Trim();
using var connection = GetConnection();
connection.Open();
var query = @"
INSERT INTO Meals
(Name, Description, RecipeUrl, CreatedAt)
VALUES (@name, @desc, @url, CURRENT_TIMESTAMP);";
using var cmd = new MySqlCommand(query, connection);
cmd.Parameters.AddWithValue("@name", meal.Name);
cmd.Parameters.AddWithValue("@desc", meal.Description ?? "");
cmd.Parameters.AddWithValue("@url", meal.RecipeUrl ?? "");
try
{
cmd.ExecuteNonQuery();
meal.Id = (int)cmd.LastInsertedId;
}
catch (MySqlException ex) when (ex.Number == 1062)
{
Console.WriteLine($"⚠️ Måltiden '{meal.Name}' finns redan i databasen.");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fel vid sparning av måltid '{meal.Name}': {ex.Message}");
}
}
public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year) public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
{ {
@@ -33,12 +92,16 @@ namespace Aberwyn.Data
{ {
connection.Open(); connection.Open();
string query = @" string query = @"
SELECT wm.Id, wm.DayOfWeek, wm.DinnerMealId, wm.LunchMealId, wm.WeekNumber, wm.Year, SELECT wm.Id, wm.DayOfWeek, wm.DinnerMealId, wm.LunchMealId, wm.BreakfastMealId,
dm.Name AS DinnerMealName, lm.Name AS LunchMealName wm.WeekNumber, wm.Year, wm.Cook,
FROM WeeklyMenu wm dm.Name AS DinnerMealName,
LEFT JOIN Meals dm ON wm.DinnerMealId = dm.Id lm.Name AS LunchMealName,
LEFT JOIN Meals lm ON wm.LunchMealId = lm.Id bm.Name AS BreakfastMealName
WHERE wm.WeekNumber = @weekNumber AND wm.Year = @year"; FROM WeeklyMenu wm
LEFT JOIN Meals dm ON wm.DinnerMealId = dm.Id
LEFT JOIN Meals lm ON wm.LunchMealId = lm.Id
LEFT JOIN Meals bm ON wm.BreakfastMealId = bm.Id
WHERE wm.WeekNumber = @weekNumber AND wm.Year = @year";
using (var cmd = new MySqlCommand(query, connection)) using (var cmd = new MySqlCommand(query, connection))
{ {
@@ -53,13 +116,15 @@ namespace Aberwyn.Data
{ {
Id = reader.GetInt32("Id"), Id = reader.GetInt32("Id"),
DayOfWeek = reader.GetInt32("DayOfWeek"), DayOfWeek = reader.GetInt32("DayOfWeek"),
DinnerMealId = reader.IsDBNull(reader.GetOrdinal("DinnerMealId")) ? (int?)null : reader.GetInt32("DinnerMealId"), DinnerMealId = reader.IsDBNull(reader.GetOrdinal("DinnerMealId")) ? (int?)null : reader.GetInt32(reader.GetOrdinal("DinnerMealId")),
LunchMealId = reader.IsDBNull(reader.GetOrdinal("LunchMealId")) ? (int?)null : reader.GetInt32("LunchMealId"), LunchMealId = reader.IsDBNull(reader.GetOrdinal("LunchMealId")) ? (int?)null : reader.GetInt32(reader.GetOrdinal("LunchMealId")),
BreakfastMealId = reader.IsDBNull(reader.GetOrdinal("BreakfastMealId")) ? (int?)null : reader.GetInt32(reader.GetOrdinal("BreakfastMealId")),
WeekNumber = reader.GetInt32("WeekNumber"), WeekNumber = reader.GetInt32("WeekNumber"),
Year = reader.GetInt32("Year"), Year = reader.GetInt32("Year"),
DinnerMealName = reader.IsDBNull(reader.GetOrdinal("DinnerMealName")) ? null : reader.GetString("DinnerMealName"), Cook = reader.IsDBNull(reader.GetOrdinal("Cook")) ? null : reader.GetString(reader.GetOrdinal("Cook")),
LunchMealName = reader.IsDBNull(reader.GetOrdinal("LunchMealName")) ? null : reader.GetString("LunchMealName"), DinnerMealName = reader.IsDBNull(reader.GetOrdinal("DinnerMealName")) ? null : reader.GetString(reader.GetOrdinal("DinnerMealName")),
LunchMealName = reader.IsDBNull(reader.GetOrdinal("LunchMealName")) ? null : reader.GetString(reader.GetOrdinal("LunchMealName")),
BreakfastMealName = reader.IsDBNull(reader.GetOrdinal("BreakfastMealName")) ? null : reader.GetString(reader.GetOrdinal("BreakfastMealName"))
}); });
} }
} }
@@ -68,6 +133,7 @@ namespace Aberwyn.Data
return weeklyMenu; return weeklyMenu;
} }
public List<Meal> GetMeals() public List<Meal> GetMeals()
{ {
var meals = new List<Meal>(); var meals = new List<Meal>();
@@ -92,6 +158,119 @@ namespace Aberwyn.Data
} }
return meals; return meals;
} }
public List<Meal> GetMealsDetailed()
{
var meals = new List<Meal>();
using (var connection = GetConnection())
{
connection.Open();
string query = @"
SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt
FROM Meals
ORDER BY CreatedAt DESC";
using (var cmd = new MySqlCommand(query, connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
meals.Add(new Meal
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Description = reader.IsDBNull(reader.GetOrdinal("Description")) ? null : reader.GetString(reader.GetOrdinal("Description")),
ProteinType = reader.IsDBNull(reader.GetOrdinal("ProteinType")) ? null : reader.GetString(reader.GetOrdinal("ProteinType")),
CarbType = reader.IsDBNull(reader.GetOrdinal("CarbType")) ? null : reader.GetString(reader.GetOrdinal("CarbType")),
RecipeUrl = reader.IsDBNull(reader.GetOrdinal("RecipeUrl")) ? null : reader.GetString(reader.GetOrdinal("RecipeUrl")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
});
}
}
}
return meals;
}
public Meal GetMealById(int id)
{
using var connection = GetConnection();
connection.Open();
string query = @"SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt
FROM Meals WHERE Id = @id";
using var cmd = new MySqlCommand(query, connection);
cmd.Parameters.AddWithValue("@id", id);
using var reader = cmd.ExecuteReader();
if (reader.Read())
{
return new Meal
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Description = reader.IsDBNull(reader.GetOrdinal("Description")) ? null : reader.GetString(reader.GetOrdinal("Description")),
ProteinType = reader.IsDBNull(reader.GetOrdinal("ProteinType")) ? null : reader.GetString(reader.GetOrdinal("ProteinType")),
CarbType = reader.IsDBNull(reader.GetOrdinal("CarbType")) ? null : reader.GetString(reader.GetOrdinal("CarbType")),
RecipeUrl = reader.IsDBNull(reader.GetOrdinal("RecipeUrl")) ? null : reader.GetString(reader.GetOrdinal("RecipeUrl")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
};
}
return null;
}
public void DeleteMeal(int id)
{
using var connection = GetConnection();
connection.Open();
string query = "DELETE FROM Meals WHERE Id = @id";
using var cmd = new MySqlCommand(query, connection);
cmd.Parameters.AddWithValue("@id", id);
cmd.ExecuteNonQuery();
}
public void SaveOrUpdateMeal(Meal meal)
{
using var connection = GetConnection();
connection.Open();
string query;
bool isNew = meal.Id == 0;
if (isNew)
{
query = @"INSERT INTO Meals (Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt)
VALUES (@name, @desc, @protein, @carb, @url, @created)";
}
else
{
query = @"UPDATE Meals
SET Name = @name, Description = @desc, ProteinType = @protein, CarbType = @carb, RecipeUrl = @url
WHERE Id = @id";
}
using var cmd = new MySqlCommand(query, connection);
cmd.Parameters.AddWithValue("@name", meal.Name);
cmd.Parameters.AddWithValue("@desc", meal.Description ?? "");
cmd.Parameters.AddWithValue("@protein", meal.ProteinType ?? "");
cmd.Parameters.AddWithValue("@carb", meal.CarbType ?? "");
cmd.Parameters.AddWithValue("@url", meal.RecipeUrl ?? "");
if (isNew)
{
cmd.Parameters.AddWithValue("@created", meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt);
}
else
{
cmd.Parameters.AddWithValue("@id", meal.Id);
}
cmd.ExecuteNonQuery();
}
public void UpdateWeeklyMenu(MenuViewModel menuData) public void UpdateWeeklyMenu(MenuViewModel menuData)
{ {
@@ -105,33 +284,50 @@ namespace Aberwyn.Data
{ {
try try
{ {
// Loop over each WeeklyMenu in the WeeklyMenus list foreach (var menu in menuData.WeeklyMenus)
foreach (var weeklyMenu in menuData.WeeklyMenus)
{ {
int dayOfWeek = weeklyMenu.DayOfWeek; int dayOfWeek = menu.DayOfWeek;
var menu = weeklyMenu;
// SQL query to update the existing records string updateQuery = @"
string query = @"
UPDATE WeeklyMenu UPDATE WeeklyMenu
SET SET
DinnerMealId = @dinnerMealId, BreakfastMealId = @breakfastMealId,
LunchMealId = @lunchMealId, LunchMealId = @lunchMealId,
DinnerMealId = @dinnerMealId,
Cook = @cook Cook = @cook
WHERE DayOfWeek = @dayOfWeek WHERE DayOfWeek = @dayOfWeek
AND WeekNumber = @weekNumber AND WeekNumber = @weekNumber
AND Year = @year;"; AND Year = @year;";
using (var cmd = new MySqlCommand(query, connection, transaction)) using (var cmd = new MySqlCommand(updateQuery, connection, transaction))
{ {
cmd.Parameters.AddWithValue("@dayOfWeek", dayOfWeek); cmd.Parameters.AddWithValue("@dayOfWeek", dayOfWeek);
cmd.Parameters.AddWithValue("@dinnerMealId", menu.DinnerMealId ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@breakfastMealId", menu.BreakfastMealId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@lunchMealId", menu.LunchMealId ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@lunchMealId", menu.LunchMealId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@dinnerMealId", menu.DinnerMealId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@weekNumber", menu.WeekNumber); cmd.Parameters.AddWithValue("@weekNumber", menu.WeekNumber);
cmd.Parameters.AddWithValue("@year", menu.Year); cmd.Parameters.AddWithValue("@year", menu.Year);
cmd.Parameters.AddWithValue("@cook", menu.Cook ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@cook", menu.Cook ?? (object)DBNull.Value);
cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
if (rowsAffected == 0)
{
string insertQuery = @"
INSERT INTO WeeklyMenu
(DayOfWeek, WeekNumber, Year, BreakfastMealId, LunchMealId, DinnerMealId, Cook)
VALUES (@dayOfWeek, @weekNumber, @year, @breakfastMealId, @lunchMealId, @dinnerMealId, @cook);";
using var insertCmd = new MySqlCommand(insertQuery, connection, transaction);
insertCmd.Parameters.AddWithValue("@dayOfWeek", dayOfWeek);
insertCmd.Parameters.AddWithValue("@breakfastMealId", menu.BreakfastMealId ?? (object)DBNull.Value);
insertCmd.Parameters.AddWithValue("@lunchMealId", menu.LunchMealId ?? (object)DBNull.Value);
insertCmd.Parameters.AddWithValue("@dinnerMealId", menu.DinnerMealId ?? (object)DBNull.Value);
insertCmd.Parameters.AddWithValue("@weekNumber", menu.WeekNumber);
insertCmd.Parameters.AddWithValue("@year", menu.Year);
insertCmd.Parameters.AddWithValue("@cook", menu.Cook ?? (object)DBNull.Value);
insertCmd.ExecuteNonQuery();
}
} }
} }
@@ -147,6 +343,7 @@ namespace Aberwyn.Data
} }
} }
public int AddMeal(Meal meal) public int AddMeal(Meal meal)
{ {
using (var connection = GetConnection()) using (var connection = GetConnection())

View File

@@ -1,40 +1,22 @@
# Base image for runtime #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80
EXPOSE 443
RUN mkdir -p /app/build && chmod -R 777 /app/build
# Leave the ports exposed to allow Unraid to configure them
EXPOSE 80 443
# Image for building the project
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src WORKDIR /src
COPY ["Aberwyn/Aberwyn.csproj", "Aberwyn/"]
# Copy the .csproj and restore dependencies RUN dotnet restore "Aberwyn/Aberwyn.csproj"
COPY Aberwyn/Aberwyn.csproj ./
RUN dotnet restore
#COPY Aberwyn/Aberwyn.csproj ./Aberwyn/
#WORKDIR /src/Aberwyn
#RUN dotnet restore
# Copy everything else and build the app
COPY . . COPY . .
RUN dotnet build Aberwyn.csproj -c Release -o /app/build/ WORKDIR "/src/Aberwyn"
RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build
# Publish the app to the /app/publish folder
FROM build AS publish FROM build AS publish
RUN dotnet publish Aberwyn.csproj -c Release -o /app/publish RUN dotnet publish "Aberwyn.csproj" -c Release -o /app/publish
# Use the base runtime image to run the app
FROM base AS final FROM base AS final
WORKDIR /app WORKDIR /app
COPY --from=publish /app/publish . COPY --from=publish /app/publish .
# Use environment variable for ports to allow flexibility
ENV ASPNETCORE_URLS="http://+:80"
# Set entry point to start the application
ENTRYPOINT ["dotnet", "Aberwyn.dll"] ENTRYPOINT ["dotnet", "Aberwyn.dll"]

View File

@@ -15,15 +15,18 @@ namespace Aberwyn.Models
{ {
public int Id { get; set; } public int Id { get; set; }
public int DayOfWeek { get; set; } public int DayOfWeek { get; set; }
public int? DinnerMealId { get; set; } public int? BreakfastMealId { get; set; } // 👈 Lägg till denna
public int? LunchMealId { get; set; } public int? LunchMealId { get; set; }
public int? DinnerMealId { get; set; }
public string Cook { get; set; } public string Cook { get; set; }
public int WeekNumber { get; set; } public int WeekNumber { get; set; }
public int Year { get; set; } public int Year { get; set; }
public string DinnerMealName { get; set; } public string BreakfastMealName { get; set; } // 👈 Och denna
public string LunchMealName { get; set; } public string LunchMealName { get; set; }
public string DinnerMealName { get; set; }
} }
public class Meal public class Meal
{ {
public int Id { get; set; } public int Id { get; set; }
@@ -32,6 +35,7 @@ namespace Aberwyn.Models
public string ProteinType { get; set; } public string ProteinType { get; set; }
public string CarbType { get; set; } public string CarbType { get; set; }
public string RecipeUrl { get; set; } public string RecipeUrl { get; set; }
public string ImageUrl { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
} }
} }

10
Aberwyn/Models/User.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace Aberwyn.Models
{
public class User
{
public int UserID { get; set; }
public string Username { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Aberwyn.Models
{
public class WeeklyMenuViewModel
{
public int WeekNumber { get; set; }
public int Year { get; set; }
public List<WeeklyMenu> WeeklyMenus { get; set; } = new();
public List<User> AvailableCooks { get; set; } = new();
public WeeklyMenu GetMealEntry(int day, string type)
{
int dayOfWeek = day + 1;
return WeeklyMenus.FirstOrDefault(m =>
m.DayOfWeek == dayOfWeek &&
(
(type == "Frukost" && m.BreakfastMealId.HasValue) ||
(type == "Lunch" && m.LunchMealId.HasValue) ||
(type == "Middag" && m.DinnerMealId.HasValue)
)
);
}
}
}

View File

@@ -1,6 +1,9 @@
using Microsoft.EntityFrameworkCore; // Add this for DbContext using Microsoft.EntityFrameworkCore; // Add this for DbContext
using Aberwyn.Data; // Include your data namespace using Aberwyn.Data; // Include your data namespace
using MySql.EntityFrameworkCore.Extensions; // For MySQL support using MySql.EntityFrameworkCore.Extensions; // For MySQL support
using System.Text;
using System.Globalization;
using Microsoft.AspNetCore.Localization;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -20,6 +23,14 @@ builder.Services.AddScoped<BudgetService>();
builder.Services.AddScoped<MenuService>(); builder.Services.AddScoped<MenuService>();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[] { new CultureInfo("sv-SE") };
options.DefaultRequestCulture = new RequestCulture("sv-SE");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.

View File

@@ -0,0 +1,45 @@
@model List<Meal>
@{
ViewData["Title"] = "Alla måltider";
}
<h1>Alla måltider</h1>
<table class="table">
<thead>
<tr>
<th>Namn</th>
<th>Beskrivning</th>
<th>Protein</th>
<th>Kolhydrat</th>
<th>Receptlänk</th>
<th>Skapad</th>
<th>Redigera</th>
</tr>
</thead>
<tbody>
@foreach (var meal in Model)
{
<tr>
<td>
<a asp-controller="Meal" asp-action="View" asp-route-id="@meal.Id">@meal.Name</a>
</td>
<td>@meal.Description</td>
<td>@meal.ProteinType</td>
<td>@meal.CarbType</td>
<td>
@if (!string.IsNullOrWhiteSpace(meal.RecipeUrl))
{
<a href="@meal.RecipeUrl" target="_blank">Länk</a>
}
</td>
<td>@meal.CreatedAt.ToString("yyyy-MM-dd")</td>
<td>
<a asp-controller="Meal" asp-action="Edit" asp-route-id="@meal.Id">Redigera</a>
</td>
</tr>
}
</tbody>
</table>
<a class="btn btn-success" asp-controller="Meal" asp-action="Edit">Lägg till ny måltid</a>

View File

@@ -0,0 +1,93 @@
@model WeeklyMenuViewModel
@{
ViewData["Title"] = "Veckomeny";
var days = new[] { "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag" };
var mealTypes = new[] { "Frukost", "Lunch", "Middag" };
}
<h1>Veckomeny - Vecka @Model.WeekNumber</h1>
<div class="week-nav">
<a asp-action="Veckomeny" asp-route-week="@(@Model.WeekNumber - 1)">Föregående vecka</a>
<a asp-action="Veckomeny" asp-route-week="@(@Model.WeekNumber + 1)">Nästa vecka</a>
</div>
<form method="post" asp-action="SaveVeckomeny">
<table class="menu-table">
<thead>
<tr>
<th>Dag</th>
@foreach (var mealType in mealTypes)
{
<th>@mealType</th>
}
</tr>
</thead>
<tbody>
@for (int i = 0; i < 7; i++)
{
<tr>
<td>@days[i]</td>
@foreach (var mealType in mealTypes)
{
var entry = Model.GetMealEntry(i, mealType);
var mealName = mealType switch
{
"Frukost" => entry?.BreakfastMealName,
"Lunch" => entry?.LunchMealName,
"Middag" => entry?.DinnerMealName,
_ => null
};
<td>
<input type="text" name="Meal[@i][@mealType]"
value="@mealName"
placeholder="Lägg till måltid..."
class="meal-input" data-day="@i" data-type="@mealType" list="meals-list" />
<select name="Cook[@i][@mealType]">
<option value="">Vem lagar?</option>
@foreach (var user in Model.AvailableCooks)
{
if (entry?.Cook == user.Username)
{
<option value="@user.Username" selected>@user.Name</option>
}
else
{
<option value="@user.Username">@user.Name</option>
}
}
</select>
</td>
}
</tr>
}
</tbody>
</table>
<button type="submit">Spara veckomeny</button>
</form>
<datalist id="meals-list"></datalist>
@section Scripts {
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function() {
$('.meal-input').on('focus input', function() {
const input = $(this);
const term = input.val();
input.attr('list', 'meals-list'); // tvinga list-id
$.getJSON('/FoodMenu/SearchMeals', { term: term }, function(data) {
const list = $('#meals-list');
list.empty();
data.forEach(name => {
list.append(`<option value="${name}">`);
});
});
});
});
function showMealInfo(name, cook) {
alert(`Måltid: ${name}\nLagas av: ${cook}`);
}
</script>
}

View File

@@ -1,127 +1,21 @@
@{ @{
ViewData["Title"] = "Home Page"; ViewData["Title"] = "Welcome to Aberwyn!";
} }
<div class="text-center"> <!DOCTYPE html>
<html lang="en">
<html>
<head> <head>
<meta charset="utf-8">
<link rel="alternate" type="application/rss+xml" title="zcz RSS" href="/rss.xml"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewData["Title"]</title>
<style type="text/css"> <link rel="stylesheet" href="~/css/site.css">
body {
scrollbar-face-color: #999999;
scrollbar-highlight-color: #999999;
scrollbar-shadow-color: #202020;
scrollbar-3dlight-color: ##666666;
scrollbar-arrow-color: #383838;
scrollbar-track-color: #202020;
scrollbar-darkshadow-color: #212121;
Background-position: 50% 0%;
background-Color: #1F2C3C;
}
.tab { border-collapse: collapse; }
.Main { background-image: url(/images/bg.gif); background-repeat: repeat-y; }
.Navi { background-image: url(/images/navi.gif); background-repeat: repeat-y; }
.footer { background-image: url(/images/footer.gif); background-repeat: repeat-y; }
</style>
</head> </head>
<body>
<body link="#6165A3" vlink="#6F76CC" alink="#66ACFF"> <div class="container">
<h1>Welcome to Aberwyn!</h1>
<table border="0" width="800" align="Center"> <p>Your home for managing all your budget and meal planning needs.</p>
<p>Were glad to have you here. Get started by exploring our features.</p>
<td> <a href="/Home/About" class="button">Learn More</a>
</div>
<table cellpadding="0" cellspacing="0" border="0" align="Center" bordercolor="#111111">
<td valign="Top" width=799>
</td>
</table>
<table border="0" height="100%" cellpadding="0" width="100%" cellspacing="1" align="Center" bordercolor="#111111" class="Main">
<td valign="Top" width=100 height="120" class=Navi>
<?php include "navi.php"; ?>
</td>
<td valign="Top" rowspan="2"><br>
<?php
if (isset($page)) {
$pagetoshow = "$page.php";
if (file_exists($pagetoshow)) {
include "$pagetoshow";
} else {
include "error.php";
}
}
if (!isset($page)) { include "main.php"; }
?>
</td>
<tr>
<td valign="Top" width=100 class=Navi>
<?php // include "login.php"; ?>
</td>
</tr>
</table>
<table border="0" height="100%" cellpadding="10" width="100%" cellspacing="1" align="Center" bordercolor="#111111" class="footer">
<img src="/images/border.gif">
<td width="100%" valign="Center">
</td>
</table>
</td>
</table>
</body> </body>
</html> </html>
</div>

View File

@@ -1,7 +1,9 @@
@model Aberwyn.Models.MenuViewModel @model Aberwyn.Models.MenuViewModel
@{ @{
Layout = "_Layout"; // Define a flag to hide the sidebar if the URL path matches '/nosidebar' (relative part after the controller)
bool hideSidebar = Context.Request.Path.Value.EndsWith("/nosidebar", StringComparison.OrdinalIgnoreCase);
Layout = hideSidebar ? null : "_Layout"; // No layout if the path ends with '/nosidebar'
} }
<!DOCTYPE html> <!DOCTYPE html>
@@ -20,14 +22,9 @@
<h1 class="page-title">Meal Menu Overview</h1> <h1 class="page-title">Meal Menu Overview</h1>
<div class="date-picker"> <div class="date-picker">
<button type="button" class="date-btn" ng-click="goToPreviousWeek()">Previous Week</button> <button type="button" class="date-btn" ng-click="goToPreviousWeek()">Föregående vecka</button>
<span class="week-info">Week {{ selectedWeek }} - {{ selectedYear }}</span> <span class="week-info">Vecka {{ selectedWeek }} - {{ selectedYear }}</span>
<button type="button" class="date-btn" ng-click="goToNextWeek()">Next Week</button> <button type="button" class="date-btn" ng-click="goToNextWeek()">Nästa vecka</button>
</div>
<div class="mode-toggle">
<button ng-click="toggleEditMode()" ng-show="!isEditing" class="mode-btn">Edit</button>
<button ng-click="toggleEditMode()" ng-show="isEditing" class="mode-btn">View</button>
</div> </div>
<div class="meal-menu-container"> <div class="meal-menu-container">
@@ -37,62 +34,21 @@
<div class="meal-info" ng-if="!isEditing"> <div class="meal-info" ng-if="!isEditing">
<div ng-if="menu[day]"> <div ng-if="menu[day]">
<div ng-if="menu[day].breakfastMealName"> <div ng-if="menu[day].breakfastMealName">
<span><strong>Breakfast:</strong> {{ menu[day].breakfastMealName }}</span> <span><strong>Frukost:</strong> {{ menu[day].breakfastMealName }}</span>
</div> </div>
<div ng-if="menu[day].lunchMealName"> <div ng-if="menu[day].lunchMealName">
<span><strong>Lunch:</strong> {{ menu[day].lunchMealName }}</span> <span><strong>Lunch:</strong> {{ menu[day].lunchMealName }}</span>
</div> </div>
<div ng-if="menu[day].dinnerMealName"> <div ng-if="menu[day].dinnerMealName">
<span><strong>Dinner:</strong> {{ menu[day].dinnerMealName }}</span> <span><strong>Middag:</strong> {{ menu[day].dinnerMealName }}</span>
</div> </div>
</div> </div>
<div ng-if="!menu[day]"> <div ng-if="!menu[day]">
<span class="not-assigned">Not Assigned</span> <span class="not-assigned">Inte bestämd</span>
</div>
</div>
<div class="meal-edit" ng-if="isEditing">
<div>
<strong>Breakfast:</strong>
<select ng-model="menu[day].breakfastMealId" ng-change="handleMealSelection(day, 'breakfast')"
ng-options="meal.id as meal.name for meal in meals">
<option value="">Select Meal</option>
<option value="new">New Meal</option>
</select>
</div>
<div>
<strong>Lunch:</strong>
<select ng-model="menu[day].lunchMealId" ng-change="handleMealSelection(day, 'lunch')"
ng-options="meal.id as meal.name for meal in meals">
<option value="">Select Meal</option>
<option value="new">New Meal</option>
</select>
</div>
<div>
<strong>Dinner:</strong>
<select ng-model="menu[day].dinnerMealId" ng-change="handleMealSelection(day, 'dinner')"
ng-options="meal.id as meal.name for meal in meals">
<option value="">Select Meal</option>
<option value="new">New Meal</option>
</select>
</div> </div>
</div> </div>
</div> </div>
<button ng-if="isEditing" ng-click="saveMenu()" class="save-btn">Save Menu</button>
</div> </div>
<div class="new-meal-form" ng-if="isEditing">
<h3>Add a New Meal</h3>
<input type="text" placeholder="Meal Name" ng-model="newMeal.name">
<input type="text" placeholder="Description" ng-model="newMeal.description">
<input type="text" placeholder="Protein Type" ng-model="newMeal.proteinType">
<input type="text" placeholder="Carb Type" ng-model="newMeal.carbType">
<input type="text" placeholder="Recipe URL" ng-model="newMeal.recipeUrl">
<button ng-click="saveNewMeal()">Save Meal</button>
<button ng-click="cancelNewMeal()">Cancel</button>
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,77 @@
@model Aberwyn.Models.Meal
@{
ViewData["Title"] = Model.Id == 0 ? "Ny måltid" : Model.Name;
var isNew = Model.Id == 0;
}
<div class="meal-details">
<h1>@(isNew ? "Skapa ny måltid" : Model.Name)</h1>
<form asp-action="SaveMeal" method="post" enctype="multipart/form-data">
@if (!isNew)
{
<input type="hidden" name="Id" value="@Model.Id" />
}
<div>
<label for="Name">Namn</label>
<input type="text" name="Name" value="@Model.Name" required />
</div>
<div>
<label for="Description">Beskrivning</label>
<textarea name="Description">@Model.Description</textarea>
</div>
<div>
<label for="ProteinType">Protein</label>
<input type="text" name="ProteinType" value="@Model.ProteinType" />
</div>
<div>
<label for="CarbType">Kolhydrat</label>
<input type="text" name="CarbType" value="@Model.CarbType" />
</div>
<div>
<label for="RecipeUrl">Receptlänk</label>
<input type="url" name="RecipeUrl" value="@Model.RecipeUrl" />
</div>
<div>
<label for="ImageFile">Bild</label>
<input type="file" name="ImageFile" />
</div>
<button type="submit">Spara</button>
</form>
</div>
<style>
.meal-details {
max-width: 600px;
margin: 2rem auto;
background: #f9f9f9;
padding: 2rem;
border-radius: 8px;
}
.meal-details label {
display: block;
font-weight: bold;
margin-top: 1rem;
}
.meal-details input[type=text],
.meal-details input[type=url],
.meal-details textarea {
width: 100%;
padding: 0.5rem;
border-radius: 4px;
border: 1px solid #ccc;
margin-top: 0.25rem;
}
.meal-details button {
margin-top: 1rem;
margin-right: 1rem;
}
</style>

View File

@@ -0,0 +1,90 @@
@model Aberwyn.Models.Meal
@{
ViewData["Title"] = Model.Name;
}
<div class="meal-container">
<h1>@Model.Name</h1>
@if (!string.IsNullOrEmpty(Model.ImageUrl))
{
<img src="@Model.ImageUrl" alt="@Model.Name" class="meal-image" />
}
@if (!string.IsNullOrEmpty(Model.Description))
{
<p class="description">@Model.Description</p>
}
<div class="buttons">
<button onclick="toggleRecipe()">Visa Recept</button>
<a class="edit-button" asp-controller="Meal" asp-action="Edit" asp-route-id="@Model.Id">Redigera</a>
</div>
<div id="recipe-section" style="display:none;">
<h2>Så här gör du</h2>
<p>(Tillagningsinstruktioner kommer snart...)</p>
@if (!string.IsNullOrEmpty(Model.RecipeUrl))
{
<p><a href="@Model.RecipeUrl" target="_blank">Öppna fullständigt recept</a></p>
}
</div>
</div>
<script>
function toggleRecipe() {
var section = document.getElementById('recipe-section');
if (section.style.display === 'none') {
section.style.display = 'block';
} else {
section.style.display = 'none';
}
}
</script>
<style>
.meal-container {
max-width: 700px;
margin: 2rem auto;
background: #fff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.meal-container h1 {
margin-bottom: 1rem;
}
.meal-container .meal-image {
width: 100%;
max-height: 400px;
object-fit: cover;
border-radius: 10px;
margin-bottom: 1rem;
}
.meal-container .description {
font-style: italic;
margin-bottom: 1.5rem;
}
.meal-container .buttons {
margin-bottom: 2rem;
}
.meal-container .buttons button, .meal-container .buttons .edit-button {
background-color: #6a0dad;
color: white;
border: none;
padding: 0.7rem 1.5rem;
border-radius: 5px;
margin: 0.5rem;
text-decoration: none;
cursor: pointer;
}
.meal-container .buttons .edit-button {
display: inline-block;
}
.meal-container a {
color: #6a0dad;
text-decoration: underline;
}
</style>

View File

@@ -37,17 +37,30 @@
<i class="fas fa-building"></i> RealEstate <i class="fas fa-building"></i> RealEstate
</a> </a>
</li> </li>
<li class="nav-section-title">Måltider</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Menu"> <a class="nav-link" asp-area="" asp-controller="Home" asp-action="Menu">
<i class="fas fa-utensils"></i> Menu <i class="fas fa-utensils"></i> Menyöversikt
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-area="" asp-controller="FoodMenu" asp-action="Veckomeny">
<i class="fas fa-calendar-week"></i> Veckomeny
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-area="" asp-controller="FoodMenu" asp-action="MealAdmin">
<i class="fas fa-list"></i> Matlista
</a> </a>
</li> </li>
</ul> </ul>
</aside> </aside>
<main role="main" class="main-content"> <main role="main" class="main-content">
@RenderBody() @RenderBody()
@RenderSection("Scripts", required: false)
</main> </main>
<!-- Right Sidebar with budget data from the RightSidebarViewComponent --> <!-- Right Sidebar with budget data from the RightSidebarViewComponent -->
@@ -57,5 +70,6 @@
</div> </div>
<script src="~/js/site.js" asp-append-version="true"></script> <script src="~/js/site.js" asp-append-version="true"></script>
</body> </body>
</html> </html>

View File

@@ -8,6 +8,6 @@
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;", "DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;",
"ProductionConnection": "Server=172.18.0.2;Database=Nevyn;Uid=root;Pwd=3edc4RFV;" "ProductionConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;"
} }
} }

View File

@@ -0,0 +1,15 @@
.menu-table {
width: 100%;
border-collapse: collapse;
margin-top: 1em;
}
.menu-table th, .menu-table td {
border: 1px solid #444;
padding: 0.5em;
}
.week-nav {
margin-bottom: 1em;
display: flex;
gap: 1em;
}

View File

@@ -224,3 +224,4 @@ body {
.date-picker-dropdown select:hover { .date-picker-dropdown select:hover {
background-color: #555; background-color: #555;
} }

View File

@@ -0,0 +1,10 @@
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".meal-cell").forEach(cell => {
cell.addEventListener("click", function () {
const name = this.getAttribute("data-name");
const cook = this.getAttribute("data-cook");
alert(`Måltid: ${name}\nLagas av: ${cook}`);
});
});
});

View File

@@ -1,4 +1,4 @@
angular.module('mealMenuApp', []) angular.module('mealMenuApp', [])
.controller('MealMenuController', function ($scope, $http) { .controller('MealMenuController', function ($scope, $http) {
$scope.isEditing = false; $scope.isEditing = false;
$scope.toggleEditMode = function () { $scope.toggleEditMode = function () {
@@ -15,7 +15,7 @@ angular.module('mealMenuApp', [])
const today = new Date(); const today = new Date();
$scope.selectedWeek = getWeek(today); $scope.selectedWeek = getWeek(today);
$scope.selectedYear = today.getFullYear(); $scope.selectedYear = today.getFullYear();
$scope.daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; $scope.daysOfWeek = ["Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "rdag", "Söndag"];
$scope.loadMeals = function () { $scope.loadMeals = function () {
$http.get('/api/mealMenuApi/getMeals') $http.get('/api/mealMenuApi/getMeals')
@@ -29,6 +29,8 @@ angular.module('mealMenuApp', [])
$http.get('/api/mealMenuApi/menu', { $http.get('/api/mealMenuApi/menu', {
params: { weekNumber: $scope.selectedWeek, year: $scope.selectedYear } params: { weekNumber: $scope.selectedWeek, year: $scope.selectedYear }
}).then(response => { }).then(response => {
console.log("Veckomenydata:", response.data); // ✅ logga ut
$scope.menu = {}; $scope.menu = {};
response.data.forEach(item => { response.data.forEach(item => {
const dayOfWeek = $scope.daysOfWeek[item.dayOfWeek - 1]; const dayOfWeek = $scope.daysOfWeek[item.dayOfWeek - 1];

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB