New stuff
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<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.Tools" Version="6.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -20,4 +21,9 @@
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="NewFolder1\" />
|
||||
<Folder Include="NewFolder\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ namespace Aberwyn.Controllers
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly BudgetService _budgetService;
|
||||
private readonly MenuService _menuService;
|
||||
|
||||
|
||||
// Constructor to inject dependencies
|
||||
public HomeController(ILogger<HomeController> logger, BudgetService budgetService)
|
||||
public HomeController(ILogger<HomeController> 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)
|
||||
{
|
||||
|
||||
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.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<IActionResult> FindImages([FromBody] UrlRequest request)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
[HttpPost("download")]
|
||||
public async Task<IActionResult> 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<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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class WebsiteRequest
|
||||
public class UrlRequest
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
@@ -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<string> GetCategories()
|
||||
{
|
||||
@@ -138,5 +145,4 @@ namespace Aberwyn.Data
|
||||
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
|
||||
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"]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 =>
|
||||
options.UseMySQL(builder.Configuration.GetConnectionString("BudgetDb")));
|
||||
|
||||
|
||||
// Register your BudgetService with a scoped lifetime
|
||||
builder.Services.AddScoped<BudgetService>();
|
||||
builder.Services.AddScoped<MenuService>();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -26,7 +29,7 @@ if (!app.Environment.IsDevelopment())
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
//app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
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
|
||||
|
||||
@{
|
||||
Layout = "_Layout"; // Assuming you have a layout file named _Layout.cshtml
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -10,32 +10,29 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Budget Overview</title>
|
||||
<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>
|
||||
<body ng-controller="BudgetController">
|
||||
|
||||
<div class="budget-page">
|
||||
<h1>Budget Overview</h1>
|
||||
|
||||
<form ng-submit="filterBudget()">
|
||||
<label for="month">Month:</label>
|
||||
<select id="month" ng-model="selectedMonth">
|
||||
<option ng-repeat="month in months" value="{{ month }}">{{ month }}</option>
|
||||
</select>
|
||||
|
||||
<label for="year">Year:</label>
|
||||
<select id="year" ng-model="selectedYear">
|
||||
<option ng-repeat="year in years" value="{{ year }}">{{ year }}</option>
|
||||
</select>
|
||||
|
||||
<button type="submit">Filter</button>
|
||||
</form>
|
||||
<div class="date-picker" ng-click="toggleDatePicker($event)">
|
||||
<button type="button" class="date-picker-toggle">
|
||||
{{ selectedMonthName() }} {{ selectedYear }}
|
||||
</button>
|
||||
<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>
|
||||
<select ng-model="selectedYear" ng-options="year for year in years" ng-change="updateMonthYear()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="budget-container">
|
||||
<div ng-repeat="category in categories" class="category-block">
|
||||
<div class="category-header">
|
||||
{{ category }}
|
||||
<span class="total">Total: {{ sumForCategory(category) }}</span> <!-- Total moved to the header -->
|
||||
<span class="total">Total: {{ sumForCategory(category) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="items-wrapper">
|
||||
@@ -49,7 +46,7 @@
|
||||
</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>
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
</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>
|
||||
|
||||
<form method="post" ng-submit="downloadWebsite()">
|
||||
<label for="urlInput">Enter Website URL:</label>
|
||||
<input type="text" id="urlInput" ng-model="websiteUrl" placeholder="https://example.com" required />
|
||||
<button type="submit">Download</button>
|
||||
</form>
|
||||
<div ng-app="realEstateApp" ng-controller="RealEstateController">
|
||||
<form ng-submit="fetchImages()">
|
||||
<label for="urlInput">Enter Website URL:</label>
|
||||
<input type="text" id="urlInput" ng-model="websiteUrl" placeholder="https://example.com" required />
|
||||
<button type="submit">Fetch Images</button>
|
||||
</form>
|
||||
|
||||
<div ng-if="downloadStatus">
|
||||
<p>{{ downloadStatus }}</p>
|
||||
<div ng-if="fetchStatus">
|
||||
<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>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
@@ -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();
|
||||
};
|
||||
});
|
||||
</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
|
||||
@model Aberwyn.Models.BudgetModel
|
||||
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="~/css/site.css" asp-append-version="true" />
|
||||
<title>Your Page Title</title>
|
||||
<title>LEWEL - Dashboard</title>
|
||||
</head>
|
||||
<header class="header">
|
||||
<div class="header-container">
|
||||
@@ -22,9 +19,8 @@
|
||||
</div>
|
||||
</header>
|
||||
<body>
|
||||
|
||||
<div class="grid-container">
|
||||
<aside class="left-sidebar"> <!-- Left sidebar -->
|
||||
<aside class="left-sidebar">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">
|
||||
@@ -41,53 +37,25 @@
|
||||
<i class="fas fa-building"></i> RealEstate
|
||||
</a>
|
||||
</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>
|
||||
</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>
|
||||
<aside class="right-sidebar"> <!-- Updated Right Sidebar -->
|
||||
@if (Model != null && Model.BudgetItems != null)
|
||||
{
|
||||
<h3 class="sidebar-budget-header">Budget this month: <span class="total-budget-amount">@Model.BudgetItems.Sum(item => item.Amount)</span></h3>
|
||||
<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>
|
||||
}
|
||||
|
||||
<!-- Right Sidebar with budget data from the RightSidebarViewComponent -->
|
||||
<aside class="right-sidebar">
|
||||
@await Component.InvokeAsync("RightSidebar")
|
||||
</aside>
|
||||
</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>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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;"
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
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 */
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
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