diff --git a/Aberwyn/Aberwyn.csproj b/Aberwyn/Aberwyn.csproj index 6cc03ce..5e012f0 100644 --- a/Aberwyn/Aberwyn.csproj +++ b/Aberwyn/Aberwyn.csproj @@ -10,6 +10,7 @@ + all @@ -20,4 +21,9 @@ + + + + + diff --git a/Aberwyn/Controllers/BudgetApiController.cs b/Aberwyn/Controllers/BudgetApiController.cs index 098129a..2c39080 100644 --- a/Aberwyn/Controllers/BudgetApiController.cs +++ b/Aberwyn/Controllers/BudgetApiController.cs @@ -19,16 +19,31 @@ namespace Aberwyn.Controllers [HttpGet("items")] public IActionResult GetBudgetItems([FromQuery] int month, [FromQuery] int year) { - var items = _budgetService.GetBudgetItems(month, year); - return Ok(items); + try + { + var items = _budgetService.GetBudgetItems(month, year); + return Ok(items); + } + catch (Exception ex) + { + // Log the exception (consider using a logging framework) + return StatusCode(500, new { message = "An error occurred while fetching budget items.", details = ex.Message }); + } } - // New endpoint to get categories [HttpGet("categories")] public IActionResult GetCategories() { - var categories = _budgetService.GetCategories(); - return Ok(categories); + try + { + var categories = _budgetService.GetCategories(); + return Ok(categories); + } + catch (Exception ex) + { + // Log the exception + return StatusCode(500, new { message = "An error occurred while fetching categories.", details = ex.Message }); + } } [HttpPut("items")] @@ -39,15 +54,20 @@ namespace Aberwyn.Controllers return BadRequest("Invalid budget item data."); } - // Assuming you have a method in your BudgetService to update an item - var result = _budgetService.UpdateBudgetItem(item); - - if (result) + try { - return Ok("Item updated successfully."); + var result = _budgetService.UpdateBudgetItem(item); + if (result) + { + return Ok("Item updated successfully."); + } + return StatusCode(500, "Error updating item."); + } + catch (Exception ex) + { + // Log the exception + return StatusCode(500, new { message = "An error occurred while updating the item.", details = ex.Message }); } - - return StatusCode(500, "Error updating item."); } [HttpPost("items")] @@ -58,17 +78,20 @@ namespace Aberwyn.Controllers return BadRequest("Invalid budget item data."); } - // Assuming you have a method in your BudgetService to add an item - var result = _budgetService.AddBudgetItem(item); - - if (result) + try { - return CreatedAtAction(nameof(GetBudgetItems), new { id = item.ID }, item); + var result = _budgetService.AddBudgetItem(item); + if (result) + { + return CreatedAtAction(nameof(GetBudgetItems), new { id = item.ID }, item); + } + return StatusCode(500, "Error adding item."); + } + catch (Exception ex) + { + // Log the exception + return StatusCode(500, new { message = "An error occurred while adding the item.", details = ex.Message }); } - - return StatusCode(500, "Error adding item."); } - } - } diff --git a/Aberwyn/Controllers/HomeController.cs b/Aberwyn/Controllers/HomeController.cs index 3137eec..15df38f 100644 --- a/Aberwyn/Controllers/HomeController.cs +++ b/Aberwyn/Controllers/HomeController.cs @@ -11,12 +11,15 @@ namespace Aberwyn.Controllers { private readonly ILogger _logger; private readonly BudgetService _budgetService; + private readonly MenuService _menuService; + // Constructor to inject dependencies - public HomeController(ILogger logger, BudgetService budgetService) + public HomeController(ILogger logger, BudgetService budgetService, MenuService menuService) { _logger = logger; _budgetService = budgetService; + _menuService = menuService; } public IActionResult Index() @@ -34,6 +37,31 @@ namespace Aberwyn.Controllers return View(); } + public IActionResult Menu() + { + // Get the current date and week + var currentDate = DateTime.Now; + var currentYear = currentDate.Year; + var calendar = new GregorianCalendar(); + var currentWeek = calendar.GetWeekOfYear(currentDate, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); + + // Fetch weekly menu items for the current week and year + var weeklyMenus = _menuService.GetWeeklyMenu(currentWeek, currentYear); + var meals = _menuService.GetMeals(); // Fetch all meals + + var model = new MenuViewModel + { + WeeklyMenus = weeklyMenus, // List of WeeklyMenu items + Meals = meals, // List of available meals + WeekNumber = currentWeek, // Dynamically set the current week number + Year = currentYear // Dynamically set the current year + }; + + return View(model); + } + + + // Optimized Budget Action to fetch filtered data directly from the database public IActionResult Budget(string month, int? year) { diff --git a/Aberwyn/Controllers/MealMenuApiController.cs b/Aberwyn/Controllers/MealMenuApiController.cs new file mode 100644 index 0000000..5a503ab --- /dev/null +++ b/Aberwyn/Controllers/MealMenuApiController.cs @@ -0,0 +1,81 @@ +// MealMenuApiController.cs +using Aberwyn.Models; +using Aberwyn.Data; +using Microsoft.AspNetCore.Mvc; + +namespace Aberwyn.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class MealMenuApiController : ControllerBase + { + private readonly MenuService _menuService; + + public MealMenuApiController(MenuService menuService) + { + _menuService = menuService; + } + + // 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()); // Return empty list if no menu is found + } + + return Ok(menu); // Return the menu as a JSON response + } + + // 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()); // Return empty list if no menu is found + } + + return Ok(meals); // Return the menu as a JSON response + } + + // 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."); + } + + var mealId = _menuService.AddMeal(meal); + + if (mealId > 0) + { + return Ok(new { Message = "Meal added successfully", MealId = mealId }); + } + + return StatusCode(500, "Failed to add meal."); + } + + + + } +} diff --git a/Aberwyn/Controllers/RealEstateApiController.cs b/Aberwyn/Controllers/RealEstateApiController.cs index 88c76b5..c33d82c 100644 --- a/Aberwyn/Controllers/RealEstateApiController.cs +++ b/Aberwyn/Controllers/RealEstateApiController.cs @@ -1,45 +1,157 @@ -using Microsoft.AspNetCore.Mvc; +using HtmlAgilityPack; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; [Route("api/[controller]")] [ApiController] -public class RealEstateApiController : ControllerBase +public class RealEstateController : ControllerBase { - private readonly HttpClient _httpClient; - - public RealEstateApiController(HttpClient httpClient) + // Step 1: Find image links without downloading + [HttpPost("findImages")] + public async Task FindImages([FromBody] UrlRequest request) { - _httpClient = httpClient; - } - - [HttpPost("download")] - public async Task DownloadWebsite([FromBody] WebsiteRequest request) - { - if (string.IsNullOrWhiteSpace(request.Url)) + if (string.IsNullOrEmpty(request.Url)) { - return BadRequest("URL cannot be empty."); + return BadRequest("URL is required."); } try { - var response = await _httpClient.GetAsync(request.Url); - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); + var imageLinks = await FetchImageLinksFromWebsiteAsync(request.Url); + return Ok(new { images = imageLinks }); + } + catch (Exception ex) + { + return StatusCode(500, $"Error: {ex.Message}"); + } + } - // Optional: Save content to a file - System.IO.File.WriteAllText("DownloadedWebsite.html", content); + // Step 2: Download images when user chooses to do so + [HttpPost("download")] + public async Task DownloadWebsite([FromBody] UrlRequest request) + { + if (string.IsNullOrEmpty(request.Url)) + { + return BadRequest("URL is required."); + } - return Ok("Website downloaded successfully."); + try + { + var images = await FetchImagesFromWebsiteAsync(request.Url); + + // Log the fetched images + Console.WriteLine("Fetched images: " + string.Join(", ", images)); + + return Ok(new { images }); + } + catch (Exception ex) + { + return StatusCode(500, $"Error: {ex.Message}"); + } + } + + + // Fetch just the image links + private async Task> FetchImageLinksFromWebsiteAsync(string url) + { + var imageLinks = new List(); + var httpClient = new HttpClient(); + + // Set User-Agent to imitate a browser request + httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"); + + try + { + // Send the GET request with headers + var response = await httpClient.GetStringAsync(url); + + // Load the HTML into HtmlAgilityPack + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(response); + + // Select all
  • elements containing the images you want + var liNodes = htmlDocument.DocumentNode.SelectNodes("//li[contains(@class, 'flex items-center justify-center')]"); + + if (liNodes != null) + { + foreach (var liNode in liNodes) + { + // Find the tag within the
  • node + var imgNode = liNode.SelectSingleNode(".//img[contains(@class, 'absolute top-0 left-0 w-full h-full')]"); + + if (imgNode != null) + { + var imgSrc = imgNode.GetAttributeValue("src", ""); + + if (!string.IsNullOrEmpty(imgSrc)) + { + // Ensure absolute URL if necessary + if (!Uri.IsWellFormedUriString(imgSrc, UriKind.Absolute)) + { + var baseUri = new Uri(url); + imgSrc = new Uri(baseUri, imgSrc).ToString(); + } + + imageLinks.Add(imgSrc); + } + } + } + } } catch (HttpRequestException ex) { - return StatusCode(500, $"Error downloading website: {ex.Message}"); + // Catch any network-related exceptions (403, 404, etc.) + Console.WriteLine($"Error: {ex.Message}"); + throw new Exception($"Error fetching images: {ex.Message}"); } + + return imageLinks; } + + + + // Fetch images and download + private async Task> FetchImagesFromWebsiteAsync(string url) + { + var images = new List(); + var httpClient = new HttpClient(); + var response = await httpClient.GetStringAsync(url); + + // Load the HTML into HtmlAgilityPack + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(response); + + // Select all elements with the specified class + var imgNodes = htmlDocument.DocumentNode.SelectNodes("//li//img"); + + if (imgNodes != null) + { + foreach (var imgNode in imgNodes) + { + var imgSrc = imgNode.GetAttributeValue("src", ""); + if (!string.IsNullOrEmpty(imgSrc)) + { + // Ensure absolute URL if necessary + if (!Uri.IsWellFormedUriString(imgSrc, UriKind.Absolute)) + { + var baseUri = new Uri(url); + imgSrc = new Uri(baseUri, imgSrc).ToString(); + } + images.Add(imgSrc); + } + } + } + + return images; + } + } -public class WebsiteRequest +public class UrlRequest { public string Url { get; set; } } diff --git a/Aberwyn/Data/BudgetService.cs b/Aberwyn/Data/BudgetService.cs index 4677935..e762cfd 100644 --- a/Aberwyn/Data/BudgetService.cs +++ b/Aberwyn/Data/BudgetService.cs @@ -1,23 +1,31 @@ using MySql.Data.MySqlClient; using System.Collections.Generic; using Aberwyn.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; // Add this namespace namespace Aberwyn.Data { public class BudgetService { private readonly IConfiguration _configuration; + private readonly IHostEnvironment _env; // Add this field - public BudgetService(IConfiguration configuration) + public BudgetService(IConfiguration configuration, IHostEnvironment env) // Update constructor { _configuration = configuration; + _env = env; // Initialize the environment field } public MySqlConnection GetConnection() { - var connectionString = _configuration.GetConnectionString("DefaultConnection"); + var connectionString = _env.IsDevelopment() // Use the injected environment variable + ? _configuration.GetConnectionString("DefaultConnection") + : _configuration.GetConnectionString("ProductionConnection"); + return new MySqlConnection(connectionString); } + public bool UpdateBudgetItem(BudgetItem item) { using (var connection = GetConnection()) @@ -25,9 +33,9 @@ namespace Aberwyn.Data connection.Open(); string query = @" - UPDATE tblBudgetItems - SET Name = @name, Amount = @amount - WHERE idtblBudgetItems = @id"; + UPDATE tblBudgetItems + SET Name = @name, Amount = @amount + WHERE idtblBudgetItems = @id"; using (var cmd = new MySqlCommand(query, connection)) { @@ -48,17 +56,17 @@ namespace Aberwyn.Data connection.Open(); string query = @" - SELECT - b.idtblBudgetItems AS id, - b.Name AS item_name, - b.Amount AS amount, - c1.Name AS category, - b.Month, - b.Year, - b.Description AS description - FROM tblBudgetItems b - LEFT JOIN tblCategories c1 ON b.Category = c1.idtblCategories - WHERE b.Month = @month AND b.Year = @year"; + SELECT + b.idtblBudgetItems AS id, + b.Name AS item_name, + b.Amount AS amount, + c1.Name AS category, + b.Month, + b.Year, + b.Description AS description + FROM tblBudgetItems b + LEFT JOIN tblCategories c1 ON b.Category = c1.idtblCategories + WHERE b.Month = @month AND b.Year = @year"; using (var cmd = new MySqlCommand(query, connection)) { @@ -96,8 +104,8 @@ namespace Aberwyn.Data connection.Open(); string query = @" - INSERT INTO tblBudgetItems (Name, Amount, Category, Month, Year) - VALUES (@name, @amount, @category, @month, @year)"; + INSERT INTO tblBudgetItems (Name, Amount, Category, Month, Year) + VALUES (@name, @amount, @category, @month, @year)"; using (var cmd = new MySqlCommand(query, connection)) { @@ -111,7 +119,6 @@ namespace Aberwyn.Data } } - // New method to fetch all categories public List GetCategories() { @@ -138,5 +145,4 @@ namespace Aberwyn.Data return categories; } } - } diff --git a/Aberwyn/Data/MenuService.cs b/Aberwyn/Data/MenuService.cs new file mode 100644 index 0000000..dc0ad90 --- /dev/null +++ b/Aberwyn/Data/MenuService.cs @@ -0,0 +1,193 @@ +using MySql.Data.MySqlClient; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Aberwyn.Models; + +namespace Aberwyn.Data +{ + public class MenuService + { + private readonly IConfiguration _configuration; + private readonly IHostEnvironment _env; + + public MenuService(IConfiguration configuration, IHostEnvironment env) + { + _configuration = configuration; + _env = env; + } + + private MySqlConnection GetConnection() + { + var connectionString = _env.IsDevelopment() + ? _configuration.GetConnectionString("DefaultConnection") + : _configuration.GetConnectionString("ProductionConnection"); + + return new MySqlConnection(connectionString); + } + + public List GetWeeklyMenu(int weekNumber, int year) + { + var weeklyMenu = new List(); + using (var connection = GetConnection()) + { + 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"; + + using (var cmd = new MySqlCommand(query, connection)) + { + cmd.Parameters.AddWithValue("@weekNumber", weekNumber); + cmd.Parameters.AddWithValue("@year", year); + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + weeklyMenu.Add(new WeeklyMenu + { + 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"), + 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"), + + }); + } + } + } + } + return weeklyMenu; + } + + public List GetMeals() + { + var meals = new List(); + using (var connection = GetConnection()) + { + connection.Open(); + string query = "SELECT Id, Name FROM Meals"; + using (var cmd = new MySqlCommand(query, connection)) + { + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + meals.Add(new Meal + { + Id = reader.GetInt32("Id"), + Name = reader.GetString("Name") + }); + } + } + } + } + return meals; + } + + public void UpdateWeeklyMenu(MenuViewModel menuData) + { + if (menuData == null || menuData.WeeklyMenus == null) + throw new ArgumentNullException(nameof(menuData), "Menu data or weekly menus cannot be null."); + + using (var connection = GetConnection()) + { + connection.Open(); + using (var transaction = connection.BeginTransaction()) + { + try + { + // Loop over each WeeklyMenu in the WeeklyMenus list + foreach (var weeklyMenu in menuData.WeeklyMenus) + { + int dayOfWeek = weeklyMenu.DayOfWeek; + var menu = weeklyMenu; + + // SQL query to update the existing records + string query = @" + UPDATE WeeklyMenu + SET + DinnerMealId = @dinnerMealId, + LunchMealId = @lunchMealId, + Cook = @cook + WHERE DayOfWeek = @dayOfWeek + AND WeekNumber = @weekNumber + AND Year = @year;"; + + using (var cmd = new MySqlCommand(query, connection, transaction)) + { + cmd.Parameters.AddWithValue("@dayOfWeek", dayOfWeek); + cmd.Parameters.AddWithValue("@dinnerMealId", menu.DinnerMealId ?? (object)DBNull.Value); + cmd.Parameters.AddWithValue("@lunchMealId", menu.LunchMealId ?? (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(); + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + Console.WriteLine($"Error updating weekly menu: {ex.Message}"); + throw; + } + } + } + } + + public int AddMeal(Meal meal) + { + using (var connection = GetConnection()) + { + connection.Open(); + string query = @" + INSERT INTO Meals (Name, Description, ProteinType, CarbType, RecipeUrl, CreatedAt) + VALUES (@name, @description, @proteinType, @carbType, @recipeUrl, NOW()); + SELECT LAST_INSERT_ID();"; + + using (var cmd = new MySqlCommand(query, connection)) + { + cmd.Parameters.AddWithValue("@name", meal.Name); + cmd.Parameters.AddWithValue("@description", string.IsNullOrWhiteSpace(meal.Description) ? (object)DBNull.Value : meal.Description); + cmd.Parameters.AddWithValue("@proteinType", string.IsNullOrWhiteSpace(meal.ProteinType) ? (object)DBNull.Value : meal.ProteinType); + cmd.Parameters.AddWithValue("@carbType", string.IsNullOrWhiteSpace(meal.CarbType) ? (object)DBNull.Value : meal.CarbType); + cmd.Parameters.AddWithValue("@recipeUrl", string.IsNullOrWhiteSpace(meal.RecipeUrl) ? (object)DBNull.Value : meal.RecipeUrl); + + var result = cmd.ExecuteScalar(); + return result != null ? Convert.ToInt32(result) : 0; + } + } + } + + + + private int GetDayOfWeek(string day) + { + return day switch + { + "Monday" => 1, + "Tuesday" => 2, + "Wednesday" => 3, + "Thursday" => 4, + "Friday" => 5, + "Saturday" => 6, + "Sunday" => 7, + _ => throw new System.ArgumentException("Invalid day name") + }; + } + } +} + +// Models for Meals and WeeklyMenu diff --git a/Aberwyn/Dockerfile b/Aberwyn/Dockerfile index c67a604..766d99a 100644 --- a/Aberwyn/Dockerfile +++ b/Aberwyn/Dockerfile @@ -1,27 +1,33 @@ # Base image for runtime FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app -EXPOSE 80 -EXPOSE 443 + +# 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.csproj", "./"] -RUN dotnet restore "Aberwyn.csproj" +COPY Aberwyn.csproj ./ +RUN dotnet restore # Copy everything else and build the app COPY . . -RUN dotnet build "Aberwyn.csproj" -c Release -o /app/build +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"] diff --git a/Aberwyn/Models/BudgetItem.cs b/Aberwyn/Models/BudgetItem.cs index ac55af8..34d50dc 100644 --- a/Aberwyn/Models/BudgetItem.cs +++ b/Aberwyn/Models/BudgetItem.cs @@ -8,12 +8,15 @@ public class BudgetItem { public int ID { get; set; } - public string Name { get; set; } + + // Option 1: Initialize with default values + public string Name { get; set; } = string.Empty; // Initialize to an empty string public decimal Amount { get; set; } - public string Category { get; set; } - public string SubCategory { get; set; } + public string Category { get; set; } = string.Empty; // Initialize to an empty string + public string SubCategory { get; set; } = string.Empty; // Initialize to an empty string public int Month { get; set; } public int Year { get; set; } - public string Description { get; set; } + public string Description { get; set; } = string.Empty; // Initialize to an empty string } + } diff --git a/Aberwyn/Models/MenuViewModel.cs b/Aberwyn/Models/MenuViewModel.cs new file mode 100644 index 0000000..c911363 --- /dev/null +++ b/Aberwyn/Models/MenuViewModel.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Aberwyn.Data; +using Aberwyn.Models; + +namespace Aberwyn.Models +{ + public class MenuViewModel + { + public List Meals { get; set; } // List of all available meals + public List WeeklyMenus { get; set; } // List of weekly menu entries + public int WeekNumber { get; set; } // Week number for the menu + public int Year { get; set; } // Year for the menu + } + public class WeeklyMenu + { + public int Id { get; set; } + public int DayOfWeek { get; set; } + public int? DinnerMealId { get; set; } + public int? LunchMealId { get; set; } + public string Cook { get; set; } + public int WeekNumber { get; set; } + public int Year { get; set; } + public string DinnerMealName { get; set; } + public string LunchMealName { get; set; } + } + + public class Meal + { + public int Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string ProteinType { get; set; } + public string CarbType { get; set; } + public string RecipeUrl { get; set; } + public DateTime CreatedAt { get; set; } + } +} + diff --git a/Aberwyn/Program.cs b/Aberwyn/Program.cs index 6ffd945..b9e26af 100644 --- a/Aberwyn/Program.cs +++ b/Aberwyn/Program.cs @@ -14,8 +14,11 @@ builder.Services.AddHttpClient(); // Register HttpClient builder.Services.AddDbContext(options => options.UseMySQL(builder.Configuration.GetConnectionString("BudgetDb"))); + // Register your BudgetService with a scoped lifetime builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); @@ -26,7 +29,7 @@ if (!app.Environment.IsDevelopment()) app.UseHsts(); } -app.UseHttpsRedirection(); +//app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); diff --git a/Aberwyn/ViewComponents/RightSidebarViewComponents.cs b/Aberwyn/ViewComponents/RightSidebarViewComponents.cs new file mode 100644 index 0000000..f2000a4 --- /dev/null +++ b/Aberwyn/ViewComponents/RightSidebarViewComponents.cs @@ -0,0 +1,31 @@ +using Aberwyn.Data; +using Aberwyn.Models; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Aberwyn.ViewComponents +{ + public class RightSidebarViewComponent : ViewComponent + { + private readonly BudgetService _budgetService; + + public RightSidebarViewComponent(BudgetService budgetService) + { + _budgetService = budgetService; + } + + public async Task InvokeAsync() + { + // Get the current month and year + int currentMonth = DateTime.Now.Month; + int currentYear = DateTime.Now.Year; + + // Fetch the budget items using the service + var budgetItems = _budgetService.GetBudgetItems(currentMonth, currentYear); + + return View(budgetItems); + } + } +} diff --git a/Aberwyn/Views/Home/Budget.cshtml b/Aberwyn/Views/Home/Budget.cshtml index 84ed2b4..e781fc4 100644 --- a/Aberwyn/Views/Home/Budget.cshtml +++ b/Aberwyn/Views/Home/Budget.cshtml @@ -1,7 +1,7 @@ @model Aberwyn.Models.BudgetModel @{ - Layout = "_Layout"; // Assuming you have a layout file named _Layout.cshtml + Layout = "_Layout"; } @@ -10,32 +10,29 @@ Budget Overview - + +

    Budget Overview

    -
    - - - - - - - -
    +
    + +
    + + +
    +
    {{ category }} - Total: {{ sumForCategory(category) }} + Total: {{ sumForCategory(category) }}
    @@ -49,7 +46,7 @@
    -
    +
    +
    +
    @@ -65,7 +62,7 @@ $scope.selectedMonth = (today.getMonth() + 1).toString(); $scope.selectedYear = today.getFullYear(); $scope.categories = []; - $scope.budgetItems = @Html.Raw(Json.Serialize(Model.BudgetItems)); // Initialize budgetItems with server-side data + $scope.budgetItems = @Html.Raw(Json.Serialize(Model.BudgetItems)); // Initialize with server-side data $scope.loadCategories = function () { $http.get('/api/budgetapi/categories').then(response => { @@ -73,6 +70,7 @@ }).catch(error => console.error("Error fetching categories:", error)); }; + // Automatically filter when month or year changes $scope.filterBudget = function () { $http.get('/api/budgetapi/items', { params: { month: $scope.selectedMonth, year: $scope.selectedYear } @@ -108,8 +106,62 @@ $scope.budgetItems.push(newItem); }; + // Dropdown toggle functions + $scope.isMonthDropdownVisible = false; + $scope.isYearDropdownVisible = false; + + $scope.toggleMonthDropdown = function () { + $scope.isMonthDropdownVisible = !$scope.isMonthDropdownVisible; + $scope.isYearDropdownVisible = false; + }; + + $scope.toggleYearDropdown = function () { + $scope.isYearDropdownVisible = !$scope.isYearDropdownVisible; + $scope.isMonthDropdownVisible = false; + }; + + $scope.selectMonth = function (month) { + $scope.selectedMonth = $scope.months.indexOf(month) + 1; + $scope.isMonthDropdownVisible = false; + $scope.filterBudget(); // Automatically filter after month selection + }; + + $scope.selectYear = function (year) { + $scope.selectedYear = year; + $scope.isYearDropdownVisible = false; + $scope.filterBudget(); // Automatically filter after year selection + }; + + $scope.selectedMonthName = function () { + return $scope.months[$scope.selectedMonth - 1]; + }; + + // Initial load $scope.loadCategories(); $scope.filterBudget(); + + // Month and year options as before + $scope.monthOptions = { + 1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", + 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December" + }; + + // Toggle display of date picker + $scope.toggleDatePicker = function ($event) { + $event.stopPropagation(); // Prevents the click from closing the dropdown + $scope.isDatePickerVisible = !$scope.isDatePickerVisible; + }; + + // Update selection and close dropdown + $scope.updateMonthYear = function () { + $scope.isDatePickerVisible = false; // Close after selection + $scope.filterBudget(); // Trigger filtering immediately + }; + + // Close the date picker when clicking outside + document.addEventListener('click', function () { + $scope.$apply(() => $scope.isDatePickerVisible = false); + }); }); diff --git a/Aberwyn/Views/Home/Menu.cshtml b/Aberwyn/Views/Home/Menu.cshtml new file mode 100644 index 0000000..b8e87a0 --- /dev/null +++ b/Aberwyn/Views/Home/Menu.cshtml @@ -0,0 +1,98 @@ +@model Aberwyn.Models.MenuViewModel + +@{ + Layout = "_Layout"; +} + + + + + + Meal Menu Overview + + + + + + + +
    +

    Meal Menu Overview

    + +
    + + Week {{ selectedWeek }} - {{ selectedYear }} + +
    + +
    + + +
    + +
    +
    +
    {{ day }}
    + +
    +
    +
    + Breakfast: {{ menu[day].breakfastMealName }} +
    +
    + Lunch: {{ menu[day].lunchMealName }} +
    +
    + Dinner: {{ menu[day].dinnerMealName }} +
    +
    +
    + Not Assigned +
    +
    + +
    +
    + Breakfast: + +
    +
    + Lunch: + +
    +
    + Dinner: + +
    +
    +
    + + +
    + +
    +

    Add a New Meal

    + + + + + + + +
    + +
    + + diff --git a/Aberwyn/Views/Home/RealEstate.cshtml b/Aberwyn/Views/Home/RealEstate.cshtml index 6531710..2f2082e 100644 --- a/Aberwyn/Views/Home/RealEstate.cshtml +++ b/Aberwyn/Views/Home/RealEstate.cshtml @@ -4,14 +4,28 @@

    Real Estate Website Downloader

    -
    - - - -
    +
    +
    + + + +
    -
    -

    {{ downloadStatus }}

    +
    +

    {{ fetchStatus }}

    +
    + + +
    +

    Fetched Images:

    +
    +
    + Preview Image +

    {{ imgSrc }}

    + +
    +
    +
    @@ -19,20 +33,63 @@ angular.module('realEstateApp', []) .controller('RealEstateController', function ($scope, $http) { $scope.websiteUrl = ''; - $scope.downloadStatus = ''; + $scope.fetchStatus = ''; + $scope.images = []; - $scope.downloadWebsite = function () { + // Fetch images from the backend + $scope.fetchImages = function () { if ($scope.websiteUrl) { $http.post('/api/realestate/download', { url: $scope.websiteUrl }) .then(function (response) { - $scope.downloadStatus = 'Download successful!'; - console.log(response.data); // Optional: Handle the downloaded data + $scope.fetchStatus = 'Images fetched successfully!'; + $scope.images = response.data.images; // Display the fetched images }) .catch(function (error) { - $scope.downloadStatus = 'Error downloading website.'; + $scope.fetchStatus = 'Error fetching images.'; console.error(error); }); } }; + + // Function to download the image when clicked + $scope.downloadImage = function (imgSrc) { + var link = document.createElement('a'); + link.href = imgSrc; + link.download = imgSrc.split('/').pop(); // Extract filename from URL + link.click(); + }; }); + + diff --git a/Aberwyn/Views/Shared/Components/RightSidebar/Default.cshtml b/Aberwyn/Views/Shared/Components/RightSidebar/Default.cshtml new file mode 100644 index 0000000..99ab146 --- /dev/null +++ b/Aberwyn/Views/Shared/Components/RightSidebar/Default.cshtml @@ -0,0 +1,116 @@ +@model IEnumerable + + + + diff --git a/Aberwyn/Views/Shared/_Layout.cshtml b/Aberwyn/Views/Shared/_Layout.cshtml index eb95389..b9f27a0 100644 --- a/Aberwyn/Views/Shared/_Layout.cshtml +++ b/Aberwyn/Views/Shared/_Layout.cshtml @@ -1,14 +1,11 @@ -@using Aberwyn.Models -@model Aberwyn.Models.BudgetModel - - + - Your Page Title + LEWEL - Dashboard
    @@ -22,9 +19,8 @@
    -
    -
    - -@await RenderSectionAsync("Scripts", required: false) diff --git a/Aberwyn/appsettings.json b/Aberwyn/appsettings.json index ec93c55..c97db5f 100644 --- a/Aberwyn/appsettings.json +++ b/Aberwyn/appsettings.json @@ -7,6 +7,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;" + "DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;", + "ProductionConnection": "Server=172.18.0.2;Database=Nevyn;Uid=root;Pwd=3edc4RFV;" } } diff --git a/Aberwyn/wwwroot/css/budget.css b/Aberwyn/wwwroot/css/budget.css new file mode 100644 index 0000000..4824ce0 --- /dev/null +++ b/Aberwyn/wwwroot/css/budget.css @@ -0,0 +1,81 @@ +/* Budget Container */ +.budget-container { + display: flex; + flex-wrap: wrap; + gap: 10px; /* Reduced gap for tighter layout */ + padding: 0; /* Removed padding */ + margin: 0; /* Removed margin */ +} + +/* Category Block */ +.category-block { + flex: 1 1 300px; + max-width: 230px; + min-width: 180px; + margin: 0; /* Removed margin */ +} + +.category-header { + display: flex; + justify-content: space-between; /* Aligns the category name and total */ + align-items: center; /* Center vertically */ + padding: 5px; /* Retain padding */ + background-color: #4A6572; /* Muted blue-gray */ + color: #ffffff; + border-radius: 8px; + margin-bottom: 10px; + font-weight: bold; + max-width: 100%; /* Ensures the header doesn't exceed the width of its container */ + box-sizing: border-box; /* Include padding in width calculation */ +} + +.total { + font-weight: bold; + color: #ffffff; /* White for visibility */ + background-color: rgba(74, 101, 114, 0.7); /* Semi-transparent muted blue-gray */ + padding: 5px 10px; /* Add some padding for spacing */ + border-radius: 5px; /* Rounded corners */ +} + +/* Items Layout */ +.items-wrapper { + display: flex; + min-width: 200px; + flex-direction: column; + padding: 0; /* Removed padding */ + margin: 0; /* Removed margin */ +} + +/* Item Styles */ +.item { + display: flex; + align-items: center; + margin-bottom: 5px; /* Space between items */ + gap: 5px; /* Reduced gap between item components */ +} + +/* Input Styles */ +.item-name input { + flex: 1; /* Compact size for amount field */ + max-width: 20ch; /* Allow up to 7 characters */ + padding: 6px; /* Slightly reduced padding */ + border: none; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + background-color: #f9f9f9; + transition: background-color 0.3s; +} + +.item-amount input { + flex: 1; /* Compact size for amount field */ + max-width: 7ch; /* Allow up to 7 characters */ + padding: 6px; /* Slightly reduced padding */ + border: none; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + background-color: #f9f9f9; + transition: background-color 0.3s; +} + +/* Focus Effect */ +.item-name input:focus diff --git a/Aberwyn/wwwroot/css/meal-menu.css b/Aberwyn/wwwroot/css/meal-menu.css new file mode 100644 index 0000000..d263455 --- /dev/null +++ b/Aberwyn/wwwroot/css/meal-menu.css @@ -0,0 +1,210 @@ +/* Meal Menu Page Styles */ +.meal-menu-page { + background-color: #2A3A48; /* Dark background to match theme */ + padding: 30px 60px; /* Add more padding to the sides */ + border-radius: 12px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + max-width: 900px; /* Adjust width to provide more space */ + margin: 0 auto; + color: #B0BEC5; /* Light gray text */ +} + + .meal-menu-page h1 { + font-size: 2rem; + text-align: center; + color: #FFFFFF; + margin-bottom: 20px; + } + +/* Date Picker */ +.date-picker { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 30px; + gap: 20px; +} + + .date-picker span { + font-size: 1.2rem; + color: #B0BEC5; + } + + .date-picker button { + background-color: #4A6572; + color: #ffffff; + padding: 12px 25px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s; + } + + .date-picker button:hover { + background-color: #3A4E62; + } + +/* Mode Toggle */ +.mode-toggle { + text-align: center; + margin-bottom: 30px; +} + + .mode-toggle button { + background-color: #4CAF50; + color: #ffffff; + padding: 12px 20px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s; + } + + .mode-toggle button:hover { + background-color: #45a049; + } + +/* Meal Menu Container - Vertical Layout */ +.meal-menu-container { + display: flex; + flex-direction: column; /* Align the days vertically */ + gap: 20px; + justify-content: flex-start; +} + +/* Day Block Styling */ +.day-block { + background-color: #1F2C3C; /* Darker background for day blocks */ + padding: 20px; + border-radius: 10px; + width: 100%; + text-align: center; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +/* Day Header with Line */ +.day-header { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 15px; + color: #FFEB3B; /* Brighter yellow for the text */ + position: relative; /* Allows absolute positioning for the line */ +} + + .day-header::after { + content: ""; + position: absolute; + bottom: 0; /* Place it at the bottom of the header */ + left: 0; + width: 100%; /* Make the line span across the header */ + height: 2px; /* Thickness of the line */ + background-color: #FFEB3B; /* Yellow line for contrast */ + border-radius: 2px; /* Rounded corners for the line */ + } + +/* Meal Selection */ +.meal-selection { + font-size: 1rem; + color: #B0BEC5; + margin-bottom: 10px; +} + + .meal-selection strong { + color: #FFFFFF; + } + + .meal-selection span { + color: #FFEB3B; /* Highlight the meal names */ + } + +/* Edit Mode Styling */ +.edit-mode select { + background-color: #444; + color: #fff; + border: none; + padding: 10px; + border-radius: 6px; + width: 100%; + font-size: 1rem; + margin-top: 5px; + cursor: pointer; +} + + .edit-mode select:focus { + outline: none; + box-shadow: 0 0 8px rgba(66, 133, 244, 0.8); + } + +/* Save Button */ +.save-menu-button { + display: block; + background-color: #FF9800; + color: #fff; + padding: 12px 25px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + width: 200px; + margin: 30px auto; + text-align: center; + transition: background-color 0.3s; +} + + .save-menu-button:hover { + background-color: #F57C00; + } + +/* New Meal Form Styling */ +.new-meal-form { + display: flex; + flex-direction: column; /* Stack input fields vertically */ + gap: 15px; /* Space between fields */ + padding: 20px; + background-color: #1F2C3C; /* Dark background to match theme */ + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); + max-width: 400px; /* Control the width of the form */ + margin: 0 auto; +} + + /* Input Fields */ + .new-meal-form input, + .new-meal-form select, + .new-meal-form textarea { + background-color: #444; /* Dark background for inputs */ + color: #fff; /* White text */ + border: none; + padding: 12px 15px; + border-radius: 6px; + font-size: 1rem; + width: 100%; /* Ensure inputs take full width */ + } + + /* Focused Input Fields */ + .new-meal-form input:focus, + .new-meal-form select:focus, + .new-meal-form textarea:focus { + outline: none; + box-shadow: 0 0 8px rgba(66, 133, 244, 0.8); /* Blue focus highlight */ + } + + /* Submit Button */ + .new-meal-form button { + background-color: #FF9800; /* Button color */ + color: #fff; + padding: 12px 20px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s; + width: 100%; /* Button takes full width */ + } + + /* Button Hover */ + .new-meal-form button:hover { + background-color: #F57C00; + } diff --git a/Aberwyn/wwwroot/css/site.css b/Aberwyn/wwwroot/css/site.css index 7b4524b..de35bab 100644 --- a/Aberwyn/wwwroot/css/site.css +++ b/Aberwyn/wwwroot/css/site.css @@ -1,7 +1,7 @@ /* Base Styles */ body { - background-color: #1F2C3C; - color: #4b004b; + background-color: #1F2C3C; /* Dark background */ + color: #B0BEC5; /* Light Gray text for better readability */ font-family: 'Roboto', sans-serif; margin: 0; padding: 20px; /* Keep padding for general layout */ @@ -10,7 +10,7 @@ body { /* Container for Centering and Max Width */ .budget-page { width: 100%; /* Make the page full width */ - max-width: 1900px; /* You can adjust or remove this */ + max-width: 1900px; /* Adjust or remove this */ margin: 0 auto; /* Centering */ padding: 0; /* Removed extra padding for budget page */ } @@ -25,7 +25,7 @@ body { } .left-sidebar, .sidebar-budget-right { - background-color: #f4f4f4; + background-color: #f9f9f9; /* Softer background */ border-radius: 12px; padding: 10px; /* Adjusted padding for sidebars */ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); @@ -33,13 +33,13 @@ body { .main-content { padding: 10px; - background-color: #f4f4f4; /* Light gray instead of white */ + background-color: #f4f4f4; /* Light gray */ border-radius: 12px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .nav-link { - color: #4b004b; + color: #3A4E62; /* Muted blue-gray for consistency */ } .nav-link:hover { @@ -52,114 +52,6 @@ body { } } - -/* Budget Container */ -.budget-container { - display: flex; - flex-wrap: wrap; - gap: 10px; /* Reduced gap for tighter layout */ - padding: 0; /* Removed padding */ - margin: 0; /* Removed margin */ -} - -/* Category Block */ -.category-block { - flex: 1 1 300px; - max-width: 230px; - min-width: 180px; - margin: 0; /* Removed margin */ -} - -.category-header { - display: flex; - justify-content: space-between; /* Aligns the category name and total */ - align-items: center; /* Center vertically */ - padding: 5px; /* Retain padding */ - background-color: #6a0dad; /* Adjust color as needed */ - color: #ffffff; - border-radius: 8px; - margin-bottom: 10px; - font-weight: bold; - max-width: 100%; /* Ensures the header doesn't exceed the width of its container */ - box-sizing: border-box; /* Include padding in width calculation */ -} - -.total { - font-weight: bold; - color: #ffffff; /* Keep white for visibility */ - background-color: rgba(106, 13, 173, 0.7); /* Add a semi-transparent background for contrast */ - padding: 5px 10px; /* Add some padding for spacing */ - border-radius: 5px; /* Rounded corners */ -} - -/* Items Layout */ -.items-wrapper { - display: flex; - min-width: 200px; - flex-direction: column; - padding: 0; /* Removed padding */ - margin: 0; /* Removed margin */ -} - -/* Item Styles */ -.item { - display: flex; - align-items: center; - margin-bottom: 5px; /* Space between items */ - gap: 5px; /* Reduced gap between item components */ -} - -/* Input Styles */ -.item-name input { - flex: 1; /* Compact size for amount field */ - max-width: 20ch; /* Allow up to 7 characters */ - padding: 6px; /* Slightly reduced padding */ - border: none; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); - background-color: #f9f9f9; - transition: background-color 0.3s; -} - -.item-amount input { - flex: 1; /* Compact size for amount field */ - max-width: 7ch; /* Allow up to 7 characters */ - padding: 6px; /* Slightly reduced padding */ - border: none; - border-radius: 5px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); - background-color: #f9f9f9; - transition: background-color 0.3s; -} - - /* Focus Effect */ - .item-name input:focus, - .item-amount input:focus { - background-color: #e6e6e6; - } - -/* Add Item Button */ -.add-item { - display: flex; /* Use flex to center the "+" */ - align-items: center; /* Center the "+" vertically */ - justify-content: center; /* Center the "+" horizontally */ - height: 25px; /* Adjusted height to match input fields */ - cursor: pointer; - color: #ffffff; /* White text color for contrast */ - background-color: #6a0dad; /* Use the same color as the header for consistency */ - margin-top: 5px; /* Reduced space above the button */ - border: none; /* No border */ - border-radius: 5px; /* Rounded corners */ - font-size: 15px; /* Adjusted font size for better fit */ - transition: background-color 0.3s, transform 0.3s; /* Transition for hover effect */ -} - - /* Hover Effect for Add Item Button */ - .add-item:hover { - background-color: #5a0c9a; /* Darker shade on hover */ - transform: scale(1.05); /* Slightly enlarge on hover for emphasis */ - } - .header-container { display: flex; justify-content: flex-start; /* Aligns the header to the left */ @@ -178,7 +70,7 @@ body { } .name { - color: #FFFFFF; /* White for names */ + color: #ffffff; /* White for names */ font-size: 0.4em; /* Adjust size as needed */ } @@ -214,7 +106,7 @@ body { .nav-link { text-decoration: none; /* Remove underline */ - color: #000; /* Link color */ + color: #3A4E62; /* Muted blue-gray for consistency */ display: flex; /* Use flexbox to align icon and text */ align-items: center; /* Center items vertically */ } @@ -224,34 +116,111 @@ body { font-size: 1.2em; /* Adjust icon size */ } - /* Sidebar Right Styles */ -.right-sidebar { - background-color: #f8f9fa; /* Use your existing background color */ - padding: 10px; /* Reduce padding for compactness */ - margin: 0; /* Remove any margins */ - border-radius: 8px; /* Optional: Add rounded corners */ - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Optional: Add shadow for depth */ +.sidebar-section { + margin-bottom: 5px; } -.sidebar-budget-header { - cursor: pointer; /* Indicate clickable header */ - font-weight: bold; /* Make header bold */ - margin: 0; /* Remove default margin */ - padding-bottom: 5px; /* Space below header */ -} - -.sidebar-budget-container { - margin-top: 10px; /* Space above budget items */ -} - -.sidebar-budget-item { +.sidebar-header { + cursor: pointer; + background-color: #4CAF50; /* Green */ + color: white; + padding: 8px; /* Slightly more padding */ + border-radius: 5px; display: flex; - justify-content: space-between; /* Space out name and amount */ - padding: 5px 0; /* Reduced padding for compactness */ - border-bottom: 1px solid #e0e0e0; /* Optional: Divider line */ + justify-content: space-between; + align-items: center; /* Center items vertically */ + font-size: 14px; + transition: background-color 0.3s; /* Smooth hover transition */ } - .sidebar-budget-item:last-child { - border-bottom: none; /* Remove border from last item */ - } \ No newline at end of file + .sidebar-header:hover { + background-color: #45a049; /* Darker green on hover */ + } + +.accordion-item { + border-top: 1px solid #ddd; /* Separator between items */ +} + +.accordion-header { + display: flex; /* Flexbox for header */ + justify-content: space-between; + align-items: center; /* Center vertically */ + padding: 8px; /* Padding for accordion header */ + cursor: pointer; + background-color: #f1f1f1; /* Light background for header */ + border-radius: 4px; + transition: background-color 0.3s; /* Smooth hover transition */ +} + + .accordion-header:hover { + background-color: #e0e0e0; /* Slightly darker background on hover */ + } + +.accordion-content { + display: none; /* Hidden by default */ + background-color: #fff; /* White background for content */ + padding: 5px; /* Padding for content */ + border-radius: 4px; /* Rounded corners */ +} + +.item-name { + font-weight: normal; /* Normal weight for item names */ + font-size: 12px; /* Smaller font size */ +} + +.item-amount { + color: #333; + font-size: 12px; /* Smaller font size for amounts */ +} + +.date-picker { + position: relative; +} + +.date-picker-toggle { + background-color: #4A6572; /* Muted blue-gray */ + color: #ffffff; + padding: 10px 15px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} + + .date-picker-toggle:hover { + background-color: #3A4E62; /* Darker blue-gray on hover */ + } + +.date-picker-dropdown { + position: absolute; + top: 100%; + left: 0; + background-color: #333; + color: #ffffff; + padding: 10px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + margin-top: 8px; + z-index: 1000; + display: flex; + gap: 10px; + width: max-content; +} + + .date-picker-dropdown select { + background-color: #444; + color: #ffffff; + border: none; + padding: 8px; + border-radius: 4px; + font-size: 14px; + appearance: none; + cursor: pointer; + transition: background-color 0.3s; + } + + .date-picker-dropdown select:hover { + background-color: #555; + } diff --git a/Aberwyn/wwwroot/js/menu.js b/Aberwyn/wwwroot/js/menu.js new file mode 100644 index 0000000..93f48e5 --- /dev/null +++ b/Aberwyn/wwwroot/js/menu.js @@ -0,0 +1,117 @@ +angular.module('mealMenuApp', []) + .controller('MealMenuController', function ($scope, $http) { + $scope.isEditing = false; + $scope.toggleEditMode = function () { + $scope.isEditing = !$scope.isEditing; + }; + + $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 = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; + + $scope.loadMeals = function () { + $http.get('/api/mealMenuApi/getMeals') + .then(response => { + $scope.meals = response.data; + }) + .catch(error => console.error("Error fetching meals:", error)); + }; + + $scope.loadMenu = function () { + $http.get('/api/mealMenuApi/menu', { + params: { weekNumber: $scope.selectedWeek, year: $scope.selectedYear } + }).then(response => { + $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); + } + }); + }).catch(error => console.error("Error fetching weekly menu:", error)); + }; + + 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.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.cancelNewMeal = function () { + $scope.showNewMealForm = false; + $scope.newMeal = {}; // Reset new meal data when canceled + }; + + 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--; + $scope.selectedWeek = 52; + } else { + $scope.selectedWeek--; + } + $scope.loadMenu(); + }; + + $scope.goToNextWeek = function () { + if ($scope.selectedWeek === 52) { + $scope.selectedYear++; + $scope.selectedWeek = 1; + } else { + $scope.selectedWeek++; + } + $scope.loadMenu(); + }; + + $scope.loadMeals(); + $scope.loadMenu(); + });