Menu - Pre 9 .net
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\NewFolder\" />
|
||||
<Folder Include="wwwroot\uploads\" />
|
||||
<Folder Include="wwwroot\images\meals\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Aberwyn.Models;
|
||||
using Aberwyn.Data;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
@@ -16,35 +23,89 @@ namespace Aberwyn.Controllers
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public IActionResult Veckomeny(int week = -1)
|
||||
public IActionResult Veckomeny(int? week, int? year)
|
||||
{
|
||||
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 today = DateTime.Today;
|
||||
int resolvedWeek = week ?? ISOWeek.GetWeekOfYear(today);
|
||||
int resolvedYear = year ?? today.Year;
|
||||
|
||||
var model = new WeeklyMenuViewModel
|
||||
var menus = menuService.GetWeeklyMenu(resolvedWeek, resolvedYear);
|
||||
|
||||
var vm = new WeeklyMenuViewModel
|
||||
{
|
||||
WeekNumber = currentWeek,
|
||||
Year = currentYear,
|
||||
WeekNumber = resolvedWeek,
|
||||
Year = resolvedYear,
|
||||
WeeklyMenus = menus,
|
||||
AvailableCooks = menuService.GetUsers()
|
||||
};
|
||||
|
||||
return View(model);
|
||||
var recent = menuService
|
||||
.GetMenuEntriesByDateRange(DateTime.Now.AddDays(-28), DateTime.Now)
|
||||
.Select(x => new WeeklyMenuViewModel.RecentMenuEntry
|
||||
{
|
||||
Date = x.Date,
|
||||
BreakfastMealName = x.BreakfastMealName,
|
||||
LunchMealName = x.LunchMealName,
|
||||
DinnerMealName = x.DinnerMealName
|
||||
})
|
||||
.ToList();
|
||||
|
||||
vm.RecentEntries = recent;
|
||||
ViewBag.AvailableMeals = menuService.GetMeals();
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult AddMealAjax([FromBody] Meal meal)
|
||||
{
|
||||
if (meal == null || string.IsNullOrWhiteSpace(meal.Name))
|
||||
return BadRequest("Ogiltigt måltidsobjekt.");
|
||||
|
||||
var service = new MenuService(_configuration, _env);
|
||||
|
||||
// Kontrollera om en måltid med samma namn redan finns
|
||||
var existing = service.GetMeals()
|
||||
.FirstOrDefault(m => m.Name.Equals(meal.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
// Fyll i CreatedAt om det inte sätts automatiskt i databasen
|
||||
meal.CreatedAt = DateTime.Now;
|
||||
service.SaveOrUpdateMeal(meal);
|
||||
existing = meal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Om måltiden finns men saknar data (t.ex. är bara ett namn) kan vi uppdatera den
|
||||
existing.Description = meal.Description;
|
||||
existing.ProteinType = meal.ProteinType;
|
||||
existing.CarbType = meal.CarbType;
|
||||
existing.RecipeUrl = meal.RecipeUrl;
|
||||
existing.ImageUrl = meal.ImageUrl;
|
||||
service.SaveOrUpdateMeal(existing);
|
||||
}
|
||||
|
||||
return Json(new
|
||||
{
|
||||
Id = existing.Id,
|
||||
Name = existing.Name,
|
||||
Description = existing.Description,
|
||||
ProteinType = existing.ProteinType,
|
||||
CarbType = existing.CarbType,
|
||||
RecipeUrl = existing.RecipeUrl
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
var meals = service.GetMealsDetailed();
|
||||
return View("MealAdmin", meals);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public JsonResult SearchMeals(string term)
|
||||
{
|
||||
@@ -64,17 +125,16 @@ namespace Aberwyn.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult SaveVeckomeny(IFormCollection form)
|
||||
public IActionResult SaveVeckomeny(IFormCollection form, int week, int year)
|
||||
{
|
||||
var menuService = new MenuService(_configuration, _env);
|
||||
var allMeals = menuService.GetMeals();
|
||||
|
||||
var model = new MenuViewModel
|
||||
{
|
||||
WeekNumber = ISOWeek.GetWeekOfYear(DateTime.Now),
|
||||
Year = DateTime.Now.Year,
|
||||
WeekNumber = week,
|
||||
Year = year,
|
||||
WeeklyMenus = new List<WeeklyMenu>()
|
||||
};
|
||||
|
||||
@@ -82,62 +142,76 @@ namespace Aberwyn.Controllers
|
||||
|
||||
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)
|
||||
var match = Regex.Match(key, @"Meal\[(\d+)\]\[(\w+)\]");
|
||||
if (!match.Success) continue;
|
||||
|
||||
int day = int.Parse(match.Groups[1].Value);
|
||||
string mealType = match.Groups[2].Value;
|
||||
string mealName = form[key];
|
||||
string cook = form[$"Cook[{day}]"];
|
||||
|
||||
if (!entriesByDay.TryGetValue(day, out var entry))
|
||||
{
|
||||
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)
|
||||
entry = new WeeklyMenu
|
||||
{
|
||||
meal = new Meal { Name = mealName };
|
||||
menuService.SaveMeal(meal);
|
||||
WeekNumber = week,
|
||||
Year = year,
|
||||
DayOfWeek = day + 1
|
||||
};
|
||||
entriesByDay[day] = entry;
|
||||
}
|
||||
|
||||
entry.Cook = cook;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mealName))
|
||||
{
|
||||
switch (mealType.ToLower())
|
||||
{
|
||||
case "lunch":
|
||||
entry.LunchMealId = null;
|
||||
entry.LunchMealName = null;
|
||||
break;
|
||||
case "middag":
|
||||
entry.DinnerMealId = null;
|
||||
entry.DinnerMealName = null;
|
||||
break;
|
||||
case "frukost":
|
||||
entry.BreakfastMealId = null;
|
||||
entry.BreakfastMealName = null;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entriesByDay.TryGetValue(day, out var entry))
|
||||
{
|
||||
entry = new WeeklyMenu
|
||||
{
|
||||
WeekNumber = model.WeekNumber,
|
||||
Year = model.Year
|
||||
};
|
||||
entriesByDay[day] = entry;
|
||||
}
|
||||
var meal = allMeals.FirstOrDefault(m => m.Name.Equals(mealName, StringComparison.OrdinalIgnoreCase))
|
||||
?? new Meal { Name = mealName };
|
||||
|
||||
entry.DayOfWeek = day + 1; // se till att alltid sätta rätt dag
|
||||
entry.Cook = cook;
|
||||
if (meal.Id == 0)
|
||||
menuService.SaveOrUpdateMeal(meal);
|
||||
|
||||
if (mealType.Equals("Lunch", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
switch (mealType.ToLower())
|
||||
{
|
||||
case "lunch":
|
||||
entry.LunchMealId = meal.Id;
|
||||
entry.LunchMealName = meal.Name;
|
||||
}
|
||||
else if (mealType.Equals("Middag", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
case "middag":
|
||||
entry.DinnerMealId = meal.Id;
|
||||
entry.DinnerMealName = meal.Name;
|
||||
}
|
||||
else if (mealType.Equals("Frukost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
case "frukost":
|
||||
entry.BreakfastMealId = meal.Id;
|
||||
entry.BreakfastMealName = meal.Name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
model.WeeklyMenus = entriesByDay.Values.ToList();
|
||||
menuService.UpdateWeeklyMenu(model);
|
||||
|
||||
return RedirectToAction("Veckomeny");
|
||||
return RedirectToAction("Veckomeny", new { week, year });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,62 @@ namespace Aberwyn.Controllers
|
||||
_env = env;
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult View(int id)
|
||||
public IActionResult View(int id, bool edit = false)
|
||||
{
|
||||
var service = new MenuService(_configuration, _env);
|
||||
var meal = service.GetMealById(id);
|
||||
|
||||
ViewData["IsEditing"] = edit; // → här
|
||||
|
||||
if (meal == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View("View", meal);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Meal/Tooltip/{id}")]
|
||||
public IActionResult Tooltip(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = new MenuService(_configuration, _env);
|
||||
var meal = service.GetMealById(id);
|
||||
|
||||
if (meal == null)
|
||||
return Content("<div>Hittade ingen rätt.</div>", "text/html");
|
||||
|
||||
// Se till att Description aldrig är null
|
||||
var desc = meal.Description ?? "";
|
||||
|
||||
// Gör en kort beskrivning
|
||||
var shortDesc = desc.Length > 100
|
||||
? desc.Substring(0, 100) + "…"
|
||||
: desc;
|
||||
|
||||
// Bygg upp en enkel HTML-snutt
|
||||
var html =
|
||||
$"<div class='tooltip-content'>" +
|
||||
$" <strong>{meal.Name}</strong><br/>" +
|
||||
(string.IsNullOrWhiteSpace(shortDesc)
|
||||
? "<em>Ingen beskrivning tillgänglig.</em>"
|
||||
: $"<em>{shortDesc}</em>") +
|
||||
"</div>";
|
||||
|
||||
return Content(html, "text/html");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// För felsökning kan du logga ex.Message, men för tillfället returnera det så vi ser det i devtools
|
||||
return StatusCode(500, $"<pre>{ex.Message}</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Edit(int? id)
|
||||
{
|
||||
@@ -34,30 +80,33 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SaveMeal(Meal meal, IFormFile ImageFile)
|
||||
public 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);
|
||||
var fileName = Path.GetFileNameWithoutExtension(ImageFile.FileName);
|
||||
var extension = Path.GetExtension(ImageFile.FileName);
|
||||
var uniqueName = $"{fileName}_{Guid.NewGuid()}{extension}";
|
||||
var imagePath = Path.Combine("wwwroot/images/meals", uniqueName);
|
||||
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||
using (var stream = new FileStream(imagePath, FileMode.Create))
|
||||
{
|
||||
await ImageFile.CopyToAsync(fileStream);
|
||||
ImageFile.CopyTo(stream);
|
||||
}
|
||||
|
||||
meal.ImageUrl = "/uploads/" + uniqueFileName;
|
||||
// Spara relativ sökväg för visning
|
||||
meal.ImageUrl = $"/images/meals/{uniqueName}";
|
||||
}
|
||||
|
||||
service.SaveOrUpdateMeal(meal);
|
||||
return RedirectToAction("Edit", new { id = meal.Id });
|
||||
|
||||
return RedirectToAction("View", new { id = meal.Id });
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult DeleteMeal(int id)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Aberwyn.Models;
|
||||
using Aberwyn.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
@@ -11,71 +13,45 @@ namespace Aberwyn.Controllers
|
||||
{
|
||||
private readonly MenuService _menuService;
|
||||
|
||||
public MealMenuApiController(MenuService menuService)
|
||||
public MealMenuApiController(IConfiguration configuration, IHostEnvironment env)
|
||||
{
|
||||
_menuService = menuService;
|
||||
_menuService = new MenuService(configuration, env);
|
||||
}
|
||||
|
||||
// API endpoint to fetch the weekly menu
|
||||
[HttpGet("menu")]
|
||||
public IActionResult GetMenu(int weekNumber, int year)
|
||||
{
|
||||
var menu = _menuService.GetWeeklyMenu(weekNumber, year);
|
||||
|
||||
if (menu == null || !menu.Any())
|
||||
{
|
||||
// Return an empty object or array instead of a 404 error
|
||||
return Ok(new List<WeeklyMenu>()); // Return empty list if no menu is found
|
||||
}
|
||||
|
||||
return Ok(menu); // Return the menu as a JSON response
|
||||
return Ok(menu ?? new List<WeeklyMenu>());
|
||||
}
|
||||
|
||||
// API endpoint to fetch the weekly menu
|
||||
[HttpGet("getMeals")]
|
||||
public IActionResult GetMeals()
|
||||
{
|
||||
var meals = _menuService.GetMeals();
|
||||
|
||||
if (meals == null || !meals.Any())
|
||||
{
|
||||
// Return an empty object or array instead of a 404 error
|
||||
return Ok(new List<Meal>()); // Return empty list if no menu is found
|
||||
}
|
||||
|
||||
return Ok(meals); // Return the menu as a JSON response
|
||||
return Ok(meals ?? new List<Meal>());
|
||||
}
|
||||
|
||||
// API endpoint to save the updated menu (if you need this)
|
||||
[HttpPut("menu")]
|
||||
public IActionResult SaveMenu([FromBody] MenuViewModel weeklyMenu)
|
||||
{
|
||||
// Process and save the menu (you may need to write this logic)
|
||||
_menuService.UpdateWeeklyMenu(weeklyMenu);
|
||||
|
||||
return Ok("Menu saved successfully");
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("addMeal")]
|
||||
public IActionResult AddMeal([FromBody] Meal meal)
|
||||
{
|
||||
if (meal == null || string.IsNullOrWhiteSpace(meal.Name))
|
||||
{
|
||||
return BadRequest("Meal Name is required.");
|
||||
}
|
||||
|
||||
// Använd AddMeal som returnerar det nya ID:t
|
||||
var mealId = _menuService.AddMeal(meal);
|
||||
|
||||
if (mealId > 0)
|
||||
{
|
||||
return Ok(new { Message = "Meal added successfully", MealId = mealId });
|
||||
}
|
||||
|
||||
return StatusCode(500, "Failed to add meal.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Aberwyn.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
@@ -197,7 +198,7 @@ namespace Aberwyn.Data
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
|
||||
string query = @"SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt
|
||||
string query = @"SELECT Id, Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl
|
||||
FROM Meals WHERE Id = @id";
|
||||
|
||||
using var cmd = new MySqlCommand(query, connection);
|
||||
@@ -214,6 +215,7 @@ namespace Aberwyn.Data
|
||||
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")),
|
||||
ImageUrl = reader.IsDBNull(reader.GetOrdinal("ImageUrl")) ? null : reader.GetString(reader.GetOrdinal("ImageUrl")),
|
||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt"))
|
||||
};
|
||||
|
||||
@@ -234,44 +236,48 @@ namespace Aberwyn.Data
|
||||
|
||||
public void SaveOrUpdateMeal(Meal meal)
|
||||
{
|
||||
using var connection = GetConnection();
|
||||
connection.Open();
|
||||
using var conn = new MySqlConnection(_configuration.GetConnectionString("DefaultConnection"));
|
||||
conn.Open();
|
||||
|
||||
string query;
|
||||
bool isNew = meal.Id == 0;
|
||||
|
||||
if (isNew)
|
||||
using var cmd = conn.CreateCommand();
|
||||
if (meal.Id == 0)
|
||||
{
|
||||
query = @"INSERT INTO Meals (Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt)
|
||||
VALUES (@name, @desc, @protein, @carb, @url, @created)";
|
||||
cmd.CommandText = @"INSERT INTO Meals
|
||||
(Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt, ImageUrl)
|
||||
VALUES (@Name, @Description, @ProteinType, @CarbType, @RecipeUrl, @CreatedAt, @ImageUrl);
|
||||
SELECT LAST_INSERT_ID();";
|
||||
}
|
||||
else
|
||||
{
|
||||
query = @"UPDATE Meals
|
||||
SET Name = @name, Description = @desc, ProteinType = @protein, CarbType = @carb, RecipeUrl = @url
|
||||
WHERE Id = @id";
|
||||
cmd.CommandText = @"UPDATE Meals
|
||||
SET Name = @Name, Description = @Description, ProteinType = @ProteinType,
|
||||
CarbType = @CarbType, RecipeUrl = @RecipeUrl, ImageUrl = @ImageUrl
|
||||
WHERE Id = @Id";
|
||||
cmd.Parameters.AddWithValue("@Id", meal.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 ?? "");
|
||||
cmd.Parameters.AddWithValue("@Name", meal.Name ?? "");
|
||||
cmd.Parameters.AddWithValue("@Description", meal.Description ?? "");
|
||||
cmd.Parameters.AddWithValue("@ProteinType", meal.ProteinType ?? "");
|
||||
cmd.Parameters.AddWithValue("@CarbType", meal.CarbType ?? "");
|
||||
cmd.Parameters.AddWithValue("@RecipeUrl", meal.RecipeUrl ?? "");
|
||||
cmd.Parameters.AddWithValue("@CreatedAt", meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt);
|
||||
cmd.Parameters.AddWithValue("@ImageUrl", meal.ImageUrl ?? "");
|
||||
|
||||
if (isNew)
|
||||
if (meal.Id == 0)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@created", meal.CreatedAt == default ? DateTime.Now : meal.CreatedAt);
|
||||
var insertedId = Convert.ToInt32(cmd.ExecuteScalar());
|
||||
meal.Id = insertedId; // ← Sätt ID direkt på objektet
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@id", meal.Id);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void UpdateWeeklyMenu(MenuViewModel menuData)
|
||||
{
|
||||
if (menuData == null || menuData.WeeklyMenus == null)
|
||||
@@ -384,6 +390,54 @@ namespace Aberwyn.Data
|
||||
_ => throw new System.ArgumentException("Invalid day name")
|
||||
};
|
||||
}
|
||||
public List<WeeklyMenu> GetMenuEntriesByDateRange(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var results = new List<WeeklyMenu>();
|
||||
|
||||
// Hitta start- och slut-år/veckonummer
|
||||
int startWeek = ISOWeek.GetWeekOfYear(startDate);
|
||||
int startYear = startDate.Year;
|
||||
int endWeek = ISOWeek.GetWeekOfYear(endDate);
|
||||
int endYear = endDate.Year;
|
||||
|
||||
// Loopar över alla veckor från start → slut
|
||||
var allMenus = new List<WeeklyMenu>();
|
||||
int w = startWeek, y = startYear;
|
||||
while (y < endYear || (y == endYear && w <= endWeek))
|
||||
{
|
||||
allMenus.AddRange(GetWeeklyMenu(w, y));
|
||||
|
||||
// öka vecka, hantera årsskifte
|
||||
int weeksInYear = ISOWeek.GetWeeksInYear(y);
|
||||
if (w == weeksInYear)
|
||||
{
|
||||
w = 1;
|
||||
y++;
|
||||
}
|
||||
else
|
||||
{
|
||||
w++;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtrera på datumintervall
|
||||
foreach (var menu in allMenus)
|
||||
{
|
||||
// Konvertera DayOfWeek (1=Mon…7=Sun) till System.DayOfWeek
|
||||
var dow = (DayOfWeek)(menu.DayOfWeek % 7);
|
||||
var date = ISOWeek.ToDateTime(menu.Year, menu.WeekNumber, dow);
|
||||
|
||||
if (date.Date >= startDate.Date && date.Date <= endDate.Date)
|
||||
{
|
||||
menu.Date = date; // Se till att WeeklyMenu har en Date-property
|
||||
results.Add(menu);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,16 @@ namespace Aberwyn.Models
|
||||
public string BreakfastMealName { get; set; } // 👈 Och denna
|
||||
public string LunchMealName { get; set; }
|
||||
public string DinnerMealName { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class RecentMenuEntry
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string BreakfastMealName { get; set; }
|
||||
public string LunchMealName { get; set; }
|
||||
public string DinnerMealName { get; set; }
|
||||
}
|
||||
public class Meal
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
@@ -5,18 +5,30 @@ using System.Linq;
|
||||
|
||||
namespace Aberwyn.Models
|
||||
{
|
||||
|
||||
public class WeeklyMenuViewModel
|
||||
{
|
||||
public int WeekNumber { get; set; }
|
||||
public int Year { get; set; }
|
||||
|
||||
public List<RecentMenuEntry> RecentEntries { get; set; } = new();
|
||||
public List<WeeklyMenu> WeeklyMenus { get; set; } = new();
|
||||
public List<User> AvailableCooks { get; set; } = new();
|
||||
|
||||
// Ny lista för översikt
|
||||
public List<WeeklyMenuViewModel> PreviousWeeks { get; set; } = new();
|
||||
public class RecentMenuEntry
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public string BreakfastMealName { get; set; }
|
||||
public string LunchMealName { get; set; }
|
||||
public string DinnerMealName { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public WeeklyMenu GetMealEntry(int day, string type)
|
||||
{
|
||||
int dayOfWeek = day + 1;
|
||||
|
||||
return WeeklyMenus.FirstOrDefault(m =>
|
||||
m.DayOfWeek == dayOfWeek &&
|
||||
(
|
||||
@@ -27,4 +39,6 @@ namespace Aberwyn.Models
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore; // Add this for DbContext
|
||||
using Aberwyn.Data; // Include your data namespace
|
||||
using MySql.EntityFrameworkCore.Extensions; // For MySQL support
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Aberwyn.Data;
|
||||
using MySql.EntityFrameworkCore.Extensions;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddRazorPages(); // Add this line to enable Razor Pages
|
||||
builder.Services.AddHttpClient(); // Register HttpClient
|
||||
builder.Services.AddControllersWithViews()
|
||||
.AddJsonOptions(opts =>
|
||||
{
|
||||
// Behåll propertynamn som i C#-klassen (PascalCase)
|
||||
opts.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||
// Ignorera null-värden vid serialisering
|
||||
opts.JsonSerializerOptions.IgnoreNullValues = true;
|
||||
});
|
||||
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
// Configure your DbContext with MySQL
|
||||
builder.Services.AddDbContext<BudgetContext>(options =>
|
||||
options.UseMySQL(builder.Configuration.GetConnectionString("BudgetDb")));
|
||||
|
||||
|
||||
// Register your BudgetService with a scoped lifetime
|
||||
// Register your services
|
||||
builder.Services.AddScoped<BudgetService>();
|
||||
builder.Services.AddScoped<MenuService>();
|
||||
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
{
|
||||
@@ -31,6 +39,7 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
options.SupportedCultures = supportedCultures;
|
||||
options.SupportedUICultures = supportedCultures;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@@ -40,20 +49,19 @@ if (!app.Environment.IsDevelopment())
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
//app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
// Map controller endpoints (including AJAX JSON actions)
|
||||
app.MapControllers();
|
||||
|
||||
// Map default controller route
|
||||
// Conventional MVC route
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
// Map Razor Pages
|
||||
app.MapRazorPages(); // Add this line to map Razor Pages
|
||||
app.MapRazorPages();
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -1,93 +1,229 @@
|
||||
@model WeeklyMenuViewModel
|
||||
@using System.Globalization
|
||||
@model Aberwyn.Models.WeeklyMenuViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Veckomeny";
|
||||
var days = new[] { "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag" };
|
||||
var mealTypes = new[] { "Frukost", "Lunch", "Middag" };
|
||||
var allMeals = ViewBag.AvailableMeals as List<Aberwyn.Models.Meal>;
|
||||
}
|
||||
|
||||
<h1>Veckomeny - Vecka @Model.WeekNumber</h1>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
<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>
|
||||
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
[x-show] {
|
||||
transition: all 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rotate-chevron {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.rotate-chevron.open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.meal-input {
|
||||
padding: 4px 6px;
|
||||
font-size: 0.85rem;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #546E7A;
|
||||
background: #445A6E;
|
||||
color: #ECEFF1;
|
||||
}
|
||||
.meal-input.new-entry {
|
||||
border-color: #FFC107; /* Orange */
|
||||
background: inherit; /* Lägg till detta om nödvändigt */
|
||||
}
|
||||
|
||||
.meal-input.existing-entry {
|
||||
border-color: #4CAF50; /* Grön */
|
||||
background: inherit; /* Lägg till detta också */
|
||||
}
|
||||
|
||||
|
||||
.add-meal-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.add-meal-wrapper input[type=text] {
|
||||
width: 160px;
|
||||
padding: 4px 6px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.delete-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
}
|
||||
.delete-btn i {
|
||||
margin-left: 6px;
|
||||
color: #FF8A65;
|
||||
}
|
||||
.week-nav a {
|
||||
color: #FFEB3B;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.week-nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.knownMeals = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(
|
||||
(ViewBag.AvailableMeals as List<Aberwyn.Models.Meal>)?.Select(m => m.Name.Trim()).ToList() ?? new List<string>()));
|
||||
</script>
|
||||
|
||||
<div class="weekly-menu-wrapper"
|
||||
style="display:grid; grid-template-columns:1fr; gap:16px; padding:16px; background:#263238;"
|
||||
x-data="{
|
||||
highlightNew(event) {
|
||||
const input = event.target;
|
||||
const val = input.value?.trim().toLowerCase();
|
||||
input.classList.remove('new-entry', 'existing-entry');
|
||||
|
||||
if (!val) return;
|
||||
|
||||
const isKnown = window.knownMeals.some(name => name.toLowerCase() === val);
|
||||
input.classList.add(isKnown ? 'existing-entry' : 'new-entry');
|
||||
}
|
||||
}"
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Rest of the content remains unchanged -->
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="weekly-editor">
|
||||
<h1 style="color:#ECEFF1; margin-bottom:12px;">Veckomeny - Vecka @Model.WeekNumber</h1>
|
||||
<div class="week-nav" style="margin-bottom:12px; display:flex; align-items:center; color:#B0BEC5; font-size:0.9rem;">
|
||||
<a asp-action="Veckomeny" asp-route-week="@(Model.WeekNumber - 1)" asp-route-year="@Model.Year" style="margin-right:8px;">← Föregående vecka</a>
|
||||
<span style="font-weight:bold; color:#ECEFF1;">Vecka @Model.WeekNumber</span>
|
||||
<a asp-action="Veckomeny" asp-route-week="@(Model.WeekNumber + 1)" asp-route-year="@Model.Year" style="margin-left:8px;">Nästa vecka →</a>
|
||||
</div>
|
||||
|
||||
<form method="post" asp-action="SaveVeckomeny">
|
||||
<input type="hidden" name="week" value="@Model.WeekNumber" />
|
||||
<input type="hidden" name="year" value="@Model.Year" />
|
||||
<table style="width:100%; border-collapse:separate; border-spacing:0 4px; font-size:0.9rem;">
|
||||
<thead>
|
||||
<tr style="background:#37474F; color:#ECEFF1;">
|
||||
<th style="padding:8px 12px; text-align:left; border-radius:4px 0 0 4px;">Dag</th>
|
||||
<th style="padding:8px 12px; text-align:left;">Middag</th>
|
||||
<th style="padding:8px 12px; text-align:left;">Kock</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int i = 0; i < 7; i++) {
|
||||
var dinnerEntry = Model.GetMealEntry(i, "Middag");
|
||||
var lunchEntry = Model.GetMealEntry(i, "Lunch");
|
||||
var breakfastEntry = Model.GetMealEntry(i, "Frukost");
|
||||
<tbody x-data="{ showExtra: false }">
|
||||
<tr style="background:#2A3A48; color:#ECEFF1;">
|
||||
<td style="padding:8px 12px; font-weight:bold; vertical-align:top; width:12%;">
|
||||
<button type="button" x-on:click="showExtra = !showExtra" style="margin-right:6px; background:none; border:none; color:#ECEFF1; font-size:1rem; cursor:pointer;">
|
||||
<i :class="showExtra ? 'fa-solid fa-chevron-right rotate-chevron open' : 'fa-solid fa-chevron-right rotate-chevron'"></i>
|
||||
</button>
|
||||
@days[i]
|
||||
</td>
|
||||
<td style="padding:8px 12px; vertical-align:top;">
|
||||
<div style="display:flex; align-items:center; gap:4px;">
|
||||
<input type="text" name="Meal[@i][Middag]" value="@dinnerEntry?.DinnerMealName" placeholder="Lägg till middag..." list="meals-list" class="meal-input" :tabindex="showExtra ? 0 : -1" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa middag" onclick="this.previousElementSibling.value='';">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px 12px; vertical-align:top; width:14%;">
|
||||
<select name="Cook[@i]" :tabindex="showExtra ? 0 : -1" style="width:100%; padding:6px; border-radius:8px; border:1px solid #546E7A; background:#445A6E; color:#ECEFF1; font-size:0.9rem;">
|
||||
<option value="">Välj kock</option>
|
||||
@foreach(var user in Model.AvailableCooks) {
|
||||
var selected = Model.WeeklyMenus.FirstOrDefault(m => m.DayOfWeek == i+1)?.Cook == user.Username;
|
||||
<option value="@user.Username" selected="@selected">@user.Name</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr x-show="showExtra" x-transition x-cloak style="background:#2A3A48; color:#ECEFF1;">
|
||||
<td></td>
|
||||
<td colspan="2">
|
||||
<div style="display:flex; flex-direction:column; gap:8px;">
|
||||
<div style="display:flex; align-items:center; gap:4px;">
|
||||
<label style="font-size:0.85rem; color:#B0BEC5; min-width:60px;">Frukost:</label>
|
||||
<input type="text" name="Meal[@i][Frukost]" value="@breakfastEntry?.BreakfastMealName" placeholder="Lägg till frukost..." list="meals-list" class="meal-input" :tabindex="showExtra ? 0 : -1" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa frukost" onclick="this.previousElementSibling.value='';">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:4px;">
|
||||
<label style="font-size:0.85rem; color:#B0BEC5; min-width:60px;">Lunch:</label>
|
||||
<input type="text" name="Meal[@i][Lunch]" value="@lunchEntry?.LunchMealName" placeholder="Lägg till lunch..." list="meals-list" class="meal-input" :tabindex="showExtra ? 0 : -1" x-on:input="highlightNew($event)" />
|
||||
<button type="button" class="delete-btn" title="Rensa lunch" onclick="this.previousElementSibling.value='';">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Save button och övrigt -->
|
||||
<div class="add-meal-wrapper">
|
||||
<button type="submit" style="padding:8px 16px; border:none; border-radius:8px; background:#FFEB3B; font-weight:bold; font-size:0.9rem;">Spara veckomeny</button>
|
||||
</div>
|
||||
<aside class="recent-history" style="background:#37474F; border-radius:8px; color:#ECEFF1; padding:16px; margin-top:24px;">
|
||||
<h2 style="margin:0 0 8px; font-size:1rem;">Översikt senaste 4 veckor</h2>
|
||||
@{
|
||||
var lastWeeks = Enumerable.Range(1, 4)
|
||||
.Select(i => DateTime.Now.AddDays(-7 * i))
|
||||
.Select(dt => new { Year = dt.Year, Week = ISOWeek.GetWeekOfYear(dt) })
|
||||
.Distinct().ToList();
|
||||
}
|
||||
<table style="width:100%; border-collapse:collapse; font-size:0.85rem;">
|
||||
<thead>
|
||||
<tr style="background:#546E7A; color:#ECEFF1;">
|
||||
<th style="padding:4px 8px;">Vecka</th>
|
||||
@foreach (var d in days)
|
||||
{
|
||||
<th style="padding:4px 8px;">@d</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var w in lastWeeks)
|
||||
{
|
||||
<tr style="background:#2A3A48; color:#ECEFF1;">
|
||||
<td style="padding:4px 8px; font-weight:bold;">@w.Week</td>
|
||||
@for (int idx = 1; idx <= 7; idx++)
|
||||
{
|
||||
var entry = Model.RecentEntries?.FirstOrDefault(e =>
|
||||
e.Date.Year == w.Year &&
|
||||
ISOWeek.GetWeekOfYear(e.Date) == w.Week &&
|
||||
((int)e.Date.DayOfWeek == (idx % 7))
|
||||
);
|
||||
<td style="padding:4px 8px; white-space:nowrap;">@(entry?.DinnerMealName ?? "—")</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</aside>
|
||||
<datalist id="meals-list">
|
||||
@foreach(var m in ViewBag.AvailableMeals as List<Aberwyn.Models.Meal>) {
|
||||
<option value="@m.Name" id="@m.Name"></option>
|
||||
}
|
||||
</datalist>
|
||||
</form>
|
||||
</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>
|
||||
}
|
||||
|
||||
@@ -1,54 +1,97 @@
|
||||
@model Aberwyn.Models.MenuViewModel
|
||||
|
||||
@{
|
||||
// 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'
|
||||
var hideSidebar = Context.Request.Path.Value.EndsWith("/nosidebar", StringComparison.OrdinalIgnoreCase);
|
||||
Layout = hideSidebar ? null : "_Layout";
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="mealMenuApp">
|
||||
<html lang="sv" ng-app="mealMenuApp">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Meal Menu Overview</title>
|
||||
<meta charset="utf-8" />
|
||||
<title>Veckomeny</title>
|
||||
|
||||
<!-- AngularJS -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-sanitize.js"></script>
|
||||
<script src="~/js/menu.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="~/css/meal-menu.css">
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="~/css/meal-menu.css" />
|
||||
<style>
|
||||
.view-selector { position: absolute; top: 24px; right: 24px; }
|
||||
.view-selector select { padding: 6px 10px; border-radius: 4px; border: none; background: #546E7A; color: #ECEFF1; font-size: 0.95rem; cursor: pointer; }
|
||||
.list-view .day-item { background: #37474F; margin-bottom: 16px; padding: 16px; border-radius: 8px; }
|
||||
.list-view .day-header { font-size: 1.4rem; color: #FFEB3B; margin-bottom: 8px; position: relative; }
|
||||
.list-view .day-header::after { content: ''; position: absolute; bottom: -4px; left: 0; width: 40px; height: 3px; background: #FFEB3B; border-radius: 2px; }
|
||||
.meal-selection { margin: 6px 0; font-size: 1rem; }
|
||||
.meal-selection a { color: #ECEFF1; text-decoration: none; }
|
||||
.meal-selection a:hover { text-decoration: underline; }
|
||||
.not-assigned { color: #B0BEC5; font-style: italic; }
|
||||
.card-view .card-container { display: grid; grid-template-columns: repeat(auto-fill,minmax(180px,1fr)); gap: 16px; }
|
||||
.meal-card { position: relative; height: 180px; color: #fff; background-size: cover; background-position: center; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 8px rgba(0,0,0,0.3); display: flex; align-items: flex-end; cursor: pointer; }
|
||||
.meal-card::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to top,rgba(0,0,0,0.7),transparent); }
|
||||
.card-content { position: relative; padding: 12px; z-index: 1; width: 100%; }
|
||||
.card-content .day { font-size: 1.2rem; font-weight: bold; margin-bottom: 6px; }
|
||||
.card-content .meal { font-size: 0.9rem; line-height: 1.2; }
|
||||
</style>
|
||||
</head>
|
||||
<body ng-controller="MealMenuController">
|
||||
<body ng-controller="MealMenuController" ng-init="viewMode='list'">
|
||||
<div class="meal-menu-page" style="position:relative">
|
||||
<h1>Veckomeny</h1>
|
||||
|
||||
<div class="meal-menu-page">
|
||||
<h1 class="page-title">Meal Menu Overview</h1>
|
||||
|
||||
<div class="date-picker">
|
||||
<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">
|
||||
<div ng-repeat="day in daysOfWeek" class="day-item">
|
||||
<div class="day-header">{{ day }}</div>
|
||||
|
||||
<div class="meal-info" ng-if="!isEditing">
|
||||
<div ng-if="menu[day]">
|
||||
<div ng-if="menu[day].breakfastMealName">
|
||||
<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>Middag:</strong> {{ menu[day].dinnerMealName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!menu[day]">
|
||||
<span class="not-assigned">Inte bestämd</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Vecko-navigering -->
|
||||
<div class="date-picker">
|
||||
<button ng-click="goToPreviousWeek()">← Föregående vecka</button>
|
||||
<span>Vecka {{selectedWeek}} – {{selectedYear}}</span>
|
||||
<button ng-click="goToNextWeek()">Nästa vecka →</button>
|
||||
</div>
|
||||
|
||||
<!-- Vy-väljare -->
|
||||
<div class="view-selector">
|
||||
<label for="viewModeSelect">Visa:</label>
|
||||
<select id="viewModeSelect" ng-model="viewMode">
|
||||
<option value="list">Lista</option>
|
||||
<option value="card">Kort</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Vy: List eller Kort -->
|
||||
<div ng-switch="viewMode">
|
||||
<div ng-switch-when="list" class="list-view">
|
||||
<div ng-repeat="day in daysOfWeek" class="day-item">
|
||||
<div class="day-header">{{day}}</div>
|
||||
<div class="meal-info" ng-if="menu[day]">
|
||||
<div ng-if="menu[day].breakfastMealId" class="meal-selection">
|
||||
<a href="/Meal/View/{{menu[day].breakfastMealId}}" target="_blank"><strong>Frukost:</strong> {{menu[day].breakfastMealName}}</a>
|
||||
</div>
|
||||
<div ng-if="menu[day].lunchMealId" class="meal-selection">
|
||||
<a href="/Meal/View/{{menu[day].lunchMealId}}" target="_blank"><strong>Lunch:</strong> {{menu[day].lunchMealName}}</a>
|
||||
</div>
|
||||
<div ng-if="menu[day].dinnerMealId" class="meal-selection">
|
||||
<a href="/Meal/View/{{menu[day].dinnerMealId}}" target="_blank"><strong>Middag:</strong> {{menu[day].dinnerMealName}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!menu[day]"><span class="not-assigned">Inte bestämd</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-switch-when="card" class="card-view">
|
||||
<div class="card-container">
|
||||
<div ng-repeat="day in daysOfWeek"
|
||||
class="meal-card"
|
||||
ng-style="{'background-image':'url('+getDayImage(day)+')'}"
|
||||
ng-click="openMeal(getMealIdByDay(day))">
|
||||
<div class="card-content">
|
||||
<div class="day">{{day}}</div>
|
||||
<div class="meal" ng-if="menu[day].breakfastMealName">Frukost: {{menu[day].breakfastMealName}}</div>
|
||||
<div class="meal" ng-if="menu[day].lunchMealName">Lunch: {{menu[day].lunchMealName}}</div>
|
||||
<div class="meal" ng-if="menu[day].dinnerMealName">Middag: {{menu[day].dinnerMealName}}</div>
|
||||
<div class="meal" ng-if="!menu[day]">Inte bestämd</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<div>
|
||||
<label for="ImageFile">Bild</label>
|
||||
<input type="file" name="ImageFile" />
|
||||
<input type="file" name="ImageFile" value="@Model.ImageUrl" />
|
||||
</div>
|
||||
|
||||
<button type="submit">Spara</button>
|
||||
|
||||
@@ -1,90 +1,192 @@
|
||||
@model Aberwyn.Models.Meal
|
||||
@{
|
||||
ViewData["Title"] = Model.Name;
|
||||
bool isEditing = (bool)(ViewData["IsEditing"] ?? false);
|
||||
var imageUrl = string.IsNullOrEmpty(Model.ImageUrl) ? "/images/placeholder-meal.jpg" : Model.ImageUrl;
|
||||
}
|
||||
|
||||
<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 class="meal-header">
|
||||
<img src="@imageUrl" alt="@Model.Name" class="meal-image" />
|
||||
<div class="meal-meta">
|
||||
<h1 class="meal-title">@Model.Name</h1>
|
||||
<p class="description">@Model.Description</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="recipe-section" style="display:none;">
|
||||
<h2>Så här gör du</h2>
|
||||
<p>(Tillagningsinstruktioner kommer snart...)</p>
|
||||
@if (isEditing)
|
||||
{
|
||||
<form asp-action="SaveMeal" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="Id" value="@Model.Id" />
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ImageFile">Bild</label>
|
||||
<input type="file" name="ImageFile" accept="image/*" value="@Model.ImageUrl" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Name">Namn</label>
|
||||
<input type="text" name="Name" value="@Model.Name" class="form-control" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Description">Beskrivning</label>
|
||||
<textarea name="Description" class="form-control">@Model.Description</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ProteinType">Protein</label>
|
||||
<input type="text" name="ProteinType" value="@Model.ProteinType" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="CarbType">Kolhydrat</label>
|
||||
<input type="text" name="CarbType" value="@Model.CarbType" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="RecipeUrl">Receptlänk</label>
|
||||
<input type="url" name="RecipeUrl" value="@Model.RecipeUrl" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="btn">Spara</button>
|
||||
<button type="submit" formaction="@Url.Action("DeleteMeal", new { id = Model.Id })" formmethod="post" onclick="return confirm('Vill du verkligen ta bort denna måltid?');" class="btn-outline">Ta bort</button>
|
||||
<a href="@Url.Action("View", new { id = Model.Id, edit = false })" class="btn-outline">Avbryt</a>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="meal-details">
|
||||
@if (!string.IsNullOrEmpty(Model.ProteinType))
|
||||
{
|
||||
<p><span class="label">Protein:</span> @Model.ProteinType</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.CarbType))
|
||||
{
|
||||
<p><span class="label">Kolhydrat:</span> @Model.CarbType</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.RecipeUrl))
|
||||
{
|
||||
<p><a href="@Model.RecipeUrl" target="_blank">Öppna fullständigt recept</a></p>
|
||||
<p><a href="@Model.RecipeUrl" class="recipe-link" target="_blank">Visa Recept</a></p>
|
||||
}
|
||||
|
||||
<div class="buttons">
|
||||
<button type="button" class="btn-outline" onclick="toggleRecipe()">Visa Tillagning</button>
|
||||
<a class="btn-outline" href="@Url.Action("View", new { id = Model.Id, edit = true })">Redigera</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div id="recipe-section" style="display:none; margin-top:1.5rem;">
|
||||
<h2>Så här gör du</h2>
|
||||
<p class="placeholder">(Tillagningsinstruktioner kommer snart...)</p>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.RecipeUrl))
|
||||
{
|
||||
<p><a href="@Model.RecipeUrl" class="recipe-link" target="_blank">Gå till 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';
|
||||
}
|
||||
const section = document.getElementById('recipe-section');
|
||||
section.style.display = section.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.meal-container {
|
||||
max-width: 700px;
|
||||
max-width: 900px;
|
||||
margin: 2rem auto;
|
||||
background: #fff;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
}
|
||||
.meal-container h1 {
|
||||
.meal-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.meal-meta {
|
||||
flex: 1;
|
||||
}
|
||||
.meal-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
.meal-container .meal-image {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
.meal-image {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.meal-container .description {
|
||||
font-style: italic;
|
||||
.description {
|
||||
font-size: 1.1rem;
|
||||
color: #555;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.meal-container .buttons {
|
||||
margin-bottom: 2rem;
|
||||
.meal-details p {
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
.meal-container .buttons button, .meal-container .buttons .edit-button {
|
||||
.label {
|
||||
font-weight: bold;
|
||||
color: #6a0dad;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.3rem;
|
||||
display: block;
|
||||
}
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.btn,
|
||||
.btn-outline {
|
||||
background-color: #6a0dad;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.7rem 1.5rem;
|
||||
border-radius: 5px;
|
||||
margin: 0.5rem;
|
||||
text-decoration: none;
|
||||
padding: 0.6rem 1.4rem;
|
||||
border-radius: 6px;
|
||||
margin: 0.4rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
.meal-container .buttons .edit-button {
|
||||
display: inline-block;
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
border: 2px solid #6a0dad;
|
||||
color: #6a0dad;
|
||||
}
|
||||
.meal-container a {
|
||||
.recipe-link {
|
||||
color: #6a0dad;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
}
|
||||
.placeholder {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
8
Aberwyn/Views/Meal/_MealTooltip.cshtml
Normal file
8
Aberwyn/Views/Meal/_MealTooltip.cshtml
Normal file
@@ -0,0 +1,8 @@
|
||||
@model dynamic
|
||||
<div class="tooltip-content">
|
||||
<strong>@Model.Name</strong><br />
|
||||
<em>@Model.ShortDescription</em><br />
|
||||
@if (Model.Ingredients != null) {
|
||||
<div><u>Ingredienser:</u> @String.Join(", ", Model.Ingredients)</div>
|
||||
}
|
||||
</div>
|
||||
@@ -208,3 +208,7 @@
|
||||
.new-meal-form button:hover {
|
||||
background-color: #F57C00;
|
||||
}
|
||||
|
||||
.meal-hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
BIN
Aberwyn/wwwroot/images/21f12523-36e9-451f-b120-d9cac21061fe.png
Normal file
BIN
Aberwyn/wwwroot/images/21f12523-36e9-451f-b120-d9cac21061fe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -1,99 +1,84 @@
|
||||
angular.module('mealMenuApp', [])
|
||||
.controller('MealMenuController', function ($scope, $http) {
|
||||
$scope.isEditing = false;
|
||||
$scope.toggleEditMode = function () {
|
||||
$scope.isEditing = !$scope.isEditing;
|
||||
};
|
||||
angular.module('mealMenuApp', ['ngSanitize'])
|
||||
.controller('MealMenuController', function ($scope, $http, $sce) {
|
||||
console.log("Controller initierad");
|
||||
|
||||
$scope.viewMode = 'list';
|
||||
$scope.tooltip = {};
|
||||
$scope.meals = [];
|
||||
$scope.menu = {};
|
||||
$scope.showNewMealForm = false;
|
||||
$scope.newMeal = {};
|
||||
$scope.selectedMealDay = null;
|
||||
$scope.selectedMealType = null;
|
||||
|
||||
const today = new Date();
|
||||
$scope.selectedWeek = getWeek(today);
|
||||
$scope.selectedYear = today.getFullYear();
|
||||
$scope.daysOfWeek = ["Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"];
|
||||
|
||||
$scope.loadMeals = function () {
|
||||
$http.get('/api/mealMenuApi/getMeals')
|
||||
.then(response => {
|
||||
$scope.meals = response.data;
|
||||
console.log("Hämtar måltider...");
|
||||
return $http.get('/api/mealMenuApi/getMeals')
|
||||
.then(res => {
|
||||
console.log("Måltider hämtade:", res.data);
|
||||
$scope.meals = res.data;
|
||||
return res;
|
||||
})
|
||||
.catch(error => console.error("Error fetching meals:", error));
|
||||
.catch(err => console.error("Fel vid hämtning av måltider:", err));
|
||||
};
|
||||
|
||||
$scope.loadMenu = function () {
|
||||
console.log("Hämtar meny för vecka:", $scope.selectedWeek, $scope.selectedYear);
|
||||
$http.get('/api/mealMenuApi/menu', {
|
||||
params: { weekNumber: $scope.selectedWeek, year: $scope.selectedYear }
|
||||
}).then(response => {
|
||||
console.log("Veckomenydata:", response.data); // ✅ logga ut
|
||||
|
||||
}).then(res => {
|
||||
console.log("Menyposter hämtade:", res.data);
|
||||
$scope.menu = {};
|
||||
response.data.forEach(item => {
|
||||
const dayOfWeek = $scope.daysOfWeek[item.dayOfWeek - 1];
|
||||
if (!$scope.menu[dayOfWeek]) $scope.menu[dayOfWeek] = {};
|
||||
|
||||
if (item.breakfastMealId) {
|
||||
$scope.menu[dayOfWeek].breakfastMealId = item.breakfastMealId;
|
||||
$scope.menu[dayOfWeek].breakfastMealName = getMealNameById(item.breakfastMealId);
|
||||
}
|
||||
if (item.lunchMealId) {
|
||||
$scope.menu[dayOfWeek].lunchMealId = item.lunchMealId;
|
||||
$scope.menu[dayOfWeek].lunchMealName = getMealNameById(item.lunchMealId);
|
||||
}
|
||||
if (item.dinnerMealId) {
|
||||
$scope.menu[dayOfWeek].dinnerMealId = item.dinnerMealId;
|
||||
$scope.menu[dayOfWeek].dinnerMealName = getMealNameById(item.dinnerMealId);
|
||||
res.data.forEach(item => {
|
||||
const dayIndex = item.DayOfWeek - 1;
|
||||
if (dayIndex < 0 || dayIndex >= $scope.daysOfWeek.length) {
|
||||
console.warn("Ogiltig dag:", item.DayOfWeek);
|
||||
return;
|
||||
}
|
||||
|
||||
const day = $scope.daysOfWeek[dayIndex];
|
||||
$scope.menu[day] = {};
|
||||
|
||||
['breakfast', 'lunch', 'dinner'].forEach(type => {
|
||||
// Konvertera till PascalCase
|
||||
const capitalType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
const idKey = capitalType + 'MealId';
|
||||
const nameKey = capitalType + 'MealName';
|
||||
|
||||
if (item[idKey]) {
|
||||
const m = $scope.meals.find(x => x.Id === item[idKey]);
|
||||
console.log(`Match för ${type} (${day}):`, m);
|
||||
|
||||
$scope.menu[day][type + 'MealId'] = item[idKey];
|
||||
$scope.menu[day][type + 'MealName'] = m?.Name || item[nameKey] || 'Okänd rätt';
|
||||
|
||||
if (m?.ImageUrl) {
|
||||
$scope.menu[day].imageUrl = m.ImageUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}).catch(error => console.error("Error fetching weekly menu:", error));
|
||||
|
||||
console.log("Byggd meny:", $scope.menu);
|
||||
}).catch(err => console.error("Fel vid hämtning av veckomeny:", err));
|
||||
};
|
||||
|
||||
function getMealNameById(mealId) {
|
||||
const meal = $scope.meals.find(m => m.id === mealId);
|
||||
return meal ? meal.name : "Unknown Meal";
|
||||
}
|
||||
|
||||
$scope.handleMealSelection = function (day, mealType) {
|
||||
if ($scope.menu[day][mealType + "MealId"] === "new") {
|
||||
$scope.showNewMealForm = true;
|
||||
$scope.newMeal = { name: "", description: "", proteinType: "", carbType: "", recipeUrl: "" };
|
||||
$scope.selectedMealDay = day;
|
||||
$scope.selectedMealType = mealType;
|
||||
}
|
||||
$scope.openMeal = function (mealId) {
|
||||
if (!mealId) return;
|
||||
window.open('/Meal/View/' + mealId, '_blank');
|
||||
};
|
||||
|
||||
$scope.saveNewMeal = function () {
|
||||
if (!$scope.newMeal.name) {
|
||||
alert("Meal name is required");
|
||||
return;
|
||||
}
|
||||
$http.post('/api/mealMenuApi/addMeal', $scope.newMeal)
|
||||
.then(response => {
|
||||
const addedMeal = response.data;
|
||||
$scope.meals.push(addedMeal);
|
||||
$scope.menu[$scope.selectedMealDay][$scope.selectedMealType + "MealId"] = addedMeal.id;
|
||||
$scope.showNewMealForm = false;
|
||||
$scope.newMeal = {}; // Reset new meal data after save
|
||||
})
|
||||
.catch(error => console.error("Error saving new meal:", error));
|
||||
$scope.getDayImage = function (day) {
|
||||
const item = $scope.menu[day];
|
||||
return (item && item.imageUrl) || '/images/default-meal.jpg';
|
||||
};
|
||||
|
||||
$scope.cancelNewMeal = function () {
|
||||
$scope.showNewMealForm = false;
|
||||
$scope.newMeal = {}; // Reset new meal data when canceled
|
||||
$scope.getMealIdByDay = function (day) {
|
||||
const item = $scope.menu[day] || {};
|
||||
return item.dinnerMealId || item.lunchMealId || item.breakfastMealId || null;
|
||||
};
|
||||
|
||||
function getWeek(date) {
|
||||
const day = date.getDay() || 7;
|
||||
date.setDate(date.getDate() + 4 - day);
|
||||
const yearStart = new Date(date.getFullYear(), 0, 1);
|
||||
return Math.ceil(((date - yearStart) / 86400000 + 1) / 7);
|
||||
}
|
||||
|
||||
$scope.goToPreviousWeek = function () {
|
||||
if ($scope.selectedWeek === 1) {
|
||||
$scope.selectedYear--;
|
||||
@@ -114,6 +99,17 @@
|
||||
$scope.loadMenu();
|
||||
};
|
||||
|
||||
$scope.loadMeals();
|
||||
$scope.loadMenu();
|
||||
function getWeek(d) {
|
||||
d = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
const dayNum = d.getDay() || 7;
|
||||
d.setDate(d.getDate() + 4 - dayNum);
|
||||
const yearStart = new Date(d.getFullYear(), 0, 1);
|
||||
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
|
||||
}
|
||||
|
||||
console.log("Initierar måltidsladdning...");
|
||||
$scope.loadMeals().then(() => {
|
||||
console.log("Laddar meny efter måltider...");
|
||||
$scope.loadMenu();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user