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>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<UseAppHost>false</UseAppHost>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>d748f28f-7908-4174-b2a1-c21d4d06499f</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Compile Remove="NewFolder\**" />
<Content Remove="NewFolder\**" />
@@ -29,4 +29,9 @@
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
</ItemGroup>
<ItemGroup>
<Folder Include="Views\NewFolder\" />
<Folder Include="wwwroot\uploads\" />
</ItemGroup>
</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()
{
ViewData["HideSidebar"] = true;
// Get the current date and week
var currentDate = DateTime.Now;
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);
}
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)
{
@@ -33,11 +92,15 @@ namespace Aberwyn.Data
{
connection.Open();
string query = @"
SELECT wm.Id, wm.DayOfWeek, wm.DinnerMealId, wm.LunchMealId, wm.WeekNumber, wm.Year,
dm.Name AS DinnerMealName, lm.Name AS LunchMealName
SELECT wm.Id, wm.DayOfWeek, wm.DinnerMealId, wm.LunchMealId, wm.BreakfastMealId,
wm.WeekNumber, wm.Year, wm.Cook,
dm.Name AS DinnerMealName,
lm.Name AS LunchMealName,
bm.Name AS BreakfastMealName
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))
@@ -53,13 +116,15 @@ namespace Aberwyn.Data
{
Id = reader.GetInt32("Id"),
DayOfWeek = reader.GetInt32("DayOfWeek"),
DinnerMealId = reader.IsDBNull(reader.GetOrdinal("DinnerMealId")) ? (int?)null : reader.GetInt32("DinnerMealId"),
LunchMealId = reader.IsDBNull(reader.GetOrdinal("LunchMealId")) ? (int?)null : reader.GetInt32("LunchMealId"),
DinnerMealId = reader.IsDBNull(reader.GetOrdinal("DinnerMealId")) ? (int?)null : reader.GetInt32(reader.GetOrdinal("DinnerMealId")),
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"),
Year = reader.GetInt32("Year"),
DinnerMealName = reader.IsDBNull(reader.GetOrdinal("DinnerMealName")) ? null : reader.GetString("DinnerMealName"),
LunchMealName = reader.IsDBNull(reader.GetOrdinal("LunchMealName")) ? null : reader.GetString("LunchMealName"),
Cook = reader.IsDBNull(reader.GetOrdinal("Cook")) ? null : reader.GetString(reader.GetOrdinal("Cook")),
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;
}
public List<Meal> GetMeals()
{
var meals = new List<Meal>();
@@ -92,6 +158,119 @@ namespace Aberwyn.Data
}
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)
{
@@ -105,33 +284,50 @@ namespace Aberwyn.Data
{
try
{
// Loop over each WeeklyMenu in the WeeklyMenus list
foreach (var weeklyMenu in menuData.WeeklyMenus)
foreach (var menu in menuData.WeeklyMenus)
{
int dayOfWeek = weeklyMenu.DayOfWeek;
var menu = weeklyMenu;
int dayOfWeek = menu.DayOfWeek;
// SQL query to update the existing records
string query = @"
string updateQuery = @"
UPDATE WeeklyMenu
SET
DinnerMealId = @dinnerMealId,
BreakfastMealId = @breakfastMealId,
LunchMealId = @lunchMealId,
DinnerMealId = @dinnerMealId,
Cook = @cook
WHERE DayOfWeek = @dayOfWeek
AND WeekNumber = @weekNumber
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("@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("@dinnerMealId", menu.DinnerMealId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@weekNumber", menu.WeekNumber);
cmd.Parameters.AddWithValue("@year", menu.Year);
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)
{
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
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
WORKDIR /src
# Copy the .csproj and restore dependencies
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 ["Aberwyn/Aberwyn.csproj", "Aberwyn/"]
RUN dotnet restore "Aberwyn/Aberwyn.csproj"
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
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
WORKDIR /app
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"]

View File

@@ -15,15 +15,18 @@ namespace Aberwyn.Models
{
public int Id { 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? DinnerMealId { get; set; }
public string Cook { get; set; }
public int WeekNumber { 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 DinnerMealName { get; set; }
}
public class Meal
{
public int Id { get; set; }
@@ -32,6 +35,7 @@ namespace Aberwyn.Models
public string ProteinType { get; set; }
public string CarbType { get; set; }
public string RecipeUrl { get; set; }
public string ImageUrl { 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 Aberwyn.Data; // Include your data namespace
using MySql.EntityFrameworkCore.Extensions; // For MySQL support
using System.Text;
using System.Globalization;
using Microsoft.AspNetCore.Localization;
var builder = WebApplication.CreateBuilder(args);
@@ -20,6 +23,14 @@ builder.Services.AddScoped<BudgetService>();
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();
// 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">
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="alternate" type="application/rss+xml" title="zcz RSS" href="/rss.xml">
<style type="text/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>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="~/css/site.css">
</head>
<body link="#6165A3" vlink="#6F76CC" alink="#66ACFF">
<table border="0" width="800" align="Center">
<td>
<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>
</html>
<body>
<div class="container">
<h1>Welcome to Aberwyn!</h1>
<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>
<a href="/Home/About" class="button">Learn More</a>
</div>
</body>
</html>

View File

@@ -1,7 +1,9 @@
@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>
@@ -20,14 +22,9 @@
<h1 class="page-title">Meal Menu Overview</h1>
<div class="date-picker">
<button type="button" class="date-btn" ng-click="goToPreviousWeek()">Previous Week</button>
<span class="week-info">Week {{ selectedWeek }} - {{ selectedYear }}</span>
<button type="button" class="date-btn" ng-click="goToNextWeek()">Next Week</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>
<button type="button" class="date-btn" ng-click="goToPreviousWeek()">Föregående vecka</button>
<span class="week-info">Vecka {{ selectedWeek }} - {{ selectedYear }}</span>
<button type="button" class="date-btn" ng-click="goToNextWeek()">Nästa vecka</button>
</div>
<div class="meal-menu-container">
@@ -37,62 +34,21 @@
<div class="meal-info" ng-if="!isEditing">
<div ng-if="menu[day]">
<div ng-if="menu[day].breakfastMealName">
<span><strong>Breakfast:</strong> {{ menu[day].breakfastMealName }}</span>
<span><strong>Frukost:</strong> {{ menu[day].breakfastMealName }}</span>
</div>
<div ng-if="menu[day].lunchMealName">
<span><strong>Lunch:</strong> {{ menu[day].lunchMealName }}</span>
</div>
<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 ng-if="!menu[day]">
<span class="not-assigned">Not Assigned</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>
<span class="not-assigned">Inte bestämd</span>
</div>
</div>
</div>
<button ng-if="isEditing" ng-click="saveMenu()" class="save-btn">Save Menu</button>
</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>
</body>
</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
</a>
</li>
<li class="nav-section-title">Måltider</li>
<li class="nav-item">
<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>
</li>
</ul>
</aside>
<main role="main" class="main-content">
@RenderBody()
@RenderSection("Scripts", required: false)
</main>
<!-- Right Sidebar with budget data from the RightSidebarViewComponent -->
@@ -57,5 +70,6 @@
</div>
<script src="~/js/site.js" asp-append-version="true"></script>
</body>
</html>

View File

@@ -8,6 +8,6 @@
"AllowedHosts": "*",
"ConnectionStrings": {
"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 {
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) {
$scope.isEditing = false;
$scope.toggleEditMode = function () {
@@ -15,7 +15,7 @@ angular.module('mealMenuApp', [])
const today = new Date();
$scope.selectedWeek = getWeek(today);
$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 () {
$http.get('/api/mealMenuApi/getMeals')
@@ -29,6 +29,8 @@ angular.module('mealMenuApp', [])
$http.get('/api/mealMenuApi/menu', {
params: { weekNumber: $scope.selectedWeek, year: $scope.selectedYear }
}).then(response => {
console.log("Veckomenydata:", response.data); // ✅ logga ut
$scope.menu = {};
response.data.forEach(item => {
const dayOfWeek = $scope.daysOfWeek[item.dayOfWeek - 1];

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB