New stuff
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
|
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
|
||||||
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.12">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.12">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -20,4 +21,9 @@
|
|||||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
|
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="NewFolder1\" />
|
||||||
|
<Folder Include="NewFolder\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -18,18 +18,33 @@ namespace Aberwyn.Controllers
|
|||||||
|
|
||||||
[HttpGet("items")]
|
[HttpGet("items")]
|
||||||
public IActionResult GetBudgetItems([FromQuery] int month, [FromQuery] int year)
|
public IActionResult GetBudgetItems([FromQuery] int month, [FromQuery] int year)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var items = _budgetService.GetBudgetItems(month, year);
|
var items = _budgetService.GetBudgetItems(month, year);
|
||||||
return Ok(items);
|
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")]
|
[HttpGet("categories")]
|
||||||
public IActionResult GetCategories()
|
public IActionResult GetCategories()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var categories = _budgetService.GetCategories();
|
var categories = _budgetService.GetCategories();
|
||||||
return Ok(categories);
|
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")]
|
[HttpPut("items")]
|
||||||
public IActionResult UpdateBudgetItem([FromBody] BudgetItem item)
|
public IActionResult UpdateBudgetItem([FromBody] BudgetItem item)
|
||||||
@@ -39,16 +54,21 @@ namespace Aberwyn.Controllers
|
|||||||
return BadRequest("Invalid budget item data.");
|
return BadRequest("Invalid budget item data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming you have a method in your BudgetService to update an item
|
try
|
||||||
|
{
|
||||||
var result = _budgetService.UpdateBudgetItem(item);
|
var result = _budgetService.UpdateBudgetItem(item);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
return Ok("Item updated successfully.");
|
return Ok("Item updated successfully.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusCode(500, "Error updating item.");
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("items")]
|
[HttpPost("items")]
|
||||||
public IActionResult AddBudgetItem([FromBody] BudgetItem item)
|
public IActionResult AddBudgetItem([FromBody] BudgetItem item)
|
||||||
@@ -58,17 +78,20 @@ namespace Aberwyn.Controllers
|
|||||||
return BadRequest("Invalid budget item data.");
|
return BadRequest("Invalid budget item data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming you have a method in your BudgetService to add an item
|
try
|
||||||
|
{
|
||||||
var result = _budgetService.AddBudgetItem(item);
|
var result = _budgetService.AddBudgetItem(item);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
return CreatedAtAction(nameof(GetBudgetItems), new { id = item.ID }, item);
|
return CreatedAtAction(nameof(GetBudgetItems), new { id = item.ID }, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusCode(500, "Error adding 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ namespace Aberwyn.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ILogger<HomeController> _logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
private readonly BudgetService _budgetService;
|
private readonly BudgetService _budgetService;
|
||||||
|
private readonly MenuService _menuService;
|
||||||
|
|
||||||
|
|
||||||
// Constructor to inject dependencies
|
// Constructor to inject dependencies
|
||||||
public HomeController(ILogger<HomeController> logger, BudgetService budgetService)
|
public HomeController(ILogger<HomeController> logger, BudgetService budgetService, MenuService menuService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_budgetService = budgetService;
|
_budgetService = budgetService;
|
||||||
|
_menuService = menuService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
@@ -34,6 +37,31 @@ namespace Aberwyn.Controllers
|
|||||||
return View();
|
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
|
// Optimized Budget Action to fetch filtered data directly from the database
|
||||||
public IActionResult Budget(string month, int? year)
|
public IActionResult Budget(string month, int? year)
|
||||||
{
|
{
|
||||||
|
|||||||
81
Aberwyn/Controllers/MealMenuApiController.cs
Normal file
81
Aberwyn/Controllers/MealMenuApiController.cs
Normal file
@@ -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<WeeklyMenu>()); // 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<Meal>()); // 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class RealEstateApiController : ControllerBase
|
public class RealEstateController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient;
|
// Step 1: Find image links without downloading
|
||||||
|
[HttpPost("findImages")]
|
||||||
public RealEstateApiController(HttpClient httpClient)
|
public async Task<IActionResult> FindImages([FromBody] UrlRequest request)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
if (string.IsNullOrEmpty(request.Url))
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("download")]
|
|
||||||
public async Task<IActionResult> DownloadWebsite([FromBody] WebsiteRequest request)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(request.Url))
|
return BadRequest("URL is required.");
|
||||||
{
|
|
||||||
return BadRequest("URL cannot be empty.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(request.Url);
|
var imageLinks = await FetchImageLinksFromWebsiteAsync(request.Url);
|
||||||
response.EnsureSuccessStatusCode();
|
return Ok(new { images = imageLinks });
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, $"Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optional: Save content to a file
|
// Step 2: Download images when user chooses to do so
|
||||||
System.IO.File.WriteAllText("DownloadedWebsite.html", content);
|
[HttpPost("download")]
|
||||||
|
public async Task<IActionResult> 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<List<string>> FetchImageLinksFromWebsiteAsync(string url)
|
||||||
|
{
|
||||||
|
var imageLinks = new List<string>();
|
||||||
|
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 <li> 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 <img> tag within the <li> 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)
|
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<List<string>> FetchImagesFromWebsiteAsync(string url)
|
||||||
|
{
|
||||||
|
var images = new List<string>();
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var response = await httpClient.GetStringAsync(url);
|
||||||
|
|
||||||
|
// Load the HTML into HtmlAgilityPack
|
||||||
|
var htmlDocument = new HtmlDocument();
|
||||||
|
htmlDocument.LoadHtml(response);
|
||||||
|
|
||||||
|
// Select all <img> 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WebsiteRequest
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UrlRequest
|
||||||
{
|
{
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Aberwyn.Models;
|
using Aberwyn.Models;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting; // Add this namespace
|
||||||
|
|
||||||
namespace Aberwyn.Data
|
namespace Aberwyn.Data
|
||||||
{
|
{
|
||||||
public class BudgetService
|
public class BudgetService
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _configuration;
|
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;
|
_configuration = configuration;
|
||||||
|
_env = env; // Initialize the environment field
|
||||||
}
|
}
|
||||||
|
|
||||||
public MySqlConnection GetConnection()
|
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);
|
return new MySqlConnection(connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateBudgetItem(BudgetItem item)
|
public bool UpdateBudgetItem(BudgetItem item)
|
||||||
{
|
{
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
@@ -111,7 +119,6 @@ namespace Aberwyn.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// New method to fetch all categories
|
// New method to fetch all categories
|
||||||
public List<string> GetCategories()
|
public List<string> GetCategories()
|
||||||
{
|
{
|
||||||
@@ -138,5 +145,4 @@ namespace Aberwyn.Data
|
|||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
193
Aberwyn/Data/MenuService.cs
Normal file
193
Aberwyn/Data/MenuService.cs
Normal file
@@ -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<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
|
||||||
|
{
|
||||||
|
var weeklyMenu = new List<WeeklyMenu>();
|
||||||
|
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<Meal> GetMeals()
|
||||||
|
{
|
||||||
|
var meals = new List<Meal>();
|
||||||
|
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
|
||||||
@@ -1,27 +1,33 @@
|
|||||||
# Base image for runtime
|
# Base image for runtime
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 443
|
# Leave the ports exposed to allow Unraid to configure them
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
# Image for building the project
|
# Image for building the project
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# Copy the .csproj and restore dependencies
|
# Copy the .csproj and restore dependencies
|
||||||
COPY ["Aberwyn.csproj", "./"]
|
COPY Aberwyn.csproj ./
|
||||||
RUN dotnet restore "Aberwyn.csproj"
|
RUN dotnet restore
|
||||||
|
|
||||||
# Copy everything else and build the app
|
# Copy everything else and build the app
|
||||||
COPY . .
|
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
|
# Publish the app to the /app/publish folder
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
RUN dotnet publish "Aberwyn.csproj" -c Release -o /app/publish
|
RUN dotnet publish Aberwyn.csproj -c Release -o /app/publish
|
||||||
|
|
||||||
# Use the base runtime image to run the app
|
# Use the base runtime image to run the app
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
|
|
||||||
|
# Use environment variable for ports to allow flexibility
|
||||||
|
ENV ASPNETCORE_URLS="http://+:80"
|
||||||
|
|
||||||
|
# Set entry point to start the application
|
||||||
ENTRYPOINT ["dotnet", "Aberwyn.dll"]
|
ENTRYPOINT ["dotnet", "Aberwyn.dll"]
|
||||||
|
|||||||
@@ -8,12 +8,15 @@
|
|||||||
public class BudgetItem
|
public class BudgetItem
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
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 decimal Amount { get; set; }
|
||||||
public string Category { get; set; }
|
public string Category { get; set; } = string.Empty; // Initialize to an empty string
|
||||||
public string SubCategory { get; set; }
|
public string SubCategory { get; set; } = string.Empty; // Initialize to an empty string
|
||||||
public int Month { get; set; }
|
public int Month { get; set; }
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; } = string.Empty; // Initialize to an empty string
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
Aberwyn/Models/MenuViewModel.cs
Normal file
38
Aberwyn/Models/MenuViewModel.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Aberwyn.Data;
|
||||||
|
using Aberwyn.Models;
|
||||||
|
|
||||||
|
namespace Aberwyn.Models
|
||||||
|
{
|
||||||
|
public class MenuViewModel
|
||||||
|
{
|
||||||
|
public List<Meal> Meals { get; set; } // List of all available meals
|
||||||
|
public List<WeeklyMenu> 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -14,8 +14,11 @@ builder.Services.AddHttpClient(); // Register HttpClient
|
|||||||
builder.Services.AddDbContext<BudgetContext>(options =>
|
builder.Services.AddDbContext<BudgetContext>(options =>
|
||||||
options.UseMySQL(builder.Configuration.GetConnectionString("BudgetDb")));
|
options.UseMySQL(builder.Configuration.GetConnectionString("BudgetDb")));
|
||||||
|
|
||||||
|
|
||||||
// Register your BudgetService with a scoped lifetime
|
// Register your BudgetService with a scoped lifetime
|
||||||
builder.Services.AddScoped<BudgetService>();
|
builder.Services.AddScoped<BudgetService>();
|
||||||
|
builder.Services.AddScoped<MenuService>();
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ if (!app.Environment.IsDevelopment())
|
|||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
//app.UseHttpsRedirection();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|||||||
31
Aberwyn/ViewComponents/RightSidebarViewComponents.cs
Normal file
31
Aberwyn/ViewComponents/RightSidebarViewComponents.cs
Normal file
@@ -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<IViewComponentResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@model Aberwyn.Models.BudgetModel
|
@model Aberwyn.Models.BudgetModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
Layout = "_Layout"; // Assuming you have a layout file named _Layout.cshtml
|
Layout = "_Layout";
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -10,32 +10,29 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Budget Overview</title>
|
<title>Budget Overview</title>
|
||||||
<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.min.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="~/css/site.css">
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="~/css/budget.css">
|
||||||
</head>
|
</head>
|
||||||
<body ng-controller="BudgetController">
|
<body ng-controller="BudgetController">
|
||||||
|
|
||||||
<div class="budget-page">
|
<div class="budget-page">
|
||||||
<h1>Budget Overview</h1>
|
<h1>Budget Overview</h1>
|
||||||
|
|
||||||
<form ng-submit="filterBudget()">
|
<div class="date-picker" ng-click="toggleDatePicker($event)">
|
||||||
<label for="month">Month:</label>
|
<button type="button" class="date-picker-toggle">
|
||||||
<select id="month" ng-model="selectedMonth">
|
{{ selectedMonthName() }} {{ selectedYear }}
|
||||||
<option ng-repeat="month in months" value="{{ month }}">{{ month }}</option>
|
</button>
|
||||||
</select>
|
<div class="date-picker-dropdown" ng-show="isDatePickerVisible">
|
||||||
|
<select ng-model="selectedMonth" ng-options="month as monthName for (month, monthName) in monthOptions" ng-change="updateMonthYear()"></select>
|
||||||
<label for="year">Year:</label>
|
<select ng-model="selectedYear" ng-options="year for year in years" ng-change="updateMonthYear()"></select>
|
||||||
<select id="year" ng-model="selectedYear">
|
</div>
|
||||||
<option ng-repeat="year in years" value="{{ year }}">{{ year }}</option>
|
</div>
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit">Filter</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="budget-container">
|
<div class="budget-container">
|
||||||
<div ng-repeat="category in categories" class="category-block">
|
<div ng-repeat="category in categories" class="category-block">
|
||||||
<div class="category-header">
|
<div class="category-header">
|
||||||
{{ category }}
|
{{ category }}
|
||||||
<span class="total">Total: {{ sumForCategory(category) }}</span> <!-- Total moved to the header -->
|
<span class="total">Total: {{ sumForCategory(category) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="items-wrapper">
|
<div class="items-wrapper">
|
||||||
@@ -49,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="add-item" ng-click="addNewItem(category)">+</div> <!-- Button for adding items -->
|
<div class="add-item" ng-click="addNewItem(category)">+</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +62,7 @@
|
|||||||
$scope.selectedMonth = (today.getMonth() + 1).toString();
|
$scope.selectedMonth = (today.getMonth() + 1).toString();
|
||||||
$scope.selectedYear = today.getFullYear();
|
$scope.selectedYear = today.getFullYear();
|
||||||
$scope.categories = [];
|
$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 () {
|
$scope.loadCategories = function () {
|
||||||
$http.get('/api/budgetapi/categories').then(response => {
|
$http.get('/api/budgetapi/categories').then(response => {
|
||||||
@@ -73,6 +70,7 @@
|
|||||||
}).catch(error => console.error("Error fetching categories:", error));
|
}).catch(error => console.error("Error fetching categories:", error));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Automatically filter when month or year changes
|
||||||
$scope.filterBudget = function () {
|
$scope.filterBudget = function () {
|
||||||
$http.get('/api/budgetapi/items', {
|
$http.get('/api/budgetapi/items', {
|
||||||
params: { month: $scope.selectedMonth, year: $scope.selectedYear }
|
params: { month: $scope.selectedMonth, year: $scope.selectedYear }
|
||||||
@@ -108,8 +106,62 @@
|
|||||||
$scope.budgetItems.push(newItem);
|
$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.loadCategories();
|
||||||
$scope.filterBudget();
|
$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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
98
Aberwyn/Views/Home/Menu.cshtml
Normal file
98
Aberwyn/Views/Home/Menu.cshtml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
@model Aberwyn.Models.MenuViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" ng-app="mealMenuApp">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Meal Menu Overview</title>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||||
|
<script src="~/js/menu.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="~/css/meal-menu.css">
|
||||||
|
</head>
|
||||||
|
<body ng-controller="MealMenuController">
|
||||||
|
|
||||||
|
<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()">Previous Week</button>
|
||||||
|
<span class="week-info">Week {{ selectedWeek }} - {{ selectedYear }}</span>
|
||||||
|
<button type="button" class="date-btn" ng-click="goToNextWeek()">Next Week</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mode-toggle">
|
||||||
|
<button ng-click="toggleEditMode()" ng-show="!isEditing" class="mode-btn">Edit</button>
|
||||||
|
<button ng-click="toggleEditMode()" ng-show="isEditing" class="mode-btn">View</button>
|
||||||
|
</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>Breakfast:</strong> {{ menu[day].breakfastMealName }}</span>
|
||||||
|
</div>
|
||||||
|
<div ng-if="menu[day].lunchMealName">
|
||||||
|
<span><strong>Lunch:</strong> {{ menu[day].lunchMealName }}</span>
|
||||||
|
</div>
|
||||||
|
<div ng-if="menu[day].dinnerMealName">
|
||||||
|
<span><strong>Dinner:</strong> {{ menu[day].dinnerMealName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!menu[day]">
|
||||||
|
<span class="not-assigned">Not Assigned</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meal-edit" ng-if="isEditing">
|
||||||
|
<div>
|
||||||
|
<strong>Breakfast:</strong>
|
||||||
|
<select ng-model="menu[day].breakfastMealId" ng-change="handleMealSelection(day, 'breakfast')"
|
||||||
|
ng-options="meal.id as meal.name for meal in meals">
|
||||||
|
<option value="">Select Meal</option>
|
||||||
|
<option value="new">New Meal</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Lunch:</strong>
|
||||||
|
<select ng-model="menu[day].lunchMealId" ng-change="handleMealSelection(day, 'lunch')"
|
||||||
|
ng-options="meal.id as meal.name for meal in meals">
|
||||||
|
<option value="">Select Meal</option>
|
||||||
|
<option value="new">New Meal</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Dinner:</strong>
|
||||||
|
<select ng-model="menu[day].dinnerMealId" ng-change="handleMealSelection(day, 'dinner')"
|
||||||
|
ng-options="meal.id as meal.name for meal in meals">
|
||||||
|
<option value="">Select Meal</option>
|
||||||
|
<option value="new">New Meal</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button ng-if="isEditing" ng-click="saveMenu()" class="save-btn">Save Menu</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="new-meal-form" ng-if="isEditing">
|
||||||
|
<h3>Add a New Meal</h3>
|
||||||
|
<input type="text" placeholder="Meal Name" ng-model="newMeal.name">
|
||||||
|
<input type="text" placeholder="Description" ng-model="newMeal.description">
|
||||||
|
<input type="text" placeholder="Protein Type" ng-model="newMeal.proteinType">
|
||||||
|
<input type="text" placeholder="Carb Type" ng-model="newMeal.carbType">
|
||||||
|
<input type="text" placeholder="Recipe URL" ng-model="newMeal.recipeUrl">
|
||||||
|
<button ng-click="saveNewMeal()">Save Meal</button>
|
||||||
|
<button ng-click="cancelNewMeal()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,14 +4,28 @@
|
|||||||
|
|
||||||
<h2>Real Estate Website Downloader</h2>
|
<h2>Real Estate Website Downloader</h2>
|
||||||
|
|
||||||
<form method="post" ng-submit="downloadWebsite()">
|
<div ng-app="realEstateApp" ng-controller="RealEstateController">
|
||||||
|
<form ng-submit="fetchImages()">
|
||||||
<label for="urlInput">Enter Website URL:</label>
|
<label for="urlInput">Enter Website URL:</label>
|
||||||
<input type="text" id="urlInput" ng-model="websiteUrl" placeholder="https://example.com" required />
|
<input type="text" id="urlInput" ng-model="websiteUrl" placeholder="https://example.com" required />
|
||||||
<button type="submit">Download</button>
|
<button type="submit">Fetch Images</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div ng-if="downloadStatus">
|
<div ng-if="fetchStatus">
|
||||||
<p>{{ downloadStatus }}</p>
|
<p>{{ fetchStatus }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Display fetched images before download -->
|
||||||
|
<div ng-if="images.length > 0">
|
||||||
|
<h3>Fetched Images:</h3>
|
||||||
|
<div class="image-container">
|
||||||
|
<div ng-repeat="imgSrc in images" class="image-preview">
|
||||||
|
<img ng-src="{{ imgSrc }}" alt="Preview Image" />
|
||||||
|
<p>{{ imgSrc }}</p>
|
||||||
|
<button ng-click="downloadImage(imgSrc)">Download</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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.min.js"></script>
|
||||||
@@ -19,20 +33,63 @@
|
|||||||
angular.module('realEstateApp', [])
|
angular.module('realEstateApp', [])
|
||||||
.controller('RealEstateController', function ($scope, $http) {
|
.controller('RealEstateController', function ($scope, $http) {
|
||||||
$scope.websiteUrl = '';
|
$scope.websiteUrl = '';
|
||||||
$scope.downloadStatus = '';
|
$scope.fetchStatus = '';
|
||||||
|
$scope.images = [];
|
||||||
|
|
||||||
$scope.downloadWebsite = function () {
|
// Fetch images from the backend
|
||||||
|
$scope.fetchImages = function () {
|
||||||
if ($scope.websiteUrl) {
|
if ($scope.websiteUrl) {
|
||||||
$http.post('/api/realestate/download', { url: $scope.websiteUrl })
|
$http.post('/api/realestate/download', { url: $scope.websiteUrl })
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
$scope.downloadStatus = 'Download successful!';
|
$scope.fetchStatus = 'Images fetched successfully!';
|
||||||
console.log(response.data); // Optional: Handle the downloaded data
|
$scope.images = response.data.images; // Display the fetched images
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
$scope.downloadStatus = 'Error downloading website.';
|
$scope.fetchStatus = 'Error fetching images.';
|
||||||
console.error(error);
|
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();
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.image-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview {
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
116
Aberwyn/Views/Shared/Components/RightSidebar/Default.cshtml
Normal file
116
Aberwyn/Views/Shared/Components/RightSidebar/Default.cshtml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
@model IEnumerable<Aberwyn.Models.BudgetItem>
|
||||||
|
|
||||||
|
<aside class="right-sidebar">
|
||||||
|
<!-- Loading Spinner -->
|
||||||
|
<div id="loadingSpinner" style="display:none;">Loading...</div>
|
||||||
|
<!-- Error Message Placeholder -->
|
||||||
|
<div id="errorMessage" style="color:red; display:none;"></div>
|
||||||
|
|
||||||
|
<!-- Budget Section -->
|
||||||
|
<div class="sidebar-section" id="budgetItemsContainer">
|
||||||
|
<h3 class="sidebar-header" onclick="toggleBudgetSection()">
|
||||||
|
<span>Budget this month:</span>
|
||||||
|
<span class="total-budget-amount">0</span>
|
||||||
|
</h3>
|
||||||
|
<div class="sidebar-container">
|
||||||
|
<p>Loading budget items...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const loadingSpinner = document.getElementById("loadingSpinner");
|
||||||
|
const errorMessage = document.getElementById("errorMessage");
|
||||||
|
const budgetItemsContainer = document.getElementById("budgetItemsContainer");
|
||||||
|
const totalBudgetAmount = budgetItemsContainer.querySelector(".total-budget-amount");
|
||||||
|
const sidebarContent = budgetItemsContainer.querySelector(".sidebar-container");
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
loadingSpinner.style.display = "block";
|
||||||
|
errorMessage.style.display = "none"; // Hide error message initially
|
||||||
|
|
||||||
|
// Fetch current month and year
|
||||||
|
const currentDate = new Date();
|
||||||
|
const month = currentDate.getMonth() + 1; // getMonth() returns 0-11, hence +1
|
||||||
|
const year = currentDate.getFullYear();
|
||||||
|
|
||||||
|
// Fetch data using AJAX
|
||||||
|
const fetchUrl = `/api/budgetapi/items?month=${month}&year=${year}`;
|
||||||
|
|
||||||
|
fetch(fetchUrl)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return response.json().then(err => { throw new Error(err.message); });
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
loadingSpinner.style.display = "none"; // Hide loading spinner
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
let totalAmount = 0;
|
||||||
|
sidebarContent.innerHTML = ''; // Clear existing items
|
||||||
|
|
||||||
|
// Group and display items
|
||||||
|
const groupedItems = data.reduce((acc, item) => {
|
||||||
|
acc[item.category] = acc[item.category] || [];
|
||||||
|
acc[item.category].push(item);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Create accordion for each category
|
||||||
|
for (const [category, items] of Object.entries(groupedItems)) {
|
||||||
|
const amount = items.reduce((sum, item) => sum + item.amount, 0);
|
||||||
|
totalAmount += amount; // Sum total amounts
|
||||||
|
|
||||||
|
// Create accordion element
|
||||||
|
const itemElement = `
|
||||||
|
<div class="accordion-item">
|
||||||
|
<div class="accordion-header" onclick="toggleAccordion(this)">
|
||||||
|
<span class="accordion-title">${category}</span>
|
||||||
|
<span class="item-amount">(${amount.toFixed(2)})</span>
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-content">
|
||||||
|
${items.map(item => `<div class="item-name">${item.name}: <span class="item-amount">${item.amount.toFixed(2)}</span></div>`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
sidebarContent.insertAdjacentHTML('beforeend', itemElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update total amount
|
||||||
|
totalBudgetAmount.textContent = totalAmount.toFixed(2); // Format total amount
|
||||||
|
} else {
|
||||||
|
sidebarContent.innerHTML = "<p>No categories available.</p>";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
errorMessage.textContent = error.message; // Display error message
|
||||||
|
errorMessage.style.display = "block"; // Show error message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to toggle accordion visibility
|
||||||
|
function toggleAccordion(header) {
|
||||||
|
const content = header.nextElementSibling;
|
||||||
|
const allContents = document.querySelectorAll(".accordion-content");
|
||||||
|
|
||||||
|
// Close all other accordion items
|
||||||
|
allContents.forEach(item => {
|
||||||
|
if (item !== content) {
|
||||||
|
item.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle current accordion item
|
||||||
|
content.style.display = content.style.display === "block" ? "none" : "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to toggle the budget section visibility
|
||||||
|
function toggleBudgetSection() {
|
||||||
|
const sidebarContainer = document.querySelector(".sidebar-container");
|
||||||
|
sidebarContainer.classList.toggle('collapsed'); // Add 'collapsed' class to toggle visibility
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
@using Aberwyn.Models
|
<!DOCTYPE html>
|
||||||
@model Aberwyn.Models.BudgetModel
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<title>Your Page Title</title>
|
<title>LEWEL - Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
@@ -22,9 +19,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<aside class="left-sidebar"> <!-- Left sidebar -->
|
<aside class="left-sidebar">
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">
|
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">
|
||||||
@@ -41,53 +37,25 @@
|
|||||||
<i class="fas fa-building"></i> RealEstate
|
<i class="fas fa-building"></i> RealEstate
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Menu">
|
||||||
|
<i class="fas fa-utensils"></i> Menu
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
<main role="main" class="main-content"> <!-- Main content area -->
|
|
||||||
@RenderBody() <!-- This is where the content of each page will be rendered -->
|
<main role="main" class="main-content">
|
||||||
|
@RenderBody()
|
||||||
</main>
|
</main>
|
||||||
<aside class="right-sidebar"> <!-- Updated Right Sidebar -->
|
|
||||||
@if (Model != null && Model.BudgetItems != null)
|
<!-- Right Sidebar with budget data from the RightSidebarViewComponent -->
|
||||||
{
|
<aside class="right-sidebar">
|
||||||
<h3 class="sidebar-budget-header">Budget this month: <span class="total-budget-amount">@Model.BudgetItems.Sum(item => item.Amount)</span></h3>
|
@await Component.InvokeAsync("RightSidebar")
|
||||||
<div class="sidebar-budget-container"> <!-- New Container -->
|
|
||||||
@if (Model.BudgetItems.Any())
|
|
||||||
{
|
|
||||||
foreach (var categoryGroup in Model.BudgetItems.GroupBy(item => item.Category))
|
|
||||||
{
|
|
||||||
<div class="sidebar-budget-item">
|
|
||||||
<span class="budget-item-name">@categoryGroup.Key</span>
|
|
||||||
<span class="budget-item-amount">@categoryGroup.Sum(item => item.Amount)</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<p>No categories available.</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<h3>No budget data available.</h3>
|
|
||||||
}
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const budgetHeader = document.querySelector('.sidebar-budget-header');
|
|
||||||
const budgetContainer = document.querySelector('.sidebar-budget-container');
|
|
||||||
|
|
||||||
budgetHeader.addEventListener('click', function () {
|
|
||||||
budgetContainer.style.display = budgetContainer.style.display === 'none' ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initially hide the budget items container
|
|
||||||
budgetContainer.style.display = 'none';
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;"
|
"DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;",
|
||||||
|
"ProductionConnection": "Server=172.18.0.2;Database=Nevyn;Uid=root;Pwd=3edc4RFV;"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
Aberwyn/wwwroot/css/budget.css
Normal file
81
Aberwyn/wwwroot/css/budget.css
Normal file
@@ -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
|
||||||
210
Aberwyn/wwwroot/css/meal-menu.css
Normal file
210
Aberwyn/wwwroot/css/meal-menu.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* Base Styles */
|
/* Base Styles */
|
||||||
body {
|
body {
|
||||||
background-color: #1F2C3C;
|
background-color: #1F2C3C; /* Dark background */
|
||||||
color: #4b004b;
|
color: #B0BEC5; /* Light Gray text for better readability */
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px; /* Keep padding for general layout */
|
padding: 20px; /* Keep padding for general layout */
|
||||||
@@ -10,7 +10,7 @@ body {
|
|||||||
/* Container for Centering and Max Width */
|
/* Container for Centering and Max Width */
|
||||||
.budget-page {
|
.budget-page {
|
||||||
width: 100%; /* Make the page full width */
|
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 */
|
margin: 0 auto; /* Centering */
|
||||||
padding: 0; /* Removed extra padding for budget page */
|
padding: 0; /* Removed extra padding for budget page */
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar, .sidebar-budget-right {
|
.left-sidebar, .sidebar-budget-right {
|
||||||
background-color: #f4f4f4;
|
background-color: #f9f9f9; /* Softer background */
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 10px; /* Adjusted padding for sidebars */
|
padding: 10px; /* Adjusted padding for sidebars */
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
@@ -33,13 +33,13 @@ body {
|
|||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #f4f4f4; /* Light gray instead of white */
|
background-color: #f4f4f4; /* Light gray */
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: #4b004b;
|
color: #3A4E62; /* Muted blue-gray for consistency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.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 {
|
.header-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start; /* Aligns the header to the left */
|
justify-content: flex-start; /* Aligns the header to the left */
|
||||||
@@ -178,7 +70,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: #FFFFFF; /* White for names */
|
color: #ffffff; /* White for names */
|
||||||
font-size: 0.4em; /* Adjust size as needed */
|
font-size: 0.4em; /* Adjust size as needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +106,7 @@ body {
|
|||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
text-decoration: none; /* Remove underline */
|
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 */
|
display: flex; /* Use flexbox to align icon and text */
|
||||||
align-items: center; /* Center items vertically */
|
align-items: center; /* Center items vertically */
|
||||||
}
|
}
|
||||||
@@ -224,34 +116,111 @@ body {
|
|||||||
font-size: 1.2em; /* Adjust icon size */
|
font-size: 1.2em; /* Adjust icon size */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Sidebar Right Styles */
|
/* Sidebar Right Styles */
|
||||||
.right-sidebar {
|
.sidebar-section {
|
||||||
background-color: #f8f9fa; /* Use your existing background color */
|
margin-bottom: 5px;
|
||||||
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-budget-header {
|
.sidebar-header {
|
||||||
cursor: pointer; /* Indicate clickable header */
|
cursor: pointer;
|
||||||
font-weight: bold; /* Make header bold */
|
background-color: #4CAF50; /* Green */
|
||||||
margin: 0; /* Remove default margin */
|
color: white;
|
||||||
padding-bottom: 5px; /* Space below header */
|
padding: 8px; /* Slightly more padding */
|
||||||
}
|
border-radius: 5px;
|
||||||
|
|
||||||
.sidebar-budget-container {
|
|
||||||
margin-top: 10px; /* Space above budget items */
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-budget-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between; /* Space out name and amount */
|
justify-content: space-between;
|
||||||
padding: 5px 0; /* Reduced padding for compactness */
|
align-items: center; /* Center items vertically */
|
||||||
border-bottom: 1px solid #e0e0e0; /* Optional: Divider line */
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s; /* Smooth hover transition */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-budget-item:last-child {
|
.sidebar-header:hover {
|
||||||
border-bottom: none; /* Remove border from last item */
|
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;
|
||||||
}
|
}
|
||||||
117
Aberwyn/wwwroot/js/menu.js
Normal file
117
Aberwyn/wwwroot/js/menu.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user