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,12 +92,16 @@ 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
FROM WeeklyMenu wm
LEFT JOIN Meals dm ON wm.DinnerMealId = dm.Id
LEFT JOIN Meals lm ON wm.LunchMealId = lm.Id
WHERE wm.WeekNumber = @weekNumber AND wm.Year = @year";
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"]
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>
<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>
</div>

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